SObjectizer-5 Extra
pub.hpp
Go to the documentation of this file.
1 /*!
2  * \file
3  * \brief Implementation of revocable timers
4  *
5  * \since
6  * v.1.2.0
7  */
8 
9 #pragma once
10 
11 #include <so_5/version.hpp>
12 
13 #include <so_5_extra/revocable_msg/pub.hpp>
14 
15 #include <so_5_extra/error_ranges.hpp>
16 
17 #include <so_5/timers.hpp>
18 #include <so_5/enveloped_msg.hpp>
19 #include <so_5/send_functions.hpp>
20 
21 #include <atomic>
22 
23 namespace so_5 {
24 
25 namespace extra {
26 
27 namespace revocable_timer {
28 
29 namespace details {
30 
31 //
32 // envelope_t
33 //
34 /*!
35  * \brief A special envelope to be used for revocable timer messages.
36  *
37  * Just a synonim for so_5::extra::revocable_msg::details::envelope_t.
38  *
39  * \since
40  * v.1.2.0
41  */
43 
44 } /* namespace details */
45 
46 namespace impl {
47 
48 // Just forward declaration. Definition will be below definition of timer_id_t.
49 struct timer_id_maker_t;
50 
51 } /* namespace impl */
52 
53 //
54 // timer_id_t
55 //
56 /*!
57  * \brief The ID of revocable timer message/signal.
58  *
59  * This type plays the same role as so_5::timer_id_t. But provide
60  * guaranteed revocation of delayed/periodic message/signal.
61  *
62  * There are several implementations of send_delayed() and send_periodic()
63  * functions in so_5::extra::revocable_timer namespace. They all return
64  * instances of timer_id_t.
65  *
66  * An instance of timer_id_t returned from send_delayed/send_periodic need
67  * to be store somewhere. Otherwise the timer message will be revoked
68  * just after completion of send_delayed/send_periodic function. It is
69  * because the destructor of timer_id_t will be called and that destructor
70  * revokes the timer message.
71  *
72  * An instance of timer_id_t can be used for revocation of a timer message.
73  * Revocation can be performed by two ways:
74  *
75  * 1. Destructor of timer_id_t automatically revokes the timer message.
76  * 2. Method timer_id_t::release() or timer_id_t::revoke() is called
77  * by an user.
78  *
79  * For example:
80  * \code
81  * namespace timer_ns = so_5::extra::revocable_timer;
82  * void demo(so_5::mchain_t work_queue) {
83  * // Send a delayed demand to work queue and store the ID returned.
84  * auto id = timer_ns::send_delayed<flush_data>(work_queue, 10s, ...);
85  * ... // Do some work.
86  * if(some_condition)
87  * // Our previous message should be revoked if it is not delivered yet.
88  * id.release();
89  * ...
90  * // Message will be automatically revoked here because ID is destroyed
91  * // on leaving the scope.
92  * }
93  * \endcode
94  *
95  * \note
96  * The timer_id_t is Movable, not Copyable.
97  *
98  * \attention
99  * This is not a thread-safe class. It means that it is dangerous to
100  * call methods of that class (like revoke() or is_active()) from
101  * different threads at the same time.
102  *
103  * \since
104  * v.1.2.0
105  */
106 class timer_id_t final
107  {
109 
110  private :
111  //! The envelope that was sent.
112  /*!
113  * \note Can be nullptr if default constructor was used.
114  */
116 
117  //! Timer ID for the envelope.
119 
121  ::so_5::intrusive_ptr_t< details::envelope_t > envelope,
122  ::so_5::timer_id_t actual_id )
123  : m_envelope{ std::move(envelope) }
125  {}
126 
127  public :
128  timer_id_t() = default;
129  /*!
130  * \note The destructor automatically revokes the message if it is
131  * not delivered yet.
132  */
133  ~timer_id_t() noexcept
134  {
135  release();
136  }
137 
138  // This class is not copyable.
139  timer_id_t( const timer_id_t & ) = delete;
140  timer_id_t & operator=( const timer_id_t & ) = delete;
141 
142  // But this class is moveable.
143  timer_id_t( timer_id_t && ) noexcept = default;
144  timer_id_t & operator=( timer_id_t && ) noexcept = default;
145 
146  friend void
147  swap( timer_id_t & a, timer_id_t & b ) noexcept
148  {
149  a.m_envelope.swap( b.m_envelope );
150  a.m_actual_id.swap( b.m_actual_id );
151  }
152 
153  //! Is message delivery still in progress?
154  /*!
155  * \note Please take care when using this method.
156  * Message delivery in SObjectizer is asynchronous operation.
157  * It means that you can receve \a true from is_active() but
158  * this value will already be obsolete because the message
159  * can be delivered just before return from is_active().
160  * The return value of is_active() can be useful in that context:
161  * \code
162  * namespace timer_ns = so_5::extra::revocable_timer;
163  * void demo(so_5::mchain_t work_queue) {
164  * auto id = timer_ns::send_delayed(work_queue, 10s, ...);
165  * ... // Do some work.
166  * if(some_condition)
167  * id.revoke();
168  * ... // Do some more work.
169  * if(another_condition)
170  * id.revoke();
171  * ...
172  * if(id.is_active()) {
173  * // No previous calls to revoke().
174  * ...
175  * }
176  * }
177  * \endcode
178  */
179  bool
180  is_active() const noexcept
181  {
182  return m_actual_id.is_active();
183  }
184 
185  //! Revoke the message and release the timer.
186  /*!
187  * \note
188  * It is safe to call release() for already revoked message.
189  */
190  void
191  release() noexcept
192  {
193  if( m_envelope )
194  {
195  m_envelope->revoke();
196  m_actual_id.release();
197 
198  m_envelope.reset();
199  }
200  }
201 
202  //! Revoke the message and release the timer.
203  /*!
204  * Just a synonym for release() method.
205  */
206  void
207  revoke() noexcept { release(); }
208  };
209 
210 namespace impl {
211 
212 /*
213  * This is helper for creation of initialized timer_id objects.
214  */
216  {
217  template< typename... Args >
218  [[nodiscard]] static auto
219  make( Args && ...args )
220  {
221  return ::so_5::extra::revocable_timer::timer_id_t{
222  std::forward<Args>(args)... };
223  }
224  };
225 
226 /*
227  * Helper function for actual sending of periodic message.
228  */
229 [[nodiscard]]
230 inline so_5::extra::revocable_timer::timer_id_t
232  const so_5::mbox_t & to,
233  const std::type_index & msg_type,
234  message_ref_t payload,
235  std::chrono::steady_clock::duration pause,
236  std::chrono::steady_clock::duration period )
237  {
238  using envelope_t = ::so_5::extra::revocable_timer::details::envelope_t;
239 
240  ::so_5::intrusive_ptr_t< envelope_t > envelope{
241  std::make_unique< envelope_t >( std::move(payload) ) };
242 
243  auto actual_id = ::so_5::low_level_api::schedule_timer(
244  msg_type,
245  envelope,
246  to,
247  pause,
248  period );
249 
250  return timer_id_maker_t::make(
251  std::move(envelope), std::move(actual_id) );
252  }
253 
254 /*
255  * This is helpers for send_delayed and send_periodic implementation.
256  */
257 
258 template< class Message, bool Is_Signal >
260  {
261  template< typename... Args >
262  [[nodiscard]] static ::so_5::extra::revocable_timer::timer_id_t
264  const ::so_5::mbox_t & to,
265  std::chrono::steady_clock::duration pause,
266  std::chrono::steady_clock::duration period,
267  Args &&... args )
268  {
271  std::forward< Args >( args )...)
272  };
273 
275 
277  to,
279  std::move(payload),
280  pause,
281  period );
282  }
283  };
284 
285 template< class Message >
286 struct instantiator_and_sender_base< Message, true >
287  {
288  //! Type of signal to be delivered.
290 
291  [[nodiscard]] static so_5::extra::revocable_timer::timer_id_t
293  const so_5::mbox_t & to,
294  std::chrono::steady_clock::duration pause,
295  std::chrono::steady_clock::duration period )
296  {
298  to,
300  message_ref_t{},
301  pause,
302  period );
303  }
304  };
305 
306 template< class Message >
309  Message,
311  {};
312 
313 } /* namespace impl */
314 
315 /*!
316  * \brief A utility function for creating and delivering a periodic message
317  * to the specified destination.
318  *
319  * Agent, mbox or mchain can be used as \a target.
320  *
321  * \note
322  * Message chains with overload control must be used for periodic messages
323  * with additional care because exceptions can't be thrown during
324  * dispatching messages from timer.
325  *
326  * Usage example 1:
327  * \code
328  * namespace timer_ns = so_5::extra::revocable_timer;
329  * class my_agent : public so_5::agent_t {
330  * timer_ns::timer_id_t timer_;
331  * ...
332  * void so_evt_start() override {
333  * ...
334  * // Initiate a periodic message to self.
335  * timer_ = timer_ns::send_periodic<do_some_task>(*this, 1s, 1s, ...);
336  * ...
337  * }
338  * ...
339  * };
340  * \endcode
341  *
342  * Usage example 2:
343  * \code
344  * so_5::wrapped_env_t sobj; // SObjectizer is started here.
345  * // Create a worker and get its mbox.
346  * so_5::mbox_t worker_mbox = sobj.environment().introduce_coop(
347  * [&](so_5::coop_t & coop) {
348  * auto worker = coop.make_agent<worker_agent>(...);
349  * return worker->so_direct_mbox();
350  * });
351  * // Send revocable periodic message to the worker.
352  * auto timer_id = so_5::extra::revocable_timer::send_periodic<tell_status>(
353  * worker_mbox(),
354  * 1s, 1s,
355  * ... );
356  * ... // Do some work.
357  * // Revoke the tell_status message.
358  * timer_id.release();
359  * \endcode
360  *
361  * \note
362  * The return value of that function must be stored somewhere. Otherwise
363  * the periodic timer will be cancelled automatically just right after
364  * send_periodic returns.
365  *
366  * \attention
367  * Values of \a pause and \a period should be non-negative.
368  *
369  * \tparam Message type of message or signal to be sent.
370  * \tparam Target can be so_5::agent_t, so_5::mbox_t or so_5::mchain_t.
371  * \tparam Args list of arguments for Message's constructor.
372  *
373  * \since
374  * v.1.2.0
375  */
376 template< typename Message, typename Target, typename... Args >
377 [[nodiscard]] timer_id_t
379  //! A destination for the periodic message.
380  Target && target,
381  //! Pause for message delaying.
382  std::chrono::steady_clock::duration pause,
383  //! Period of message repetitions.
384  std::chrono::steady_clock::duration period,
385  //! Message constructor parameters.
386  Args&&... args )
387  {
390  pause,
391  period,
392  std::forward< Args >( args )... );
393  }
394 
395 /*!
396  * \brief A utility function for delivering a periodic
397  * from an existing message hood.
398  *
399  * \attention Message must not be a mutable message if \a period is not 0.
400  * Otherwise an exception will be thrown.
401  *
402  * \tparam Message a type of message to be redirected (it can be
403  * in form of Msg, so_5::immutable_msg<Msg> or so_5::mutable_msg<Msg>).
404  *
405  * Usage example:
406  * \code
407  namespace timer_ns = so_5::extra::revocable_timer;
408  class redirector : public so_5::agent_t {
409  ...
410  void on_some_immutable_message(mhood_t<first_msg> cmd) {
411  timer_id = timer_ns::send_periodic(
412  another_mbox,
413  std::chrono::seconds(1),
414  std::chrono::seconds(15),
415  cmd);
416  ...
417  }
418 
419  void on_some_mutable_message(mhood_t<mutable_msg<second_msg>> cmd) {
420  timer_id = timer_ns::send_periodic(
421  another_mbox,
422  std::chrono::seconds(1),
423  std::chrono::seconds(20),
424  std::move(cmd));
425  // Note: cmd is nullptr now, it can't be used anymore.
426  ...
427  }
428  };
429  * \endcode
430  *
431  * \note
432  * The return value of that function must be stored somewhere. Otherwise
433  * the periodic timer will be cancelled automatically just right after
434  * send_periodic returns.
435  *
436  * \attention
437  * Values of \a pause and \a period should be non-negative.
438  *
439  * \since
440  * v.1.2.0
441  */
442 template< typename Message >
443 [[nodiscard]]
444 typename std::enable_if<
445  !::so_5::is_signal< Message >::value,
446  timer_id_t >::type
448  //! Mbox for the message to be sent to.
449  const ::so_5::mbox_t & to,
450  //! Pause for message delaying.
452  //! Period of message repetitions.
454  //! Existing message hood for message to be sent.
455  ::so_5::mhood_t< Message > mhood )
456  {
458  to,
461  pause,
462  period );
463  }
464 
465 /*!
466  * \brief A utility function for periodic redirection of a signal
467  * from existing message hood.
468  *
469  * \tparam Message a type of signal to be redirected (it can be
470  * in form of Sig or so_5::immutable_msg<Sig>).
471  *
472  * Usage example:
473  * \code
474  class redirector : public so_5::agent_t {
475  ...
476  void on_some_immutable_signal(mhood_t<some_signal> cmd) {
477  timer_id = so_5::extra::revocable_timer::send_periodic(
478  another_mbox,
479  std::chrono::seconds(1),
480  std::chrono::seconds(10),
481  cmd);
482  ...
483  }
484  };
485  * \endcode
486  *
487  * \note
488  * The return value of that function must be stored somewhere. Otherwise
489  * the periodic timer will be cancelled automatically just right after
490  * send_periodic returns.
491  *
492  * \attention
493  * Values of \a pause and \a period should be non-negative.
494  *
495  * \since
496  * v.1.2.0
497  */
498 template< typename Message >
499 [[nodiscard]]
500 typename std::enable_if<
501  ::so_5::is_signal< Message >::value,
502  timer_id_t >::type
504  //! Mbox for the message to be sent to.
505  const ::so_5::mbox_t & to,
506  //! Pause for message delaying.
508  //! Period of message repetitions.
510  //! Existing message hood for message to be sent.
511  ::so_5::mhood_t< Message > /*mhood*/ )
512  {
514  to,
516  message_ref_t{},
517  pause,
518  period );
519  }
520 
521 /*!
522  * \brief A helper function for redirection of a message/signal as a periodic
523  * message/signal.
524  *
525  * This function can be used if \a target is a reference to agent or if
526  * \a target is a mchain
527  *
528  * Example usage:
529  * \code
530  * namespace timer_ns = so_5::extra::revocable_timer;
531  * class my_agent : public so_5::agent_t {
532  * ...
533  * so_5::mchain_t target_mchain_;
534  * timer_ns::timer_id_t periodic_msg_id_;
535  * ...
536  * void on_some_msg(mhood_t<some_msg> cmd) {
537  * if( ... )
538  * // Message should be resend as a periodic message.
539  * periodic_msg_id_ = timer_ns::send_periodic(target_mchain_, 10s, 20s, std::move(cmd));
540  * }
541  * \endcode
542  *
543  * \note
544  * The return value of that function must be stored somewhere. Otherwise
545  * the periodic timer will be cancelled automatically just right after
546  * send_periodic returns.
547  *
548  * \attention
549  * Values of \a pause and \a period should be non-negative.
550  *
551  * \since
552  * v.1.2.0
553  */
554 template< typename Message, typename Target >
557  //! A target for periodic message/signal.
558  //! It can be a reference to a target agent or a mchain_t.
559  Target && target,
560  //! Pause for the first occurence of the message/signal.
562  //! Period of message repetitions.
564  //! Existing message hood for message/signal to be sent.
565  ::so_5::mhood_t< Message > mhood )
566  {
569  pause,
570  period,
571  std::move(mhood) );
572  }
573 
574 /*!
575  * \brief A utility function for creating and delivering a delayed message
576  * to the specified destination.
577  *
578  * Agent, mbox or mchain can be used as \a target.
579  *
580  * \note
581  * Message chains with overload control must be used for periodic messages
582  * with additional care because exceptions can't be thrown during
583  * dispatching messages from timer.
584  *
585  * Usage example 1:
586  * \code
587  * namespace timer_ns = so_5::extra::revocable_timer;
588  * class my_agent : public so_5::agent_t {
589  * timer_ns::timer_id_t timer_;
590  * ...
591  * void so_evt_start() override {
592  * ...
593  * // Initiate a delayed message to self.
594  * timer_ = timer_ns::send_periodic<kill_youself>(*this, 60s, ...);
595  * ...
596  * }
597  * ...
598  * };
599  * \endcode
600  *
601  * Usage example 2:
602  * \code
603  * so_5::wrapped_env_t sobj; // SObjectizer is started here.
604  * // Create a worker and get its mbox.
605  * so_5::mbox_t worker_mbox = sobj.environment().introduce_coop(
606  * [&](so_5::coop_t & coop) {
607  * auto worker = coop.make_agent<worker_agent>(...);
608  * worker_mbox = worker->so_direct_mbox();
609  * });
610  * // Send revocable delayed message to the worker.
611  * auto timer_id = so_5::extra::revocable_timer::send_periodic<kill_yourself>(
612  * worker_mbox(),
613  * 60s,
614  * ... );
615  * ... // Do some work.
616  * // Revoke the kill_yourself message.
617  * timer_id.release();
618  * \endcode
619  *
620  * \note
621  * The return value of that function must be stored somewhere. Otherwise
622  * the delayed timer will be cancelled automatically just right after
623  * send_delayed returns.
624  *
625  * \attention
626  * Value of \a pause should be non-negative.
627  *
628  * \tparam Message type of message or signal to be sent.
629  * \tparam Target can be so_5::agent_t, so_5::mbox_t or so_5::mchain_t.
630  * \tparam Args list of arguments for Message's constructor.
631  *
632  * \since
633  * v.1.2.0
634  */
635 template< typename Message, typename Target, typename... Args >
638  //! A destination for the periodic message.
639  Target && target,
640  //! Pause for message delaying.
642  //! Message constructor parameters.
643  Args&&... args )
644  {
647  pause,
649  std::forward<Args>(args)... );
650  }
651 
652 /*!
653  * \brief A helper function for redirection of existing message/signal
654  * as delayed message.
655  *
656  * Usage example:
657  * \code
658  * namespace timer_ns = so_5::extra::revocable_timer;
659  * class my_agent : public so_5::agent_t {
660  * const so_5::mbox_t another_worker_;
661  * timer_ns::timer_id_t timer_;
662  * ...
663  * void on_some_msg(mhood_t<some_message> cmd) {
664  * // Redirect this message to another worker with delay in 250ms.
665  * timer_ = timer_ns::send_delayed(
666  * another_worker_,
667  * std::chrono::milliseconds(250),
668  * std::move(cmd));
669  * ...
670  * }
671  * };
672  * \endcode
673  *
674  * \note
675  * The return value of that function must be stored somewhere. Otherwise
676  * the delayed timer will be cancelled automatically just right after
677  * send_delayed returns.
678  *
679  * \attention
680  * Value of \a pause should be non-negative.
681  *
682  * \tparam Message type of message or signal to be sent.
683  *
684  * \since
685  * v.1.2.0
686  */
687 template< typename Message >
690  //! Mbox for the message to be sent to.
691  const so_5::mbox_t & to,
692  //! Pause for message delaying.
694  //! Message to redirect.
695  ::so_5::mhood_t< Message > cmd )
696  {
698  to,
699  pause,
701  std::move(cmd) );
702  }
703 
704 /*!
705  * \brief A helper function for redirection of existing message/signal
706  * as delayed message.
707  *
708  * Agent or mchain can be used as \a target.
709  *
710  * Usage example:
711  * \code
712  * namespace timer_ns = so_5::extra::revocable_timer;
713  * class my_agent : public so_5::agent_t {
714  * const so_5::mchain_t work_queue_;
715  * timer_ns::timer_id_t timer_;
716  * ...
717  * void on_some_msg(mhood_t<some_message> cmd) {
718  * // Redirect this message to another worker with delay in 250ms.
719  * timer_ = timer_ns::send_delayed(work_queue_,
720  * std::chrono::milliseconds(250),
721  * std::move(cmd));
722  * ...
723  * }
724  * };
725  * \endcode
726  *
727  * \note
728  * The return value of that function must be stored somewhere. Otherwise
729  * the delayed timer will be cancelled automatically just right after
730  * send_delayed returns.
731  *
732  * \attention
733  * Value of \a pause should be non-negative.
734  *
735  * \tparam Message type of message or signal to be sent.
736  *
737  * \since
738  * v.1.2.0
739  */
740 template< typename Message, typename Target >
743  //! A destination for the periodic message.
744  Target && target,
745  //! Pause for message delaying.
747  //! Message to redirect.
748  ::so_5::mhood_t< Message > cmd )
749  {
752  pause,
754  std::move(cmd) );
755  }
756 
757 } /* namespace revocable_timer */
758 
759 } /* namespace extra */
760 
761 } /* namespace so_5 */
timer_id_t & operator=(const timer_id_t &)=delete
friend void swap(timer_id_t &a, timer_id_t &b) noexcept
Definition: pub.hpp:147
static so_5::extra::revocable_timer::timer_id_t send_periodic(const so_5::mbox_t &to, std::chrono::steady_clock::duration pause, std::chrono::steady_clock::duration period)
Definition: pub.hpp:292
Ranges for error codes of each submodules.
Definition: details.hpp:13
timer_id_t(timer_id_t &&) noexcept=default
timer_id_t(::so_5::intrusive_ptr_t< details::envelope_t > envelope, ::so_5::timer_id_t actual_id)
Definition: pub.hpp:120
timer_id_t send_delayed(Target &&target, std::chrono::steady_clock::duration pause, ::so_5::mhood_t< Message > cmd)
A helper function for redirection of existing message/signal as delayed message.
Definition: pub.hpp:742
void release() noexcept
Revoke the message and release the timer.
Definition: pub.hpp:191
timer_id_t & operator=(timer_id_t &&) noexcept=default
timer_id_t send_periodic(Target &&target, std::chrono::steady_clock::duration pause, std::chrono::steady_clock::duration period, Args &&... args)
A utility function for creating and delivering a periodic message to the specified destination...
Definition: pub.hpp:378
void revoke() noexcept
Revoke the message and release the timer.
Definition: pub.hpp:207
so_5::extra::revocable_timer::timer_id_t make_envelope_and_initiate_timer(const so_5::mbox_t &to, const std::type_index &msg_type, message_ref_t payload, std::chrono::steady_clock::duration pause, std::chrono::steady_clock::duration period)
Definition: pub.hpp:231
bool is_active() const noexcept
Is message delivery still in progress?
Definition: pub.hpp:180
::so_5::intrusive_ptr_t< details::envelope_t > m_envelope
The envelope that was sent.
Definition: pub.hpp:115
::so_5::timer_id_t m_actual_id
Timer ID for the envelope.
Definition: pub.hpp:118
timer_id_t(const timer_id_t &)=delete
static ::so_5::extra::revocable_timer::timer_id_t send_periodic(const ::so_5::mbox_t &to, std::chrono::steady_clock::duration pause, std::chrono::steady_clock::duration period, Args &&... args)
Definition: pub.hpp:263