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