SObjectizer-5 Extra
Loading...
Searching...
No Matches
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
26namespace so_5 {
27
28namespace extra {
29
30namespace sync {
31
32namespace 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 */
42
43/*!
44 * \brief No reply.
45 *
46 * The reply has not been received after waiting for the specified time.
47 */
48const int rc_no_reply =
50
51} /* namespace errors */
52
53namespace 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 */
63template< typename T >
65 {
66 using type = T;
67 };
68
69template< 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
77template< 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 */
88template< typename T >
89using ensure_no_mutability_modificators_t =
90 typename ensure_no_mutability_modificators<T>::type;
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 */
118class 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 */
166inline 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 */
187template< 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 */
235template< typename Base, bool is_signal >
236class 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 */
245template< typename Base >
246class 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 */
276template< typename Base >
277class 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
368Some older versions of SObjectizer-5 supported synchronous interactions between
369agents. But since SObjectizer-5.6 this functionality has been removed from
370SObjectizer core. Some form of synchronous interaction is now supported via
371so_5::extra::sync.
372
373The request_reply_t template is the main building block for the synchronous
374interaction between agents in the form of request-reply. The basic usage
375example is looked like the following:
376
377\code
378struct my_request {
379 int a_;
380 std::string b_;
381};
382
383struct my_reply {
384 std::string c_;
385 std::pair<int, int> d_;
386};
387
388namespace sync_ns = so_5::extra::sync;
389
390// The agent that processes requests.
391class 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.
407const 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.
412my_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.
424optional<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
431When a user calls request_reply() or request_opt_reply() helper function an
432instance of request_reply_t class is created and sent to the destination.
433This instance is sent as a mutable message, because of that it should be
434received via mutable_mhood_t. Usage of so_5::extra::sync::request_mhood_t
435template is the most convenient way to receive instances of request_reply_t
436class.
437
438If Request is not a type of a signal then request_reply_t holds an instance
439of that type inside. This instance is accessible via request_reply_t::request()
440getter:
441
442\code
443struct my_request {
444 int a_;
445 std::string b_;
446};
447
448struct my_reply {...};
449
450namespace sync_ns = so_5::extra::sync;
451
452// The agent that processes requests.
453class 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
464If Request is a type of a signal then there is no request_reply_t::request()
465getter.
466
467To make a reply it is necessary to call request_reply_t::make_reply() method:
468
469\code
470void 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
479The make_reply() method can be called only once. An attempt to do the second
480call to make_reply() will lead to raising exception of type
481so_5::exception_t.
482
483Please note that repeating of Request and Reply types, again and again, is not
484a good idea. For example, the following code can be hard to maintain:
485\code
486void some_agent::on_some_request(
487 sync_ns::request_mhood_t<some_request, some_reply> cmd)
488{...}
489
490void some_agent::on_another_request(
491 sync_ns::request_mhood_t<another_request, another_reply> cmd)
492{...}
493
494...
495auto some_result = sync_ns::request_reply<some_request, some_reply>(...);
496...
497auto another_result = sync_ns::request_reply<another_request, another_reply>(...);
498\endcode
499
500It is better to define a separate name for Request and Reply pair and
501use this name:
502\code
503using some_request_reply = sync_ns::request_reply_t<some_request, some_reply>;
504using another_request_reply = sync_ns::request_reply_t<another_request, another_reply>;
505
506void some_agent::on_some_request(
507 typename some_request_reply::request_mhood_t cmd)
508{...}
509
510void some_agent::on_another_request(
511 typename another_request_reply::request_mhood_t cmd)
512{...}
513
514...
515auto some_result = some_request_reply::ask_value(...);
516...
517auto 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
523so_5::immutable_msg can't be used for Request and Reply parameters. It means
524that the following code will lead to compilation errors:
525\code
526// NOTE: so_5::immutable_msg can't be used here!
527auto 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!
530auto 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
535By default the reply message is sent to a separate mchain created inside
536request_value() and request_opt_value() functions (strictly speacking
537this mchain is created inside request_reply_t::initiate() function and
538then used by request_value() and request_opt_value() functions).
539
540But sometimes it can be necessary to have an ability to specify a custom
541destination for the reply message. It can be done via
542request_reply_t::initiate_with_custom_reply_to() methods. For example,
543the following code sends two requests to different targets but all replies
544are going to the same mchain:
545\code
546using first_ask_reply = so_5::extra::sync::request_reply_t<first_request, first_reply>;
547using second_ask_reply = so_5::extra::sync::request_reply_t<second_request, second_reply>;
548
549auto reply_ch = create_mchain(env);
550
551// Sending of requests.
552first_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 ... );
557second_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.
566receive(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
574MoveAssignable+MoveConstructible or CopyAssignable+CopyConstructible.
575
576 */
577template<typename Request, typename Reply>
578class request_reply_t final
579 : public details::request_holder_part_t<
580 details::basic_request_reply_part_t<Request, Reply>,
581 is_signal<Request>::value >
582 {
583 using base_type = details::request_holder_part_t<
584 details::basic_request_reply_part_t<Request, Reply>,
585 is_signal<Request>::value >;
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 */
616 using request_mhood_t = mutable_mhood_t< request_reply_t >;
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 */
645 using reply_mhood_t = mutable_mhood_t< reply_t >;
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 */
665 using holder_t = message_holder_t< mutable_msg<request_reply_t> >;
666
667 private :
668 using base_type::base_type;
669
670 using base_type::is_reply_moveable;
671 using base_type::is_reply_copyable;
672
673 using msg_holder_t = message_holder_t< mutable_msg< request_reply_t > >;
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.
788 auto mchain = so_5::extra::mchains::fixed_size::create_mchain<1>(
789 so_5::send_functions_details::arg_to_env( target ),
790 so_5::mchain_props::overflow_reaction_t::throw_exception );
791
792 msg_holder_t msg{
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 {
837 msg_holder_t msg{
838 // Calling 'new' directly because request_reply_t has
839 // private constructor.
840 new request_reply_t{ reply_to, std::forward<Args>(args)... }
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 {
878 msg_holder_t msg{
879 // Calling 'new' directly because request_reply_t has
880 // private constructor.
881 new request_reply_t{
882 reply_to.so_direct_mbox(), std::forward<Args>(args)... }
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 {
912 msg_holder_t msg;
913
914 switch( close_flag )
915 {
916 case close_reply_chain:
917 msg = msg_holder_t{ new request_reply_t{
918 reply_ch,
919 std::forward<Args>(args)... } };
920 break;
921
922 case do_not_close_reply_chain:
923 msg = msg_holder_t{ new request_reply_t{
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 )
959 SO_5_THROW_EXCEPTION( errors::rc_reply_was_sent,
960 std::string{ "reply has already been sent, "
961 "request_reply type: " } +
962 typeid(request_reply_t).name() );
963
964 so_5::send< so_5::mutable_msg<reply_t> >(
965 details::query_actual_reply_target(
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 */
1106template< typename Request, typename Reply >
1107using 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 */
1115template< typename Request, typename Reply >
1116using 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 */
1138template<
1139 typename Request, typename Reply,
1140 typename Target, typename Duration, typename... Args>
1141[[nodiscard]]
1142auto
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 */
1177template<
1178 typename Request, typename Reply,
1179 typename Target, typename Duration, typename... Args>
1180[[nodiscard]]
1181auto
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 */
The basic part of implementation of request_reply type.
Definition pub.hpp:189
basic_request_reply_part_t(reply_target_t reply_target) noexcept
Initializing constructor.
Definition pub.hpp:219
reply_target_holder_t m_reply_target
The target for the reply.
Definition pub.hpp:211
bool m_reply_sent
The flag for detection of repeated replies.
Definition pub.hpp:216
static constexpr const bool is_reply_copyable
Is reply copyable type?
Definition pub.hpp:203
static constexpr const bool is_reply_moveable
Is reply moveable type?
Definition pub.hpp:198
reply_target_holder_t(reply_target_holder_t &&)=delete
reply_target_holder_t(reply_target_t target) noexcept
Definition pub.hpp:124
const auto & target() const noexcept
Getter.
Definition pub.hpp:153
reply_target_holder_t(const reply_target_holder_t &)=delete
reply_target_t m_target
The target for the reply message.
Definition pub.hpp:121
const auto & request() const noexcept
Getter for the case of const object.
Definition pub.hpp:264
auto & request() noexcept
Getter for the case of non-const object.
Definition pub.hpp:268
Base::request_t m_request
An actual request object.
Definition pub.hpp:250
request_holder_part_t(reply_target_t reply_target, Args &&...args)
Initializing constructor.
Definition pub.hpp:254
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
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
static auto ask_value(Target &&target, Duration duration, Args &&...args)
Send a request and wait for the reply.
Definition pub.hpp:1070
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
static void initiate_with_custom_reply_to(const Target &target, const mbox_t &reply_to, Args &&...args)
Initiate a request by sending request_reply_t message instance with sending the reply to the specifie...
Definition pub.hpp:832
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_opt_value(Target &&target, Duration duration, Args &&...args)
Send a request and wait for the reply.
Definition pub.hpp:1013
void make_reply(Args &&...args)
Make the reply and send it back.
Definition pub.hpp:956
static mchain_t initiate(const Target &target, Args &&...args)
Initiate a request by sending request_reply_t message instance.
Definition pub.hpp:785
const int sync_errors
Starting point for errors of sync submodule.
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
const int rc_no_reply
No reply.
Definition pub.hpp:48
const int rc_reply_was_sent
An attempt to send a new reply when the reply is already sent.
Definition pub.hpp:40
auto request_opt_reply(Target &&target, Duration duration, Args &&...args)
A helper function for performing request_reply-iteraction.
Definition pub.hpp:1182
constexpr const close_reply_chain_flag_t close_reply_chain
The indicator that the reply chain should be closed automatically.
Definition pub.hpp:325
auto request_reply(Target &&target, Duration duration, Args &&...args)
A helper function for performing request_reply-iteraction.
Definition pub.hpp:1143
close_reply_chain_flag_t
A flag to specify should the reply chain be closed automatically.
Definition pub.hpp:292
@ close
The reply chain should be automatically closed when the corresponding request_reply_t instance is bei...
@ do_not_close
The reply chain shouldn't be closed even if the corresponding request_reply_t instance is destroyed....
constexpr const close_reply_chain_flag_t do_not_close_reply_chain
The indicator that the reply chain shouldn't be closed automatically.
Definition pub.hpp:358
Ranges for error codes of each submodules.
Definition details.hpp:13
Helper class to ensure that immutable_msg/mutable_msg modificators are not used.
Definition pub.hpp:65