SObjectizer-5 Extra
pub.hpp
Go to the documentation of this file.
1 /*!
2  * \file
3  * \brief Implementation of synchronous operations on top of SObjectizer.
4  *
5  * \since
6  * v.1.3.0
7  */
8 
9 #pragma once
10 
11 #include <so_5_extra/error_ranges.hpp>
12 
13 #include <so_5_extra/mchains/fixed_size.hpp>
14 
15 #include <so_5/version.hpp>
16 #if SO_5_VERSION < SO_5_VERSION_MAKE(5u, 7u, 3u)
17 #error "SObjectizer-5.7.3 is required"
18 #endif
19 
20 #include <so_5/send_functions.hpp>
21 
22 #include <so_5/details/always_false.hpp>
23 
24 #include <variant>
25 
26 namespace so_5 {
27 
28 namespace extra {
29 
30 namespace sync {
31 
32 namespace errors {
33 
34 /*!
35  * \brief An attempt to send a new reply when the reply is already sent.
36  *
37  * Only one reply can be sent as a result of request_reply-interaction.
38  * An attempt to send another reply is an error.
39  */
40 const int rc_reply_was_sent =
42 
43 /*!
44  * \brief No reply.
45  *
46  * The reply has not been received after waiting for the specified time.
47  */
48 const int rc_no_reply =
50 
51 } /* namespace errors */
52 
53 namespace details
54 {
55 
56 //
57 // ensure_no_mutability_modificators
58 //
59 /*!
60  * \brief Helper class to ensure that immutable_msg/mutable_msg
61  * modificators are not used.
62  */
63 template< typename T >
65  {
66  using type = T;
67  };
68 
69 template< typename T >
71  {
72  static_assert( so_5::details::always_false<T>::value,
73  "so_5::immutable_msg<T> modificator can't be used with "
74  "so_5::extra::sync::request_reply_t" );
75  };
76 
77 template< typename T >
79  {
80  static_assert( so_5::details::always_false<T>::value,
81  "so_5::mutable_msg<T> modificator can't be used with "
82  "so_5::extra::sync::request_reply_t" );
83  };
84 
85 /*!
86  * \brief A short form of ensure_no_mutability_modificators metafunction.
87  */
88 template< typename T >
89 using ensure_no_mutability_modificators_t =
91 
92 //
93 // reply_target_t
94 //
95 /*!
96  * \brief Type of storage for reply's target.
97  *
98  * A reply can be sent to a mchain or to a mbox. If a mchain is used as
99  * a target then it should be closed.
100  *
101  * This type allow to distinguish between mchain and mbox cases.
102  */
104 
105 //
106 // reply_target_holder_t
107 //
108 /*!
109  * \brief A special holder for reply_target instance.
110  *
111  * This class performs the proper cleanup in the destructor: if
112  * reply_target instance holds a mchain that mchain will be closed
113  * automatically.
114  *
115  * \note
116  * This class is not Moveable nor Copyable.
117  */
118 class reply_target_holder_t final
119  {
120  //! The target for the reply message.
122 
123  public :
124  reply_target_holder_t( reply_target_t target ) noexcept
125  : m_target{ std::move(target) }
126  {}
127 
129  {
130  struct closer_t final {
131  void operator()( const mchain_t & ch ) const noexcept {
132  // Close the reply chain.
133  // If there is no reply but someone is waiting
134  // on that chain it will be awakened.
135  // NOTE: because we're in the destructor then
136  // throwing exceptions from close_retain_content is
137  // prohibited.
138  close_retain_content( so_5::terminate_if_throws, ch );
139  }
140  void operator()( const mbox_t & ) const noexcept {
141  // Nothing to do.
142  }
143  };
144 
145  std::visit( closer_t{}, m_target );
146  }
147 
148  reply_target_holder_t( const reply_target_holder_t & ) = delete;
149  reply_target_holder_t( reply_target_holder_t && ) = delete;
150 
151  //! Getter.
152  const auto &
153  target() const noexcept { return m_target; }
154  };
155 
156 //
157 // query_actual_reply_target
158 //
159 /*!
160  * \brief Helper function for extraction of actual reply target
161  * from reply_target instance.
162  *
163  * \note
164  * Reply target is returned as mbox_t.
165  */
166 inline mbox_t
168  {
169  struct extractor_t {
170  mbox_t operator()( const mchain_t & ch ) const noexcept {
171  return ch->as_mbox();
172  }
173  mbox_t operator()( const mbox_t & mbox ) const noexcept {
174  return mbox;
175  }
176  };
177 
178  return std::visit( extractor_t{}, rt );
179  }
180 
181 //
182 // basic_request_reply_part_t
183 //
184 /*!
185  * \brief The basic part of implementation of request_reply type.
186  */
187 template< typename Request, typename Reply >
189  {
190  public :
191  //! An actual type of request.
193  //! An actual type of reply.
195 
196  protected :
197  //! Is reply moveable type?
198  static constexpr const bool is_reply_moveable =
201 
202  //! Is reply copyable type?
203  static constexpr const bool is_reply_copyable =
206 
207  static_assert( is_reply_moveable || is_reply_copyable,
208  "Reply type should be MoveAssignable or CopyAssignable" );
209 
210  //! The target for the reply.
211  reply_target_holder_t m_reply_target;
212  //! The flag for detection of repeated replies.
213  /*!
214  * Recives `true` when the first reply is sent.
215  */
216  bool m_reply_sent{ false };
217 
218  //! Initializing constructor.
219  basic_request_reply_part_t( reply_target_t reply_target ) noexcept
221  {}
222 
223  public :
225  };
226 
227 //
228 // request_holder_part_t
229 //
230 /*!
231  * \brief The type to be used as holder for request type instance.
232  *
233  * Has two specialization for every variant of \a is_signal parameter.
234  */
235 template< typename Base, bool is_signal >
236 class request_holder_part_t;
237 
238 /*!
239  * \brief A specialization for the case when request type is not a signal.
240  *
241  * This specialization holds an instance of request type.
242  * This instance is constructed in request_holder_part_t's constructor
243  * and is accessible via getters.
244  */
245 template< typename Base >
246 class request_holder_part_t<Base, false> : public Base
247  {
248  protected :
249  //! An actual request object.
250  typename Base::request_t m_request;
251 
252  //! Initializing constructor.
253  template< typename... Args >
255  reply_target_t reply_target,
256  Args && ...args )
257  : Base{ std::move(reply_target) }
258  , m_request{ std::forward<Args>(args)... }
259  {}
260 
261  public :
262  //! Getter for the case of const object.
263  const auto &
264  request() const noexcept { return m_request; }
265 
266  //! Getter for the case of non-const object.
267  auto &
268  request() noexcept { return m_request; }
269  };
270 
271 /*!
272  * \brief A specialization for the case when request type is a signal.
273  *
274  * There is no need to hold anything. Becase of that this type is empty.
275  */
276 template< typename Base >
277 class request_holder_part_t<Base, true> : public Base
278  {
279  protected :
280  using Base::Base;
281  };
282 
283 } /* namespace details */
284 
285 //
286 // close_reply_chain_flag_t
287 //
288 /*!
289  * \brief A flag to specify should the reply chain be closed automatically.
290  */
292  {
293  //! The reply chain should be automatically closed when
294  //! the corresponding request_reply_t instance is being destroyed.
295  close,
296  //! The reply chain shouldn't be closed even if the corresponding
297  //! request_reply_t instance is destroyed.
298  //! A user should close the reply chain manually.
300  };
301 
302 //! The indicator that the reply chain should be closed automatically.
303 /*!
304  * If this flag is used then the reply chain will be automatically closed when
305  * the corresponding request_reply_t instance is being destroyed.
306  *
307  * Usage example:
308  * \code
309  * using my_ask_reply = so_5::extra::sync::request_reply_t<my_request, my_reply>;
310  * // Create the reply chain manually.
311  * auto reply_ch = create_mchain(env);
312  * // Issue a request.
313  * my_ask_reply::initiate_with_custom_reply_to(
314  * target,
315  * reply_ch,
316  * so_5::extra::sync::close_reply_chain,
317  * ...);
318  * ... // Do something.
319  * // Now we can read the reply.
320  * // The reply chain will be automatically closed after dispatching of the request.
321  * receive(from(reply_ch).handle_n(1),
322  * [](typename my_ask_reply::reply_mhood_t cmd) {...});
323  * \endcode
324  */
327 
328 //! The indicator that the reply chain shouldn't be closed automatically.
329 /*!
330  * If this flag is used then the reply chain won't be automatically closed when
331  * the corresponding request_reply_t instance is being destroyed. It means that
332  * one reply chain can be used for receiving of different replies:
333  * \code
334  * using one_ask_reply = so_5::extra::sync::request_reply_t<one_request, one_reply>;
335  * using another_ask_reply = so_5::extra::sync::request_reply_t<another_request, another_reply>;
336  *
337  * // Create the reply chain manually.
338  * auto reply_ch = create_mchain(env);
339  * // Issue the first request.
340  * one_ask_reply::initiate_with_custom_reply_to(
341  * one_target,
342  * reply_ch,
343  * so_5::extra::sync::do_not_close_reply_chain,
344  * ...);
345  * // Issue the second request.
346  * another_ask_reply::initiate_with_custom_reply_to(
347  * another_target,
348  * reply_ch,
349  * so_5::extra::sync::do_not_close_reply_chain,
350  * ...);
351  * ... // Do something.
352  * // Now we can read the replies.
353  * receive(from(reply_ch).handle_n(2),
354  * [](typename one_ask_reply::reply_mhood_t cmd) {...},
355  * [](typename another_ask_reply::reply_mhood_t cmd) {...});
356  * \endcode
357  */
360 
361 //
362 // request_reply_t
363 //
364 /*!
365  * \brief A special class for performing interactions between
366  * agents in request-reply maner.
367 
368 Some older versions of SObjectizer-5 supported synchronous interactions between
369 agents. But since SObjectizer-5.6 this functionality has been removed from
370 SObjectizer core. Some form of synchronous interaction is now supported via
371 so_5::extra::sync.
372 
373 The request_reply_t template is the main building block for the synchronous
374 interaction between agents in the form of request-reply. The basic usage
375 example is looked like the following:
376 
377 \code
378 struct my_request {
379  int a_;
380  std::string b_;
381 };
382 
383 struct my_reply {
384  std::string c_;
385  std::pair<int, int> d_;
386 };
387 
388 namespace sync_ns = so_5::extra::sync;
389 
390 // The agent that processes requests.
391 class service final : public so_5::agent_t {
392  ...
393  void on_request(
394  sync_ns::request_mhood_t<my_request, my_reply> cmd) {
395  ...; // Some processing.
396  // Now the reply can be sent. Instance of my_reply will be created
397  // automatically.
398  cmd->make_reply(
399  // Value for my_reply::c_ field.
400  "Reply",
401  // Value for my_reply::d_ field.
402  std::make_pair(0, 1) );
403  }
404 }
405 ...
406 // Mbox of service agent.
407 const so_5::mbox_t svc_mbox = ...;
408 
409 // Issue the request and wait reply for at most 15s.
410 // An exception of type so_5::exception_t will be sent if reply
411 // is not received in 15 seconds.
412 my_reply reply = sync_ns::request_reply<my_request, my_reply>(
413  // Destination of the request.
414  svc_mbox,
415  // Max waiting time.
416  15s,
417  // Parameters for initialization of my_request instance.
418  // Value for my_request::a_ field.
419  42,
420  // Value for my_request::b_ field.
421  "Request");
422 
423 // Or, if we don't want to get an exception.
424 optional<my_reply> opt_reply = sync_ns::request_opt_reply<my_request, my_reply>(
425  svc_mbox,
426  15s,
427  4242,
428  "Request #2");
429 \endcode
430 
431 When a user calls request_reply() or request_opt_reply() helper function an
432 instance of request_reply_t class is created and sent to the destination.
433 This instance is sent as a mutable message, because of that it should be
434 received via mutable_mhood_t. Usage of so_5::extra::sync::request_mhood_t
435 template is the most convenient way to receive instances of request_reply_t
436 class.
437 
438 If Request is not a type of a signal then request_reply_t holds an instance
439 of that type inside. This instance is accessible via request_reply_t::request()
440 getter:
441 
442 \code
443 struct my_request {
444  int a_;
445  std::string b_;
446 };
447 
448 struct my_reply {...};
449 
450 namespace sync_ns = so_5::extra::sync;
451 
452 // The agent that processes requests.
453 class service final : public so_5::agent_t {
454  ...
455  void on_request(
456  sync_ns::request_mhood_t<my_request, my_reply> cmd) {
457  std::cout << "request received: " << cmd->request().a_
458  << ", " << cmd->request().b_ << std::endl;
459  ...
460  }
461 };
462 \endcode
463 
464 If Request is a type of a signal then there is no request_reply_t::request()
465 getter.
466 
467 To make a reply it is necessary to call request_reply_t::make_reply() method:
468 
469 \code
470 void some_agent::on_request(so_5::extra::sync::request_mhood_t<Request, Reply> cmd) {
471  ...;
472  cmd->make_reply(
473  // This arguments will be passed to Reply's constructor.
474  ... );
475 }
476 \endcode
477 
478 \note
479 The make_reply() method can be called only once. An attempt to do the second
480 call to make_reply() will lead to raising exception of type
481 so_5::exception_t.
482 
483 Please note that repeating of Request and Reply types, again and again, is not
484 a good idea. For example, the following code can be hard to maintain:
485 \code
486 void some_agent::on_some_request(
487  sync_ns::request_mhood_t<some_request, some_reply> cmd)
488 {...}
489 
490 void some_agent::on_another_request(
491  sync_ns::request_mhood_t<another_request, another_reply> cmd)
492 {...}
493 
494 ...
495 auto some_result = sync_ns::request_reply<some_request, some_reply>(...);
496 ...
497 auto another_result = sync_ns::request_reply<another_request, another_reply>(...);
498 \endcode
499 
500 It is better to define a separate name for Request and Reply pair and
501 use this name:
502 \code
503 using some_request_reply = sync_ns::request_reply_t<some_request, some_reply>;
504 using another_request_reply = sync_ns::request_reply_t<another_request, another_reply>;
505 
506 void some_agent::on_some_request(
507  typename some_request_reply::request_mhood_t cmd)
508 {...}
509 
510 void some_agent::on_another_request(
511  typename another_request_reply::request_mhood_t cmd)
512 {...}
513 
514 ...
515 auto some_result = some_request_reply::ask_value(...);
516 ...
517 auto another_result = another_request_reply::ask_value(...);
518 \endcode
519 
520 \attention The request_reply_t is not thread safe class.
521 
522 \attention Message mutability indicators like so_5::mutable_msg and
523 so_5::immutable_msg can't be used for Request and Reply parameters. It means
524 that the following code will lead to compilation errors:
525 \code
526 // NOTE: so_5::immutable_msg can't be used here!
527 auto v1 = so_5::extra::sync::request_value<so_5::immutable_msg<Q>, A>(...);
528 
529 // NOTE: so_5::mutable_msg can't be used here!
530 auto v2 = so_5::extra::sync::request_value<Q, so_5::mutable_msg<A>>(...);
531 \endcode
532 
533 \par A custom destination for the reply message
534 
535 By default the reply message is sent to a separate mchain created inside
536 request_value() and request_opt_value() functions (strictly speacking
537 this mchain is created inside request_reply_t::initiate() function and
538 then used by request_value() and request_opt_value() functions).
539 
540 But sometimes it can be necessary to have an ability to specify a custom
541 destination for the reply message. It can be done via
542 request_reply_t::initiate_with_custom_reply_to() methods. For example,
543 the following code sends two requests to different targets but all replies
544 are going to the same mchain:
545 \code
546 using first_ask_reply = so_5::extra::sync::request_reply_t<first_request, first_reply>;
547 using second_ask_reply = so_5::extra::sync::request_reply_t<second_request, second_reply>;
548 
549 auto reply_ch = create_mchain(env);
550 
551 // Sending of requests.
552 first_ask_reply::initiate_with_custom_reply_to(
553  first_target,
554  // do_not_close_reply_chain is mandatory in that scenario.
555  reply_ch, so_5::extra::sync::do_not_close_reply_chain,
556  ... );
557 second_ask_reply::initiate_with_custom_reply_to(
558  second_target,
559  // do_not_close_reply_chain is mandatory in that scenario.
560  reply_ch, so_5::extra::sync::do_not_close_reply_chain,
561  ... );
562 
563 ... // Do something.
564 
565 // Now we want to receive and handle replies.
566 receive(from(reply_ch).handle_n(2).empty_timeout(15s),
567  [](typename first_ask_reply::reply_mhood_t cmd) {...},
568  [](typename second_ask_reply::reply_mhood_t cmd) {...});
569 \endcode
570 
571 \tparam Request a type of a request. It can be type of a signal.
572 
573 \tparam Reply a type of a reply. This type should be
574 MoveAssignable+MoveConstructible or CopyAssignable+CopyConstructible.
575 
576  */
577 template<typename Request, typename Reply>
579  : public details::request_holder_part_t<
582  {
586 
587  public :
588  //! An actual type of request.
589  using request_t = typename base_type::request_t;
590  //! An actual type of reply.
591  using reply_t = typename base_type::reply_t;
592 
593  //! A shorthand for mhood for receiving request object.
594  /*!
595  * An instance of requst is sent as mutabile message of
596  * type request_reply_t<Q,A>. It means that this message
597  * can be received if a request handler has the following
598  * prototype:
599  * \code
600  * void handler(so_5::mhood_t<so_5::mutable_msg<so_5::extra::sync::request_reply_t<Q,A>>>);
601  * \endcode
602  * or:
603  * \code
604  * void handler(so_5::mutable_mhood_t<so_5::extra::sync::request_reply_t<Q,A>>);
605  * \endcode
606  * But it is better to use request_mhood_t typedef:
607  * \code
608  * void handler(typename so_5::extra::sync::request_reply_t<Q,A>::request_mhood_t);
609  * \endcode
610  * And yet better to use request_mhood_t typedef that way:
611  * \code
612  * using my_request_reply = so_5::extra::sync::request_reply_t<Q,A>;
613  * void handler(typename my_request_reply::request_mhood_t);
614  * \endcode
615  */
617 
618  //! A shorthand for mhood for receiving reply object.
619  /*!
620  * An instance of requst is sent as mutabile message of
621  * type reply_t. It means that this message
622  * can be received if a message handler has the following
623  * prototype:
624  * \code
625  * void handler(so_5::mhood_t<so_5::mutable_msg<typename so_5::extra::sync::request_reply_t<Q,A>::reply_t>>);
626  * \endcode
627  * or:
628  * \code
629  * void handler(so_5::mutable_mhood_t<typename so_5::extra::sync::request_reply_t<Q,A>::reply_t>);
630  * \endcode
631  * But it is better to use reply_mhood_t typedef:
632  * \code
633  * void handler(typename so_5::extra::sync::request_reply_t<Q,A>::reply_mhood_t);
634  * \endcode
635  * And yet better to use reply_mhood_t typedef that way:
636  * \code
637  * using my_request_reply = so_5::extra::sync::request_reply_t<Q,A>;
638  * void handler(typename my_request_reply::reply_mhood_t);
639  * \endcode
640  *
641  * \note
642  * Usage of that typedef can be necessary only if you implement
643  * you own handling of reply-message from the reply chain.
644  */
646 
647  //! A shorthand for message_holder that can hold request_reply instance.
648  /*!
649  * Usage example:
650  * \code
651  * using my_request_reply = so_5::extra::sync::request_reply_t<my_request, my_reply>;
652  *
653  * class requests_collector final : public so_5::agent_t {
654  * // Container for holding received requests.
655  * std::vector<typename my_request_reply::holder_t> requests_;
656  *
657  * ...
658  * void on_request(typename my_request_reply::request_mhood_t cmd) {
659  * // Store the request to process it later.
660  * requests_.push_back(cmd.make_holder());
661  * }
662  * };
663  * \endcode
664  */
666 
667  private :
668  using base_type::base_type;
669 
672 
674 
675  //! Helper method for getting the result value from reply_mhood
676  //! with respect to moveability of reply object.
677  template< typename Reply_Receiver >
678  static void
680  reply_mhood_t & cmd,
681  Reply_Receiver & result )
682  {
683  if constexpr( is_reply_moveable )
684  result = std::move(*cmd);
685  else
686  result = *cmd;
687  }
688 
689  //! An actual implementation of ask_value for the case when
690  //! reply object is DefaultConstructible.
691  template<typename Target, typename Duration, typename... Args>
692  [[nodiscard]]
693  static auto
695  Target && target,
696  Duration duration,
697  Args && ...args )
698  {
699  // Because reply object is default constructible we can create it
700  // on the stack. And this can be the subject of NRVO.
701  reply_t result;
702 
703  auto reply_ch = initiate(
704  std::forward<Target>(target),
705  std::forward<Args>(args)... );
706 
707  bool result_received{false};
708  receive(
709  from(reply_ch).handle_n(1).empty_timeout(duration),
710  [&result, &result_received]( reply_mhood_t cmd ) {
711  result_received = true;
712  borrow_from_reply_mhood( cmd, result );
713  } );
714 
715  if( !result_received )
716  {
717  SO_5_THROW_EXCEPTION( errors::rc_no_reply,
718  std::string{ "no reply received, request_reply type: " } +
719  typeid(request_reply_t).name() );
720  }
721 
722  return result;
723  }
724 
725  //! An actual implementation of request_value for the case when
726  //! reply object is not DefaultConstructible.
727  template<typename Target, typename Duration, typename... Args>
728  [[nodiscard]]
729  static auto
731  Target && target,
732  Duration duration,
733  Args && ...args )
734  {
735  // For the case when Reply is not default constructible we
736  // will use request_opt_value that can handle then case.
737  auto result = ask_opt_value(
738  std::forward<Target>(target),
739  duration,
740  std::forward<Args>(args)... );
741 
742  if( !result )
743  {
744  SO_5_THROW_EXCEPTION( errors::rc_no_reply,
745  std::string{ "no reply received, request_reply type: " } +
746  typeid(request_reply_t).name() );
747  }
748 
749  if constexpr( is_reply_moveable )
750  return std::move(*result);
751  else
752  return *result;
753  }
754 
755  public :
756  /*!
757  * \brief Initiate a request by sending request_reply_t message instance.
758  *
759  * This method creates a mchain for reply, then instantiates and
760  * sends an instance of request_reply_t<Request, Reply> type.
761  *
762  * Returns the reply mchain.
763  *
764  * Note that this method is used in implementation of ask_value()
765  * and ask_opt_value() methods. Call it only if you want to receive
766  * reply message by itself. For example usage of initiate() can
767  * be useful in a such case:
768  * \code
769  * // Issue the first request.
770  * auto ch1 = so_5::extra::sync::request_reply_t<Ask1, Reply1>::initiate(target1, ..);
771  * // Issue the second request.
772  * auto ch2 = so_5::extra::sync::request_reply_t<Ask2, Reply2>::initiate(target2, ...);
773  * // Issue the third request.
774  * auto ch3 = so_5::extra::sync::request_reply_t<Ask3, Reply3>::initiate(target3, ...);
775  * // Wait and handle requests in any order of appearance.
776  * so_5::select( so_5::from_all().handle_all(),
777  * case_(ch1, [](so_5::extra::sync::reply_mhood_t<Ask1, Reply1> cmd) {...}),
778  * case_(ch2, [](so_5::extra::sync::reply_mhood_t<Ask2, Reply2> cmd) {...}),
779  * case_(ch3, [](so_5::extra::sync::reply_mhood_t<Ask3, Reply3> cmd) {...}));
780  * \endcode
781  */
782  template< typename Target, typename... Args >
783  [[nodiscard]]
784  static mchain_t
785  initiate( const Target & target, Args && ...args )
786  {
787  // Only one message should be stored in reply_ch.
791 
793  // Calling 'new' directly because request_reply_t has
794  // private constructor.
795  new request_reply_t{ mchain, std::forward<Args>(args)... }
796  };
797 
798  send( target, std::move(msg) );
799 
800  return mchain;
801  }
802 
803  /*!
804  * \brief Initiate a request by sending request_reply_t message instance
805  * with sending the reply to the specified mbox.
806  *
807  * A new instance of request_reply_t message is created and sent to
808  * \a target. The reply will be sent to \a reply_to mbox.
809  *
810  * Usage example:
811  * \code
812  * using my_ask_reply = so_5::extra::sync::request_reply_t<my_request, my_reply>;
813  * class consumer final : public so_5::agent_t {
814  * void on_some_event(mhood_t<some_event> cmd) {
815  * // Prepare for issuing a request.
816  * // New mbox for receiving the reply.
817  * auto reply_mbox = so_make_new_direct_mbox();
818  * // Make subscription for handling the reply.
819  * so_subscribe(reply_mbox).event(
820  * [this](typename my_ask_reply::reply_mhood_t cmd) {
821  * ... // Some handling.
822  * });
823  * // Issuing a request with redirection of the reply to
824  * // the new reply mbox.
825  * my_ask_reply::initiate_with_custom_reply_to(target, reply_mbox, ...);
826  * }
827  * };
828  * \endcode
829  */
830  template< typename Target, typename... Args >
831  static void
833  const Target & target,
834  const mbox_t & reply_to,
835  Args && ...args )
836  {
838  // Calling 'new' directly because request_reply_t has
839  // private constructor.
841  };
842 
843  send( target, std::move(msg) );
844  }
845 
846  /*!
847  * \brief Initiate a request by sending request_reply_t message instance
848  * with sending the reply to the direct mbox of the specified agent.
849  *
850  * A new instance of request_reply_t message is created and sent to
851  * \a target. The reply will be sent to the direct mbox \a reply_to agent.
852  *
853  * Usage example:
854  * \code
855  * using my_ask_reply = so_5::extra::sync::request_reply_t<my_request, my_reply>;
856  * class consumer final : public so_5::agent_t {
857  * void on_some_event(mhood_t<some_event> cmd) {
858  * // Prepare for issuing a request.
859  * // Make subscription for handling the reply.
860  * so_subscribe_self().event(
861  * [this](typename my_ask_reply::reply_mhood_t cmd) {
862  * ... // Some handling.
863  * });
864  * // Issuing a request with redirection of the reply to
865  * // the direct mbox of the agent.
866  * my_ask_reply::initiate_with_custom_reply_to(target, *this, ...);
867  * }
868  * };
869  * \endcode
870  */
871  template< typename Target, typename... Args >
872  static void
874  const Target & target,
875  const agent_t & reply_to,
876  Args && ...args )
877  {
879  // Calling 'new' directly because request_reply_t has
880  // private constructor.
881  new request_reply_t{
883  };
884 
885  send( target, std::move(msg) );
886  }
887 
888  /*!
889  * \brief Initiate a request by sending request_reply_t message instance
890  * with sending the reply to the specified mchain.
891  *
892  * A new instance of request_reply_t message is created and sent to
893  * \a target. The reply will be sent to \a reply_ch mchain.
894  *
895  * Argument \a close_flag specifies what should be done with \a reply_ch
896  * after destroying of request_reply_t message instance:
897  * - if \a close_flag is so_5::extra::sync::close_reply_chain then \a
898  * reply_ch will be automatically closed;
899  * - if \a close_flag is so_5::extra::sync::do_not_close_reply_chain then
900  * \a reply_ch won't be closed. In that case \a reply_ch mchain can be
901  * used for handling replies from several requests. See description of
902  * so_5::extra::sync::do_not_close_reply_chain for an example.
903  */
904  template< typename Target, typename... Args >
905  static void
907  const Target & target,
908  const mchain_t & reply_ch,
909  close_reply_chain_flag_t close_flag,
910  Args && ...args )
911  {
913 
914  switch( close_flag )
915  {
916  case close_reply_chain:
918  reply_ch,
919  std::forward<Args>(args)... } };
920  break;
921 
924  reply_ch->as_mbox(),
925  std::forward<Args>(args)... } };
926  break;
927  }
928 
929  send( target, std::move(msg) );
930  }
931 
932  /*!
933  * \brief Make the reply and send it back.
934  *
935  * Instantiates an instance of type Reply and send it back to
936  * the reply chain.
937  *
938  * \attention This method should be called at most once.
939  * An attempt to call it twice will lead to an exception.
940  *
941  * Usage example:
942  * \code
943  * void some_agent::on_request(
944  * so_5::extra::sync::request_reply_t<Request, Reply> cmd)
945  * {
946  * ... // Some processing of the request.
947  * // Sending the reply back.
948  * cmd->make_reply(...);
949  * }
950  * \endcode
951  *
952  * \tparam Args types of arguments for Reply's constructor.
953  */
954  template< typename... Args >
955  void
956  make_reply( Args && ...args )
957  {
958  if( this->m_reply_sent )
960  std::string{ "reply has already been sent, "
961  "request_reply type: " } +
962  typeid(request_reply_t).name() );
963 
966  this->m_reply_target.target() ),
967  std::forward<Args>(args)... );
968 
969  this->m_reply_sent = true;
970  }
971 
972  /*!
973  * \brief Send a request and wait for the reply.
974  *
975  * This method instantiates request_reply_t object using
976  * \a args arguments for initializing instance of Request type.
977  * Then the request_reply_t is sent as a mutable message to
978  * \a target. Then this method waits the reply for no more
979  * than \a duration. If there is no reply then an empty
980  * std::optional is returned.
981  *
982  * Returns an instance of std::optional<Reply>.
983  *
984  * Usage example:
985  * \code
986  * struct check_user_request final {...};
987  * struct check_user_result final {...};
988  * using check_user = so_5::extra::sync::request_reply_t<check_user_request, check_user_result>;
989  * ...
990  * std::optional<check_user_result> result = check_user::ask_opt_value(
991  * target,
992  * 10s,
993  * ... );
994  * if(result) {
995  * ... // Some handling of *result.
996  * }
997  * \endcode
998  *
999  * \note
1000  * If Request is a type of a signal then \a args should be an empty
1001  * sequence:
1002  * \code
1003  * struct statistics_request final : public so_5::signal_t {};
1004  * class current_stats final {...};
1005  *
1006  * std::optional<current_stats> stats =
1007  * so_5::extra::sync::request_reply_t<statistics_request, current_stats>::ask_opt_value(target, 10s);
1008  * \endcode
1009  */
1010  template<typename Target, typename Duration, typename... Args>
1011  [[nodiscard]]
1012  static auto
1014  Target && target,
1015  Duration duration,
1016  Args && ...args )
1017  {
1018  auto reply_ch = initiate(
1019  std::forward<Target>(target),
1020  std::forward<Args>(args)... );
1021 
1022  optional<reply_t> result;
1023  receive(
1024  from(reply_ch).handle_n(1).empty_timeout(duration),
1025  [&result]( reply_mhood_t cmd ) {
1026  borrow_from_reply_mhood( cmd, result );
1027  } );
1028 
1029  return result;
1030  }
1031 
1032  /*!
1033  * \brief Send a request and wait for the reply.
1034  *
1035  * This method instantiates request_reply_t object using
1036  * \a args arguments for initializing instance of Request type.
1037  * Then the request_reply_t is sent as a mutable message to
1038  * \a target. Then this method waits the reply for no more
1039  * than \a duration. If there is no reply then an exception
1040  * of type so_5::exception_t is thrown.
1041  *
1042  * Returns an instance of Reply.
1043  *
1044  * Usage example:
1045  * \code
1046  * struct check_user_request final {...};
1047  * struct check_user_result final {...};
1048  * using check_user = so_5::extra::sync::request_reply_t<check_user_request, check_user_result>;
1049  * ...
1050  * check_user_result result = check_user::ask_value(
1051  * target,
1052  * 10s,
1053  * ... );
1054  * \endcode
1055  *
1056  * \note
1057  * If Request is a type of a signal then \a args should be an empty
1058  * sequence:
1059  * \code
1060  * struct statistics_request final : public so_5::signal_t {};
1061  * class current_stats final {...};
1062  *
1063  * std::optional<current_stats> stats =
1064  * so_5::extra::sync::request_reply_t<statistics_request, current_stats>::ask_value(target, 10s);
1065  * \endcode
1066  */
1067  template<typename Target, typename Duration, typename... Args>
1068  [[nodiscard]]
1069  static auto
1071  Target && target,
1072  Duration duration,
1073  Args && ...args )
1074  {
1075  if constexpr( std::is_default_constructible_v<reply_t> )
1076  return ask_default_constructible_value(
1077  std::forward<Target>(target),
1078  duration,
1079  std::forward<Args>(args)... );
1080  else
1081  return ask_not_default_constructible_value(
1082  std::forward<Target>(target),
1083  duration,
1084  std::forward<Args>(args)... );
1085  }
1086  };
1087 
1088 //
1089 // request_mhood_t
1090 //
1091 /*!
1092  * \brief A short form of request_reply_t<Q,A>::request_mhood_t.
1093  *
1094  * Usage example:
1095  * \code
1096  * namespace sync_ns = so_5::extra::sync;
1097  * class service final : public so_5::agent_t {
1098  * ...
1099  * void on_request(sync_ns::request_mhood_t<my_request, my_reply> cmd) {
1100  * ...
1101  * cmd->make_reply(...);
1102  * }
1103  * };
1104  * \endcode
1105  */
1106 template< typename Request, typename Reply >
1107 using request_mhood_t = typename request_reply_t<Request, Reply>::request_mhood_t;
1108 
1109 //
1110 // reply_mhood_t
1111 //
1112 /*!
1113  * \brief A short form of request_reply_t<Q,A>::reply_mhood_t.
1114  */
1115 template< typename Request, typename Reply >
1116 using reply_mhood_t = typename request_reply_t<Request, Reply>::reply_mhood_t;
1117 
1118 //
1119 // request_reply
1120 //
1121 /*!
1122  * \brief A helper function for performing request_reply-iteraction.
1123  *
1124  * Sends a so_5::extra::sync::request_reply_t <Request,Reply> to the specified
1125  * \a target and waits the reply for no more that \a duration. If there is no
1126  * reply then an exception will be thrown.
1127  *
1128  * Usage example:
1129  * \code
1130  * auto r = so_5::extra::sync::request_reply<my_request, my_reply>(
1131  * some_mchain,
1132  * 10s,
1133  * ...);
1134  * \endcode
1135  *
1136  * Returns an instance of Reply object.
1137  */
1138 template<
1139  typename Request, typename Reply,
1140  typename Target, typename Duration, typename... Args>
1141 [[nodiscard]]
1142 auto
1144  Target && target,
1145  Duration duration,
1146  Args && ...args )
1147  {
1148  return request_reply_t<Request, Reply>::ask_value(
1149  std::forward<Target>(target),
1150  duration,
1151  std::forward<Args>(args)... );
1152  }
1153 
1154 //
1155 // request_opt_reply
1156 //
1157 /*!
1158  * \brief A helper function for performing request_reply-iteraction.
1159  *
1160  * Sends a so_5::extra::sync::request_reply_t <Request,Reply> to the specified
1161  * \a target and waits the reply for no more that \a duration. If there is no
1162  * reply then an empty optional object will be returned.
1163  *
1164  * Usage example:
1165  * \code
1166  * auto r = so_5::extra::sync::request_opt_reply<my_request, my_reply>(
1167  * some_mchain,
1168  * 10s,
1169  * ...);
1170  * if(r) {
1171  * ... // Do something with *r.
1172  * }
1173  * \endcode
1174  *
1175  * Returns an instance of std::optional<Reply> object.
1176  */
1177 template<
1178  typename Request, typename Reply,
1179  typename Target, typename Duration, typename... Args>
1180 [[nodiscard]]
1181 auto
1183  Target && target,
1184  Duration duration,
1185  Args && ...args )
1186  {
1187  return request_reply_t<Request, Reply>::ask_opt_value(
1188  std::forward<Target>(target),
1189  duration,
1190  std::forward<Args>(args)... );
1191  }
1192 
1193 } /* namespace sync */
1194 
1195 } /* namespace extra */
1196 
1197 } /* namespace so_5 */
static mchain_t initiate(const Target &target, Args &&...args)
Initiate a request by sending request_reply_t message instance.
Definition: pub.hpp:785
static void initiate_with_custom_reply_to(const Target &target, const agent_t &reply_to, Args &&...args)
Initiate a request by sending request_reply_t message instance with sending the reply to the direct m...
Definition: pub.hpp:873
auto request_reply(Target &&target, Duration duration, Args &&...args)
A helper function for performing request_reply-iteraction.
Definition: pub.hpp:1143
The reply chain shouldn&#39;t be closed even if the corresponding request_reply_t instance is destroyed...
constexpr const close_reply_chain_flag_t close_reply_chain
The indicator that the reply chain should be closed automatically.
Definition: pub.hpp:325
reply_target_holder_t m_reply_target
The target for the reply.
Definition: pub.hpp:208
A special class for performing interactions between agents in request-reply maner.
Definition: pub.hpp:578
bool m_reply_sent
The flag for detection of repeated replies.
Definition: pub.hpp:216
reply_target_holder_t(reply_target_holder_t &&)=delete
static void borrow_from_reply_mhood(reply_mhood_t &cmd, Reply_Receiver &result)
Helper method for getting the result value from reply_mhood with respect to moveability of reply obje...
Definition: pub.hpp:679
static auto ask_default_constructible_value(Target &&target, Duration duration, Args &&...args)
An actual implementation of ask_value for the case when reply object is DefaultConstructible.
Definition: pub.hpp:694
The reply chain should be automatically closed when the corresponding request_reply_t instance is bei...
close_reply_chain_flag_t
A flag to specify should the reply chain be closed automatically.
Definition: pub.hpp:291
constexpr const close_reply_chain_flag_t do_not_close_reply_chain
The indicator that the reply chain shouldn&#39;t be closed automatically.
Definition: pub.hpp:358
const auto & request() const noexcept
Getter for the case of const object.
Definition: pub.hpp:264
reply_target_t m_target
The target for the reply message.
Definition: pub.hpp:121
Ranges for error codes of each submodules.
Definition: details.hpp:13
The basic part of implementation of request_reply type.
Definition: pub.hpp:188
reply_target_holder_t(const reply_target_holder_t &)=delete
void make_reply(Args &&...args)
Make the reply and send it back.
Definition: pub.hpp:956
basic_request_reply_part_t(reply_target_t reply_target) noexcept
Initializing constructor.
Definition: pub.hpp:219
auto request_opt_reply(Target &&target, Duration duration, Args &&...args)
A helper function for performing request_reply-iteraction.
Definition: pub.hpp:1182
mbox_t query_actual_reply_target(const reply_target_t &rt) noexcept
Helper function for extraction of actual reply target from reply_target instance. ...
Definition: pub.hpp:167
static auto ask_opt_value(Target &&target, Duration duration, Args &&...args)
Send a request and wait for the reply.
Definition: pub.hpp:1013
static auto ask_value(Target &&target, Duration duration, Args &&...args)
Send a request and wait for the reply.
Definition: pub.hpp:1070
static auto ask_not_default_constructible_value(Target &&target, Duration duration, Args &&...args)
An actual implementation of request_value for the case when reply object is not DefaultConstructible...
Definition: pub.hpp:730
const int rc_reply_was_sent
An attempt to send a new reply when the reply is already sent.
Definition: pub.hpp:40
request_holder_part_t(reply_target_t reply_target, Args &&...args)
Initializing constructor.
Definition: pub.hpp:254
auto & request() noexcept
Getter for the case of non-const object.
Definition: pub.hpp:268
static void initiate_with_custom_reply_to(const Target &target, const mchain_t &reply_ch, close_reply_chain_flag_t close_flag, Args &&...args)
Initiate a request by sending request_reply_t message instance with sending the reply to the specifie...
Definition: pub.hpp:906
const int rc_no_reply
No reply.
Definition: pub.hpp:48
const auto & target() const noexcept
Getter.
Definition: pub.hpp:153
Base::request_t m_request
An actual request object.
Definition: pub.hpp:250
Helper class to ensure that immutable_msg/mutable_msg modificators are not used.
Definition: pub.hpp:64
static constexpr const bool is_reply_copyable
Is reply copyable type?
Definition: pub.hpp:203
reply_target_holder_t(reply_target_t target) noexcept
Definition: pub.hpp:124
static constexpr const bool is_reply_moveable
Is reply moveable type?
Definition: pub.hpp:198