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