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