SObjectizer-5 Extra
time_limited.hpp
Go to the documentation of this file.
1 /*!
2  * \file
3  * \brief Implementation of time-limited asynchronous one-time operation.
4  *
5  * \since
6  * v.1.0.4
7  */
8 
9 #pragma once
10 
11 #include <so_5_extra/async_op/details.hpp>
12 #include <so_5_extra/async_op/errors.hpp>
13 
14 #include <so_5/details/h/invoke_noexcept_code.hpp>
15 
16 #include <so_5/rt/impl/h/internal_env_iface.hpp>
17 
18 #include <so_5/rt/h/agent.hpp>
19 #include <so_5/rt/h/send_functions.hpp>
20 
21 #include <so_5/h/timers.hpp>
22 
23 #include <so_5/h/outliving.hpp>
24 
25 #if defined(SO_5_VERSION)
26  #if (SO_5_VERSION < SO_5_VERSION_MAKE(5, 21, 0))
27  #error "SObjectizer v.5.5.21 or above is required"
28  #endif
29 #else
30  #error "SObjectizer v.5.5.21 or above is required"
31 #endif
32 
33 #include <chrono>
34 #include <vector>
35 
36 namespace so_5 {
37 
38 namespace extra {
39 
40 namespace async_op {
41 
42 namespace time_limited {
43 
44 //! Enumeration for status of operation.
45 enum class status_t
46  {
47  //! Status of operation is unknown because the
48  //! operation data has been moved to another proxy-object.
50  //! Operation is not activated yet.
52  //! Operation is activated.
53  activated,
54  //! Operation is completed.
55  completed,
56  //! Operation is cancelled.
57  cancelled,
58  //! Operation is timed-out.
59  timedout
60  };
61 
62 namespace details {
63 
64 /*!
65  * \brief Container of all data related to async operation.
66  *
67  * \attention
68  * There can be cyclic references from op_data_t instance to
69  * completion/timeout handlers and back from completion/timeout
70  * handlers to op_data_t. Because of that op_data_t can't
71  * perform cleanup in its destructor because the destructor
72  * will not be called until these cyclic references exist.
73  * It requires special attention: content of op_data_t must
74  * be cleared by owners of op_data_t instances.
75  *
76  * \since
77  * v.1.0.4
78  */
79 class op_data_t : protected ::so_5::atomic_refcounted_t
80  {
81  private :
82  friend class ::so_5::intrusive_ptr_t<op_data_t>;
83 
84  //! Description of one completion handler subscription.
86  {
87  //! Mbox from that a message is expected.
89 
90  //! State for that a subscription should be created.
92 
93  //! Subscription type.
94  /*!
95  * \note
96  * This is a subscription type. Not a type which will
97  * be passed to the event handler.
98  */
100 
101  //! Event handler.
103 
104  //! Initializing constructor.
106  so_5::mbox_t mbox,
107  const so_5::state_t & state,
108  std::type_index subscription_type,
109  so_5::event_handler_method_t handler )
110  : m_mbox( std::move(mbox) )
113  , m_handler( std::move(handler) )
114  {}
115  };
116 
117  //! Description of one timeout handler subscription.
119  {
120  //! State for that a subscription should be created.
122 
123  //! Event handler.
125 
126  //! Initializing constructor.
128  const so_5::state_t & state,
129  so_5::event_handler_method_t handler )
131  , m_handler( std::move(handler) )
132  {}
133  };
134 
135  //! Owner of async operation.
137 
138  //! Type of timeout message.
140 
141  //! The status of the async operation.
143 
144  //! Subscriptions for completion handlers which should be created on
145  //! activation.
147 
148  //! Subscriptions for timeout handlers which should be created on
149  //! activation.
151 
152  //! A default timeout handler which will be used as
153  //! deadletter handler for timeout message/signal.
154  /*!
155  * \note
156  * Can be nullptr. If so the default timeout handler will
157  * be created during activation procedure.
158  */
160 
161  //! A mbox to which timeout message/signal will be sent.
162  /*!
163  * \note
164  * It will be limitless MPSC-mbox.
165  */
167 
168  //! An ID of timeout message/signal.
169  /*!
170  * Will be used for cancellation of async operation.
171  */
173 
174  //! Create subscriptions for all defined completion handlers.
175  /*!
176  * This method will rollback all subscriptions made in case of
177  * an exception. It means that if an exception is throw during
178  * subscription then all already subscribed completion handlers
179  * will be removed.
180  */
181  void
183  {
184  std::size_t i = 0;
185  ::so_5::details::do_with_rollback_on_exception( [&] {
186  for(; i != m_completion_handlers.size(); ++i )
187  {
188  auto & ch = m_completion_handlers[ i ];
189  m_owner.get().so_create_event_subscription(
190  ch.m_mbox,
191  ch.m_subscription_type,
192  ch.m_state.get(),
193  ch.m_handler,
194  ::so_5::thread_safety_t::unsafe );
195  }
196  },
197  [&] {
198  drop_completion_handlers_subscriptions_up_to( i );
199  } );
200  }
201 
202  //! Removes subscription for the first N completion handlers.
203  void
205  //! Upper bound in m_completion_handlers (not included).
206  std::size_t upper_border ) noexcept
207  {
208  for( std::size_t i = 0; i != upper_border; ++i )
209  {
210  const auto & ch = m_completion_handlers[ i ];
211  m_owner.get().so_destroy_event_subscription(
212  ch.m_mbox,
213  ch.m_subscription_type,
214  ch.m_state.get() );
215  }
216  }
217 
218  //! Create subscriptions for all defined timeout handlers
219  //! (including default handler).
220  /*!
221  * This method will rollback all subscriptions made in case of
222  * an exception. It means that if an exception is throw during
223  * subscription then all already subscribed timeout handlers
224  * will be removed.
225  */
226  void
228  {
230  ::so_5::details::do_with_rollback_on_exception( [&] {
231  do_subscribe_default_timeout_handler();
232  },
233  [&] {
234  do_unsubscribe_default_timeout_handler();
235  } );
236  }
237 
238  //! An implementation of subscription of timeout handlers.
239  /*!
240  * Default timeout handler is not subscribed by this method.
241  */
242  void
244  {
245  std::size_t i = 0;
246  ::so_5::details::do_with_rollback_on_exception( [&] {
247  for(; i != m_timeout_handlers.size(); ++i )
248  {
249  auto & th = m_timeout_handlers[ i ];
250  m_owner.get().so_create_event_subscription(
251  m_timeout_mbox,
252  m_timeout_msg_type,
253  th.m_state,
254  th.m_handler,
255  ::so_5::thread_safety_t::unsafe );
256  }
257  },
258  [&] {
259  drop_timeout_handlers_subscriptions_up_to( i );
260  } );
261  }
262 
263  //! An implementation of subscription of default timeout handler.
264  void
266  {
267  m_owner.get().so_create_deadletter_subscription(
268  m_timeout_mbox,
269  m_timeout_msg_type,
270  m_default_timeout_handler,
271  ::so_5::thread_safety_t::unsafe );
272  }
273 
274  //! An implementation of unsubscription of the first N timeout handlers.
275  /*!
276  * The default timeout handler is not unsubscribed by this method.
277  */
278  void
280  //! Upper bound in m_timeout_handlers (not included).
281  std::size_t upper_border ) noexcept
282  {
283  for( std::size_t i = 0; i != upper_border; ++i )
284  {
285  const auto & th = m_timeout_handlers[ i ];
286  m_owner.get().so_destroy_event_subscription(
287  m_timeout_mbox,
288  m_timeout_msg_type,
289  th.m_state.get() );
290  }
291  }
292 
293  //! Remove subscriptions for all completion handlers.
294  void
296  {
297  drop_completion_handlers_subscriptions_up_to(
298  m_completion_handlers.size() );
299  }
300 
301  //! Remove subscriptions for all timeout handlers
302  //! (including the default timeout handler).
303  void
305  {
308  }
309 
310  //! Actual unsubscription for the default timeout handler.
311  void
313  {
314  m_owner.get().so_destroy_deadletter_subscription(
315  m_timeout_mbox,
316  m_timeout_msg_type );
317  }
318 
319  //! Actual unsubscription for timeout handlers.
320  /*!
321  * The default timeout handler is not unsubscribed by this method.
322  */
323  void
325  {
326  drop_timeout_handlers_subscriptions_up_to(
327  m_timeout_handlers.size() );
328  }
329 
330  //! Performs total cleanup of the operation data.
331  /*!
332  * All subscriptions are removed.
333  * The delayed message is released.
334  *
335  * Content of m_completion_handlers, m_timeout_handlers and
336  * m_default_timeout_handler will be erased.
337  */
338  void
340  {
341  // All subscriptions must be removed.
344 
345  // Timer is no more needed.
346  m_timeout_timer_id.release();
347 
348  // Information about completion and timeout handlers
349  // is no more needed.
350  m_completion_handlers.clear();
351  m_timeout_handlers.clear();
352  m_default_timeout_handler = ::so_5::event_handler_method_t{};
353  }
354 
355  //! Creates the default timeout handler if it is not set
356  //! by the user.
357  void
359  {
360  if( !m_default_timeout_handler )
361  {
362  m_default_timeout_handler =
363  [op = ::so_5::intrusive_ptr_t<op_data_t>(this)](
364  ::so_5::invocation_type_t /*invoke_type*/,
365  ::so_5::message_ref_t & /*msg*/ )
366  {
367  op->timedout();
368  };
369  }
370  }
371 
372  //! Cleans the operation data and sets the status specified.
373  void
375  //! A new status to be set.
376  status_t status )
377  {
379  m_status = status;
380  }
381 
382  public :
383  //! Initializing constructor.
385  //! Owner of the async operation.
386  ::so_5::outliving_reference_t< ::so_5::agent_t > owner,
387  //! Type of the timeout message/signal.
388  const std::type_index & timeout_msg_type )
389  : m_owner( owner )
391  // Timeout mbox must be created right now.
392  // It will be used during timeout_handlers processing.
393  , m_timeout_mbox(
396  &m_owner.get(),
397  // No message limits for this mbox.
398  nullptr ) )
399  {}
400 
401  //! Access to timeout mbox.
402  SO_5_NODISCARD const ::so_5::mbox_t &
403  timeout_mbox() const noexcept
404  {
405  return m_timeout_mbox;
406  }
407 
408  //! Access to type of timeout message/signal.
409  SO_5_NODISCARD const std::type_index &
410  timeout_msg_type() const noexcept
411  {
412  return m_timeout_msg_type;
413  }
414 
415  //! Access to owner of the async operation.
417  owner() noexcept
418  {
419  return m_owner.get();
420  }
421 
422  //! Access to SObjectizer Environment in that the owner is working.
424  environment() noexcept
425  {
426  return m_owner.get().so_environment();
427  }
428 
429  //! Reserve a space for storage of completion handlers.
430  void
432  //! A required capacity.
433  std::size_t capacity )
434  {
436  }
437 
438  //! Reserve a space for storage of timeout handlers.
439  void
441  //! A required capacity.
442  std::size_t capacity )
443  {
445  }
446 
447  //! Performs activation actions.
448  /*!
449  * The default timeout handler is created if necessary.
450  *
451  * Subscriptions for completion handlers and timeout handler are created.
452  * If an exception is thrown during subscription then all already
453  * subscribed completion and timeout handler will be unsubscribed.
454  */
455  void
457  {
459 
461 
463  [&] {
465  },
466  [&] {
468  } );
469  }
470 
471  //! Change the status of async operation.
472  void
474  //! A new status to be set.
475  status_t status ) noexcept
476  {
477  m_status = status;
478  }
479 
480  //! Get the current status of async operation.
482  current_status() const noexcept
483  {
484  return m_status;
485  }
486 
487  //! Add a new completion handler for the async operation.
488  void
490  //! A mbox from which the completion message/signal is expected.
491  const ::so_5::mbox_t & mbox,
492  //! A state for completion handler.
493  const ::so_5::state_t & state,
494  //! A type of the completion message/signal.
495  const std::type_index msg_type,
496  //! Completion handler itself.
498  {
500  mbox,
501  state,
502  msg_type,
503  std::move(evt_handler) );
504  }
505 
506  //! Add a new timeout handler for the async operation.
507  void
509  //! A state for timeout handler.
510  const ::so_5::state_t & state,
511  //! Timeout handler itself.
513  {
515  state,
516  std::move(evt_handler) );
517  }
518 
519  //! Set the default timeout handler.
520  /*!
521  * \note
522  * If there already is the default timeout handler then the old
523  * one will be replaced by new handler.
524  */
525  void
527  //! Timeout handler itself.
529  {
531  }
532 
533  //! Set the ID of timeout message/signal timer.
534  void
536  //! Timer ID of delayed timeout message/signal.
537  ::so_5::timer_id_t id )
538  {
540  }
541 
542  //! Mark the async operation as completed.
543  void
544  completed() noexcept
545  {
547  }
548 
549  //! Mark the async operation as timedout.
550  void
551  timedout() noexcept
552  {
554  }
555 
556  //! Mark the async operation as cancelled.
557  void
558  cancelled() noexcept
559  {
561  }
562  };
563 
564 /*!
565  * \brief An alias for smart pointer to operation data.
566  *
567  * \tparam Operation_Data Type of actual operation data representation.
568  * It is expected to be op_data_t or some of its derivatives.
569  */
570 template< typename Operation_Data >
572 
573 } /* namespace details */
574 
575 /*!
576  * \brief An object that allows to cancel async operation.
577  *
578  * Usage example:
579  * \code
580  * namespace asyncop = so_5::extra::async_op::time_limited;
581  * class demo : public so_5::agent_t {
582  * asyncop::cancellation_point_t<> cp_;
583  * ...
584  * void initiate_async_op() {
585  * auto op = asyncop::make<timeout>(*this);
586  * op.completed_on(...);
587  * op.timeout_handler(...);
588  * cp_ = op.activate(std::chrono::milliseconds(300), ...);
589  * }
590  * void on_operation_aborted(mhood_t<op_aborted_notify> cmd) {
591  * // Operation aborted. There is no need to wait for
592  * // completion of operation.
593  * cp_.cancel();
594  * }
595  * };
596  * \endcode
597  *
598  * \note
599  * This class is DefaultConstructible and Moveable, but not Copyable.
600  *
601  * \attention
602  * Objects of this class are not thread safe. It means that cancellation
603  * point should be used only by agent which created it. And the cancellation
604  * point can't be used inside thread-safe event handlers of that agent.
605  *
606  * \tparam Operation_Data Type of actual operation data representation.
607  * Please note that this template parameter is indended to be used for
608  * debugging and testing purposes only.
609  *
610  * \since
611  * v.1.0.4
612  */
613 template< typename Operation_Data = details::op_data_t >
615  {
616  private :
617  template<typename Msg, typename Op_Data> friend class definition_point_t;
618 
619  //! Actual data for async op.
620  /*!
621  * \note
622  * This can be a nullptr if the default constructor was used,
623  * or if operation is already cancelled, or if the content of
624  * the cancellation_point was moved to another object.
625  */
627 
628  //! Initializing constructor to be used by definition_point.
630  //! Actual data for async operation.
631  //! Can't be null.
632  details::op_shptr_t< Operation_Data > op )
633  : m_op( std::move(op) )
634  {}
635 
636  public :
637  cancellation_point_t() = default;
638 
639  cancellation_point_t( const cancellation_point_t & ) = delete;
641 
643  operator=( const cancellation_point_t & ) = delete;
644 
646  operator=( cancellation_point_t && ) = default;
647 
648  //! Get the status of the operation.
649  /*!
650  * \note
651  * The value status_t::unknown_moved_away can be returned if
652  * the actual data of the async operation was moved to another object
653  * (like another cancellation_point_t). Or after a call to
654  * cleanup() method.
655  */
656  SO_5_NODISCARD status_t
657  status() const noexcept
658  {
659  if( m_op)
660  return m_op->current_status();
662  }
663 
664  //! Can the async operation be cancelled via this cancellation point?
665  /*!
666  * \return true if the cancellation_point holds actual async operation's
667  * data and this async operation is not completed nor timedout yet.
668  */
669  SO_5_NODISCARD bool
670  is_cancellable() const noexcept
671  {
672  return m_op && status_t::activated == m_op->current_status();
673  }
674 
675  //! An attempt to cancel the async operation.
676  /*!
677  * \note
678  * Operation will be cancelled only if (true == is_cancellable()).
679  *
680  * It is safe to call cancel() if the operation is already
681  * cancelled or completed, or timed out. In that case the call to
682  * cancel() will have no effect.
683  */
684  void
685  cancel() noexcept
686  {
687  if( is_cancellable() )
688  {
689  m_op->cancelled();
690  }
691  }
692 
693  //! Throw out a reference to the async operation data.
694  /*!
695  * A cancellation_point holds a reference to the async operation
696  * data. It means that the async operation data will be destroyed
697  * only when the cancellation_point will be destroyed. For example,
698  * in that case:
699  * \code
700  * namespace asyncop = so_5::extra::async_op::time_limited;
701  * class demo : public so_5::agent_t {
702  * ...
703  * asyncop::cancellation_point_t<> cp_;
704  * ...
705  * void initiate_async_op() {
706  * cp_ = asyncop::make<timeout_msg>(*this)
707  * ...
708  * .activate(...);
709  * ...
710  * }
711  * ...
712  * void on_some_interruption() {
713  * // Cancel asyncop.
714  * cp_.cancel();
715  * // Async operation is cancelled, but the async operation
716  * // data is still in memory. The data will be deallocated
717  * // only when cp_ receives new value or when cp_ will be
718  * // destroyed (e.g. after destruction of demo agent).
719  * }
720  * \endcode
721  *
722  * A call to cleanup() method removes the reference to the async
723  * operation data. It means that if the operation is already
724  * completed, timed out or cancelled, then the operation data
725  * fill be deallocated.
726  *
727  * \note
728  * If the operation is still in progress then a call to cleanup()
729  * doesn't break the operation. You need to call cancel() manually
730  * before calling cleanup() to cancel the operation.
731  */
732  void
733  cleanup() noexcept
734  {
735  m_op.reset();
736  }
737  };
738 
739 namespace details
740 {
741 
742 /*!
743  * \brief A basic part of implementation of definition_point.
744  *
745  * This part is independent from timeout message/signal type.
746  *
747  * \note
748  * This is Moveable class. But not DefaultConstructible.
749  * And not Copyable.
750  *
751  * \note
752  * Most of methods are declared as protected to be used only in
753  * derived class.
754  *
755  * \since
756  * v.1.0.4
757  */
758 template< typename Operation_Data >
760  {
761  protected :
762  //! Actual async operation data.
763  /*!
764  * \note This pointer can be nullptr after activation or
765  * when the object is move away.
766  */
768 
769  //! Initializing constructor.
771  //! Actual async operation data.
772  //! This pointer can't be nullptr.
773  details::op_shptr_t< Operation_Data > op )
774  : m_op( std::move(op) )
775  {}
776 
781 
786 
787  //! Can the async_op be activated?
788  /*!
789  * The async operation can't be activated if it is already activated.
790  * Or if the content of the definition_point is moved to another
791  * definition_point.
792  */
793  SO_5_NODISCARD bool
794  is_activable() const noexcept
795  {
796  return m_op;
797  }
798 
799  //! Throws an exception if the async operation can't be activated.
800  void
802  {
803  if( !is_activable() )
806  "An attempt to do something on non-activable async_op" );
807  }
808 
809  //! Reserve a space for storage of completion handlers.
810  void
812  //! A required capacity.
813  std::size_t capacity )
814  {
816 
818  }
819 
820  //! Reserve a space for storage of timeout handlers.
821  void
823  //! A required capacity.
824  std::size_t capacity )
825  {
827 
829  }
830 
831  //! The actual implementation of addition of completion handler.
832  /*!
833  * \tparam Msg_Target It can be a mbox, or a reference to an agent.
834  * In the case if Msg_Target if a reference to an agent the agent's
835  * direct mbox will be used as message source.
836  *
837  * \tparam Event_Handler Type of actual handler for message/signal.
838  * It can be a pointer to agent's method or lambda (or another
839  * type of functional object).
840  */
841  template<
842  typename Msg_Target,
843  typename Event_Handler >
844  void
846  //! A source from which a completion message/signal is expected.
847  //! It can be a mbox or an agent (in that case the agent's direct
848  //! mbox will be used).
850  //! A state for that a completion handler is defined.
851  const ::so_5::state_t & state,
852  //! Completion handler itself.
854  {
856 
857  const auto mbox = ::so_5::extra::async_op::details::target_to_mbox(
858  msg_target );
859 
860  auto evt_handler_info =
862  mbox,
863  m_op->owner(),
865 
867  [op = m_op,
870  ::so_5::message_ref_t & msg )
871  {
872  op->completed();
874  };
875 
877  mbox,
878  state,
880  std::move(actual_handler) );
881  }
882 
883  //! The actual implementation of addition of timeout handler.
884  /*!
885  * \tparam Event_Handler Type of actual handler for message/signal.
886  * It can be a pointer to agent's method or lambda (or another
887  * type of functional object).
888  */
889  template< typename Event_Handler >
890  void
892  //! A state for that a timeout handler is defined.
893  const ::so_5::state_t & state,
894  //! Timeout handler itself.
896  {
898 
900  state,
903  }
904 
905  //! The actual implementation of addition of the default timeout handler.
906  /*!
907  * \tparam Event_Handler Type of actual handler for message/signal.
908  * It can be a pointer to agent's method or lambda (or another
909  * type of functional object).
910  */
911  template< typename Event_Handler >
912  void
914  //! The default timeout handler itself.
916  {
918 
922  }
923 
924  private :
925  //! A helper method for creation a wrapper for a timeout handler.
926  template< typename Event_Handler >
929  //! Timeout handler to be wrapped.
931  {
932  auto evt_handler_info =
934  m_op->timeout_mbox(),
935  m_op->owner(),
940  std::string(
941  "An attempt to register timeout handler for "
942  "different message type. Expected type: " )
943  + m_op->timeout_msg_type().name()
944  + ", actual type: " + evt_handler_info.m_msg_type.name() );
945 
946  return
947  [op = m_op,
950  ::so_5::message_ref_t & msg )
951  {
952  op->timedout();
954  };
955  }
956  };
957 
958 } /* namespace details */
959 
960 /*!
961  * \brief An interface for definition of async operation.
962  *
963  * Object of this type is usually created by make() function and
964  * is used for definition of async operation. Completion and timeout
965  * handlers are set for async operation by using definition_point object.
966  *
967  * Then an user calls activate() method and the definition_point transfers
968  * the async operation data into cancellation_point object. It means that
969  * after a call to activate() method the definition_point object should
970  * not be used. Because it doesn't hold any async operation anymore.
971  *
972  * A simple usage without storing the cancellation_point:
973  * \code
974  * namespace asyncop = so_5::extra::async_op::time_limited;
975  * class demo : public so_5::agent_t {
976  * struct timeout final : public so_5::signal_t {};
977  * ...
978  * void initiate_async_op() {
979  * // Create a definition_point for a new async operation...
980  * asyncop::make<timeout>(*this)
981  * // ...then set up completion handler(s)...
982  * .completed_on(
983  * *this,
984  * so_default_state(),
985  * &demo::on_first_completion_msg )
986  * .completed_on(
987  * some_external_mbox_,
988  * some_user_defined_state_,
989  * [this](mhood_t<another_completion_msg> cmd) {...})
990  * // ...then set up timeout handler(s)...
991  * .timeout_handler(
992  * so_default_state(),
993  * &demo::on_timeout )
994  * .timeout_handler(
995  * some_user_defined_state_,
996  * [this](mhood_t<timeout> cmd) {...})
997  * // ...and now we can activate the operation.
998  * .activate(std::chrono::milliseconds(300));
999  * ...
1000  * }
1001  * };
1002  * \endcode
1003  * \note
1004  * There is no need to hold definition_point object after activation
1005  * of the async operation. This object can be safely discarded.
1006  *
1007  * A more complex example using cancellation_point for
1008  * cancelling the operation.
1009  * \code
1010  * namespace asyncop = so_5::extra::async_op::time_limited;
1011  * class demo : public so_5::agent_t {
1012  * struct timeout final : public so_5::signal_t {};
1013  * ...
1014  * // Cancellation point for the async operation.
1015  * asyncop::cancellation_point_t<> cp_;
1016  * ...
1017  * void initiate_async_op() {
1018  * // Create a definition_point for a new async operation
1019  * // and store the cancellation point after activation.
1020  * cp_ = asyncop::make<timeout>(*this)
1021  * // ...then set up completion handler(s)...
1022  * .completed_on(
1023  * *this,
1024  * so_default_state(),
1025  * &demo::on_first_completion_msg )
1026  * .completed_on(
1027  * some_external_mbox_,
1028  * some_user_defined_state_,
1029  * [this](mhood_t<another_completion_msg> cmd) {...})
1030  * // ...then set up timeout handler(s)...
1031  * .timeout_handler(
1032  * so_default_state(),
1033  * &demo::on_timeout )
1034  * .timeout_handler(
1035  * some_user_defined_state_,
1036  * [this](mhood_t<timeout> cmd) {...})
1037  * // ...and now we can activate the operation.
1038  * .activate(std::chrono::milliseconds(300));
1039  * ...
1040  * }
1041  * ...
1042  * void on_abortion_signal(mhood_t<abort_signal>) {
1043  * // The current async operation should be cancelled.
1044  * cp_.cancel();
1045  * }
1046  * };
1047  * \endcode
1048  *
1049  * \note
1050  * This class is Moveable, but not DefaultConstructible nor Copyable.
1051  *
1052  * \attention
1053  * Objects of this class are not thread safe. It means that a definition
1054  * point should be used only by agent which created it. And the definition
1055  * point can't be used inside thread-safe event handlers of that agent.
1056  *
1057  * \since
1058  * v.1.0.4
1059  */
1060 template<
1061  typename Message,
1062  typename Operation_Data = details::op_data_t >
1064  : protected details::msg_independent_part_of_definition_point_t< Operation_Data >
1065  {
1066  //! A short alias for base type.
1067  using base_type_t =
1069 
1070  public :
1071  //! Initializing constructor.
1073  //! The owner of the async operation.
1074  ::so_5::outliving_reference_t< ::so_5::agent_t > owner )
1075  : base_type_t(
1077  new Operation_Data( owner, typeid(Message) ) ) )
1078  {}
1079 
1081  {
1082  // If operation data is still here then it means that
1083  // there wasn't call to `activate()` and we should cancel
1084  // all described handlers.
1085  // This will lead to deallocation of operation data.
1086  if( this->m_op )
1087  this->m_op->cancelled();
1088  }
1089 
1090  definition_point_t( const definition_point_t & ) = delete;
1092  operator=( const definition_point_t & ) = delete;
1093 
1094  definition_point_t( definition_point_t && ) = default;
1096  operator=( definition_point_t && ) = default;
1097 
1098  using base_type_t::is_activable;
1099 
1100  /*!
1101  * \brief Reserve a space for storage of completion handlers.
1102  *
1103  * Usage example:
1104  * \code
1105  * namespace asyncop = so_5::extra::async_op::time_limited;
1106  * auto op = asyncop::make<timeout>(some_agent);
1107  * // Reserve space for four completion handlers.
1108  * op.reserve_completion_handlers_capacity(4);
1109  * op.completed_on(...);
1110  * op.completed_on(...);
1111  * op.completed_on(...);
1112  * op.completed_on(...);
1113  * op.activate(...);
1114  * \endcode
1115  */
1118  //! A required capacity.
1119  std::size_t capacity ) &
1120  {
1122  return *this;
1123  }
1124 
1125  //! Just a proxy for actual version of %reserve_completion_handlers_capacity.
1126  template< typename... Args >
1127  auto
1129  {
1130  return std::move(this->reserve_completion_handlers_capacity(
1131  std::forward<Args>(args)... ));
1132  }
1133 
1134  /*!
1135  * \brief Reserve a space for storage of timeout handlers.
1136  *
1137  * Usage example:
1138  * \code
1139  * namespace asyncop = so_5::extra::async_op::time_limited;
1140  * auto op = asyncop::make<timeout>(some_agent);
1141  * // Reserve space for eight timeout handlers.
1142  * op.reserve_timeout_handlers_capacity(8);
1143  * op.timeout_handler(...);
1144  * op.timeout_handler(...);
1145  * ...
1146  * op.activate(...);
1147  * \endcode
1148  */
1151  //! A required capacity.
1152  std::size_t capacity ) &
1153  {
1155  return *this;
1156  }
1157 
1158  //! Just a proxy for actual version of %reserve_timeout_handlers_capacity.
1159  template< typename... Args >
1160  auto
1162  {
1163  return std::move(this->reserve_timeout_handlers_capacity(
1164  std::forward<Args>(args)... ));
1165  }
1166 
1167  /*!
1168  * \brief Add a completion handler for the async operation.
1169  *
1170  * Usage example:
1171  * \code
1172  * namespace asyncop = so_5::extra::async_op::time_limited;
1173  * class demo : public so_5::agent_t {
1174  * ...
1175  * void initiate_async_op() {
1176  * asyncop::make<timeout>(*this)
1177  * .completed_on(
1178  * *this,
1179  * so_default_state(),
1180  * &demo::some_event )
1181  * .completed_on(
1182  * some_mbox_,
1183  * some_agent_state_,
1184  * [this](mhood_t<some_msg> cmd) {...})
1185  * ...
1186  * .activate(...);
1187  * }
1188  * };
1189  * \endcode
1190  *
1191  * \note
1192  * The completion handler will be stored inside async operation
1193  * data. Actual subscription for it will be made during activation
1194  * of the async operation.
1195  *
1196  * \tparam Msg_Target It can be a mbox, or a reference to an agent.
1197  * In the case if Msg_Target if a reference to an agent the agent's
1198  * direct mbox will be used as message source.
1199  *
1200  * \tparam Event_Handler Type of actual handler for message/signal.
1201  * It can be a pointer to agent's method or lambda (or another
1202  * type of functional object).
1203  */
1204  template<
1205  typename Msg_Target,
1206  typename Event_Handler >
1209  //! A source from which a completion message is expected.
1210  //! It \a msg_target is a reference to an agent then
1211  //! the agent's direct mbox will be used.
1212  Msg_Target && msg_target,
1213  //! A state for which that completion handler will be subscribed.
1214  const ::so_5::state_t & state,
1215  //! The completion handler itself.
1216  Event_Handler && evt_handler ) &
1217  {
1218  this->completed_on_impl(
1220  state,
1222 
1223  return *this;
1224  }
1225 
1226  //! Just a proxy for the main version of %completed_on.
1227  template< typename... Args >
1229  completed_on( Args && ...args ) &&
1230  {
1231  return std::move(this->completed_on(std::forward<Args>(args)...));
1232  }
1233 
1234  /*!
1235  * \brief Add a timeout handler for the async operation.
1236  *
1237  * Usage example:
1238  * \code
1239  * namespace asyncop = so_5::extra::async_op::time_limited;
1240  * class demo : public so_5::agent_t {
1241  * ...
1242  * void initiate_async_op() {
1243  * asyncop::make<timeout>(*this)
1244  * .timeout_handler(
1245  * so_default_state(),
1246  * &demo::some_event )
1247  * .timeout_handler(
1248  * some_agent_state_,
1249  * [this](mhood_t<timeout> cmd) {...})
1250  * ...
1251  * .activate(...);
1252  * }
1253  * };
1254  * \endcode
1255  *
1256  * \note
1257  * The timeout handler will be stored inside async operation
1258  * data. Actual subscription for it will be made during activation
1259  * of the async operation.
1260  *
1261  * \tparam Event_Handler Type of actual handler for message/signal.
1262  * It can be a pointer to agent's method or lambda (or another
1263  * type of functional object).
1264  * Please notice that Event_Handler must receive a message/signal
1265  * of type \a Message. It means that Event_Handler can have one of the
1266  * following formats:
1267  * \code
1268  * ret_type handler(Message);
1269  * ret_type handler(const Message &);
1270  * ret_type handler(mhood_t<Message>); // This is the recommended format.
1271  * \endcode
1272  */
1273  template< typename Event_Handler >
1276  const ::so_5::state_t & state,
1277  Event_Handler && evt_handler ) &
1278  {
1279  this->timeout_handler_impl(
1280  state,
1282 
1283  return *this;
1284  }
1285 
1286  //! Just a proxy for the main version of %timeout_handler.
1287  template< typename... Args >
1289  timeout_handler( Args && ...args ) &&
1290  {
1291  return std::move(this->timeout_handler(
1292  std::forward<Args>(args)...));
1293  }
1294 
1295  /*!
1296  * \brief Add the default timeout handler for the async operation.
1297  *
1298  * The default timeout handler will be called if no timeout
1299  * handler was called for timeout message/signal.
1300  * A deadletter handlers from SObjectizer-5.5.21 are used for
1301  * implementation of the default timeout handler for async
1302  * operation.
1303  *
1304  * There can be only one the default timeout handler.
1305  * If the default timeout handler is already specified a new call
1306  * to default_timeout_handler() will replace the old handler with
1307  * new one (the old default timeout handler will be lost).
1308  *
1309  * Usage example:
1310  * \code
1311  * namespace asyncop = so_5::extra::async_op::time_limited;
1312  * class demo : public so_5::agent_t {
1313  * ...
1314  * void initiate_async_op() {
1315  * asyncop::make<timeout>(*this)
1316  * .default_timeout_handler(
1317  * &demo::some_deadletter_handler )
1318  * ...
1319  * .activate(...);
1320  * }
1321  * };
1322  * \endcode
1323  *
1324  * \note
1325  * The default timeout handler will be stored inside async operation
1326  * data. Actual subscription for it will be made during activation
1327  * of the async operation.
1328  *
1329  * \tparam Event_Handler Type of actual handler for message/signal.
1330  * It can be a pointer to agent's method or lambda (or another
1331  * type of functional object).
1332  * Please notice that Event_Handler must receive a message/signal
1333  * of type \a Message. It means that Event_Handler can have one of the
1334  * following formats:
1335  * \code
1336  * ret_type handler(Message);
1337  * ret_type handler(const Message &);
1338  * ret_type handler(mhood_t<Message>); // This is the recommended format.
1339  * \endcode
1340  */
1341  template< typename Event_Handler >
1344  //! The default timeout handler itself.
1345  Event_Handler && evt_handler ) &
1346  {
1349 
1350  return *this;
1351  }
1352 
1353  //! Just a proxy for the main version of %default_timeout_handler.
1354  template< typename... Args >
1356  default_timeout_handler( Args && ...args ) &&
1357  {
1358  return std::move(this->timeout_handler(
1359  std::forward<Args>(args)...));
1360  }
1361 
1362  /*!
1363  * \brief Activation of the async operation.
1364  *
1365  * All subscriptions for completion and timeout handlers will be made
1366  * here. Then the timeout message/signal will be sent. And then
1367  * a cancellation_point for that async operation will be returned.
1368  *
1369  * An example of activation of the async operation in the case
1370  * when \a Message is a signal:
1371  * \code
1372  * namespace asyncop = so_5::extra::async_op::time_limited;
1373  * class demo : public so_5::agent_t {
1374  * struct timeout final : public so_5::signal_t {};
1375  * ...
1376  * void initiate_async_op() {
1377  * asyncop::make<timeout>()
1378  * ... // Set up completion and timeout handlers...
1379  * // Activation with just one argument - timeout duration.
1380  * .activate(std::chrono::milliseconds(200));
1381  * ...
1382  * }
1383  * };
1384  * \endcode
1385  *
1386  * An example of activation of the async operation in the case
1387  * when \a Message is a message:
1388  * \code
1389  * namespace asyncop = so_5::extra::async_op::time_limited;
1390  * class demo : public so_5::agent_t {
1391  * struct timeout final : public so_5::message_t {
1392  * int id_;
1393  * const std::string reply_payload_;
1394  * const so_5::mbox_t reply_mbox_;
1395  *
1396  * timeout(int id, std::string playload, so_5::mbox_t mbox)
1397  * : id_(id), reply_payload_(std::move(payload), reply_mbox_(std::move(mbox))
1398  * {}
1399  * };
1400  * ...
1401  * void initiate_async_op() {
1402  * asyncop::make<timeout>()
1403  * ... // Set up completion and timeout handlers...
1404  * // Activation with several arguments.
1405  * .activate(
1406  * // This is timeout duration.
1407  * std::chrono::milliseconds(200),
1408  * // All these arguments will be passed to the constructor of timeout message.
1409  * current_op_id,
1410  * op_reply_payload,
1411  * op_result_consumer_mbox);
1412  * ...
1413  * }
1414  * };
1415  * \endcode
1416  *
1417  * \note
1418  * There is no need to store the cancellation_point returned if you
1419  * don't want to cancel async operation. The return value of
1420  * activate() can be safely discarded in that case.
1421  *
1422  * \attention
1423  * If cancellation_point is stored then it should be used only on
1424  * the working context of the agent for that the async operation is
1425  * created. And the cancellation_point should not be used in the
1426  * thread-safe event handlers of that agent. It is because
1427  * cancellation_point is not a thread safe object.
1428  *
1429  * \note
1430  * If an exception is thrown during the activation procedure all
1431  * completion and timeout handlers which were subscribed before
1432  * the exception will be unsubscribed. And the async operation
1433  * data will be deleted. It means that after an exception in
1434  * activate() method the definition_point can't be used anymore.
1435  */
1436  template< typename... Args >
1437  cancellation_point_t< Operation_Data >
1439  //! The maximum duration for the async operation.
1440  //! Note: this should be non-negative value.
1441  std::chrono::steady_clock::duration timeout,
1442  //! Arguments for the construction of timeout message/signal
1443  //! of the type \a Message.
1444  //! This arguments will be passed to so_5::send_delayed
1445  //! function.
1446  Args && ...args ) &
1447  {
1448  this->ensure_activable();
1449 
1450  // From this point the definition_point will be detached
1451  // from operation data. It means that is_activable() will
1452  // return false.
1453  auto op = std::move(this->m_op);
1454 
1457  op->setup_timer_id(
1459  op->environment(),
1460  op->timeout_mbox(),
1461  timeout,
1463  std::forward<Args>(args)... ) );
1465  },
1466  [&] {
1467  op->cancelled();
1468  } );
1469 
1471  }
1472 
1473  //! Just a proxy for the main version of %activate.
1474  template< typename... Args >
1475  auto
1476  activate( Args && ...args ) &&
1477  {
1478  return this->activate( std::forward<Args>(args)... );
1479  }
1480  };
1481 
1482 //
1483 // make
1484 //
1485 /*!
1486  * \brief A factory for creation definition points of async operations.
1487  *
1488  * Instead of creation of definition_point_t object by hand this
1489  * helper function should be used.
1490  *
1491  * Usage examples:
1492  * \code
1493  * namespace asyncop = so_5::extra::async_op::time_limited;
1494  * class demo : public so_5::agent_t {
1495  * struct timeout final : public so_5::signal_t {};
1496  * ...
1497  * void initiate_async_op() {
1498  * // Create a definition_point for a new async operation...
1499  * asyncop::make<timeout>(*this)
1500  * // ...then set up completion handler(s)...
1501  * .completed_on(
1502  * *this,
1503  * so_default_state(),
1504  * &demo::on_first_completion_msg )
1505  * .completed_on(
1506  * some_external_mbox_,
1507  * some_user_defined_state_,
1508  * [this](mhood_t<another_completion_msg> cmd) {...})
1509  * // ...then set up timeout handler(s)...
1510  * .timeout_handler(
1511  * so_default_state(),
1512  * &demo::on_timeout )
1513  * .timeout_handler(
1514  * some_user_defined_state_,
1515  * [this](mhood_t<timeout> cmd) {...})
1516  * // ...and now we can activate the operation.
1517  * .activate(std::chrono::milliseconds(300));
1518  * ...
1519  * }
1520  * };
1521  * \endcode
1522  *
1523  * \since
1524  * v.1.0.4
1525  */
1526 template< typename Message >
1529  //! Agent for that this async operation will be created.
1530  ::so_5::agent_t & owner )
1531  {
1532  return definition_point_t<Message>( ::so_5::outliving_mutable(owner) );
1533  }
1534 
1535 } /* namespace time_limited */
1536 
1537 } /* namespace async_op */
1538 
1539 } /* namespace extra */
1540 
1541 } /* namespace so_5 */
void do_subscribe_timeout_handlers()
An implementation of subscription of timeout handlers.
cancellation_point_t< Operation_Data > activate(std::chrono::steady_clock::duration timeout, Args &&...args) &
Activation of the async operation.
definition_point_t & reserve_completion_handlers_capacity(std::size_t capacity) &
Reserve a space for storage of completion handlers.
void drop_all_completion_handlers_subscriptions() noexcept
Remove subscriptions for all completion handlers.
::so_5::timer_id_t m_timeout_timer_id
An ID of timeout message/signal.
void create_timeout_handlers_subscriptions()
Create subscriptions for all defined timeout handlers (including default handler).
msg_independent_part_of_definition_point_t & operator=(const msg_independent_part_of_definition_point_t &)=delete
completion_handler_subscription_t(so_5::mbox_t mbox, const so_5::state_t &state, std::type_index subscription_type, so_5::event_handler_method_t handler)
Initializing constructor.
auto activate(Args &&...args) &&
Just a proxy for the main version of activate.
timeout_handler_subscription_t(const so_5::state_t &state, so_5::event_handler_method_t handler)
Initializing constructor.
op_data_t(::so_5::outliving_reference_t< ::so_5::agent_t > owner, const std::type_index &timeout_msg_type)
Initializing constructor.
void make_default_timeout_handler_if_necessary()
Creates the default timeout handler if it is not set by the user.
void cleanup() noexcept
Throw out a reference to the async operation data.
::so_5::outliving_reference_t< const ::so_5::state_t > m_state
State for that a subscription should be created.
details::op_shptr_t< Operation_Data > m_op
Actual data for async op.
void drop_all_timeout_handlers_subscriptions() noexcept
Remove subscriptions for all timeout handlers (including the default timeout handler).
cancellation_point_t & operator=(const cancellation_point_t &)=delete
SO_5_NODISCARD ::so_5::event_handler_method_t create_actual_timeout_handler(Event_Handler &&evt_handler)
A helper method for creation a wrapper for a timeout handler.
definition_point_t & operator=(const definition_point_t &)=delete
Ranges for error codes of each submodules.
Definition: details.hpp:14
status_t
Enumeration for status of operation.
std::vector< completion_handler_subscription_t > m_completion_handlers
Subscriptions for completion handlers which should be created on activation.
void cancelled() noexcept
Mark the async operation as cancelled.
definition_point_t & reserve_timeout_handlers_capacity(std::size_t capacity) &
Reserve a space for storage of timeout handlers.
cancellation_point_t(details::op_shptr_t< Operation_Data > op)
Initializing constructor to be used by definition_point.
const ::so_5::mbox_t m_timeout_mbox
A mbox to which timeout message/signal will be sent.
void do_unsubscribe_default_timeout_handler() noexcept
Actual unsubscription for the default timeout handler.
void do_subscribe_default_timeout_handler()
An implementation of subscription of default timeout handler.
definition_point_t && default_timeout_handler(Args &&...args) &&
Just a proxy for the main version of default_timeout_handler.
definition_point_t && completed_on(Args &&...args) &&
Just a proxy for the main version of completed_on.
msg_independent_part_of_definition_point_t(const msg_independent_part_of_definition_point_t &)=delete
definition_point_t(const definition_point_t &)=delete
definition_point_t & timeout_handler(const ::so_5::state_t &state, Event_Handler &&evt_handler) &
Add a timeout handler for the async operation.
definition_point_t & operator=(definition_point_t &&)=default
auto reserve_completion_handlers_capacity(Args &&...args) &&
Just a proxy for actual version of reserve_completion_handlers_capacity.
cancellation_point_t(cancellation_point_t &&)=default
definition_point_t & default_timeout_handler(Event_Handler &&evt_handler) &
Add the default timeout handler for the async operation.
Status of operation is unknown because the operation data has been moved to another proxy-object...
std::vector< timeout_handler_subscription_t > m_timeout_handlers
Subscriptions for timeout handlers which should be created on activation.
auto reserve_timeout_handlers_capacity(Args &&...args) &&
Just a proxy for actual version of reserve_timeout_handlers_capacity.
::so_5::event_handler_method_t m_default_timeout_handler
A default timeout handler which will be used as deadletter handler for timeout message/signal.
cancellation_point_t(const cancellation_point_t &)=delete
definition_point_t && timeout_handler(Args &&...args) &&
Just a proxy for the main version of timeout_handler.
const std::type_index m_timeout_msg_type
Type of timeout message.
msg_independent_part_of_definition_point_t & operator=(msg_independent_part_of_definition_point_t &&)=default
SO_5_NODISCARD definition_point_t< Message > make(::so_5::agent_t &owner)
A factory for creation definition points of async operations.
status_t m_status
The status of the async operation.
void drop_completion_handlers_subscriptions_up_to(std::size_t upper_border) noexcept
Removes subscription for the first N completion handlers.
void create_completion_handlers_subscriptions()
Create subscriptions for all defined completion handlers.
definition_point_t(::so_5::outliving_reference_t< ::so_5::agent_t > owner)
Initializing constructor.
definition_point_t & completed_on(Msg_Target &&msg_target, const ::so_5::state_t &state, Event_Handler &&evt_handler) &
Add a completion handler for the async operation.
msg_independent_part_of_definition_point_t(details::op_shptr_t< Operation_Data > op)
Initializing constructor.
::so_5::outliving_reference_t< const ::so_5::state_t > m_state
State for that a subscription should be created.
msg_independent_part_of_definition_point_t(msg_independent_part_of_definition_point_t &&)=default
cancellation_point_t & operator=(cancellation_point_t &&)=default
void clean_with_status(status_t status)
Cleans the operation data and sets the status specified.
details::op_shptr_t< Operation_Data > m_op
Actual async operation data.
void do_cancellation_actions() noexcept
Performs total cleanup of the operation data.
void drop_timeout_handlers_subscriptions_up_to(std::size_t upper_border) noexcept
An implementation of unsubscription of the first N timeout handlers.
::so_5::outliving_reference_t<::so_5::agent_t > m_owner
Owner of async operation.
void do_unsubscribe_timeout_handlers() noexcept
Actual unsubscription for timeout handlers.