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  using std::swap;
150  swap( a.m_envelope, b.m_envelope );
151  swap( a.m_actual_id, b.m_actual_id );
152  }
153 
154  //! Is message delivery still in progress?
155  /*!
156  * \note Please take care when using this method.
157  * Message delivery in SObjectizer is asynchronous operation.
158  * It means that you can receve \a true from is_active() but
159  * this value will already be obsolete because the message
160  * can be delivered just before return from is_active().
161  * The return value of is_active() can be useful in that context:
162  * \code
163  * namespace timer_ns = so_5::extra::revocable_timer;
164  * void demo(so_5::mchain_t work_queue) {
165  * auto id = timer_ns::send_delayed(work_queue, 10s, ...);
166  * ... // Do some work.
167  * if(some_condition)
168  * id.revoke();
169  * ... // Do some more work.
170  * if(another_condition)
171  * id.revoke();
172  * ...
173  * if(id.is_active()) {
174  * // No previous calls to revoke().
175  * ...
176  * }
177  * }
178  * \endcode
179  */
180  bool
181  is_active() const noexcept
182  {
183  return m_actual_id.is_active();
184  }
185 
186  //! Revoke the message and release the timer.
187  /*!
188  * \note
189  * It is safe to call release() for already revoked message.
190  */
191  void
192  release() noexcept
193  {
194  if( m_envelope )
195  {
196  m_envelope->revoke();
197  m_actual_id.release();
198 
199  m_envelope.reset();
200  }
201  }
202 
203  //! Revoke the message and release the timer.
204  /*!
205  * Just a synonym for release() method.
206  */
207  void
208  revoke() noexcept { release(); }
209  };
210 
211 namespace impl {
212 
213 /*
214  * This is helper for creation of initialized timer_id objects.
215  */
217  {
218  template< typename... Args >
219  [[nodiscard]] static auto
220  make( Args && ...args )
221  {
222  return ::so_5::extra::revocable_timer::timer_id_t{
223  std::forward<Args>(args)... };
224  }
225  };
226 
227 /*
228  * Helper function for actual sending of periodic message.
229  */
230 [[nodiscard]]
231 inline so_5::extra::revocable_timer::timer_id_t
233  const so_5::mbox_t & to,
234  const std::type_index & msg_type,
235  message_ref_t payload,
236  std::chrono::steady_clock::duration pause,
237  std::chrono::steady_clock::duration period )
238  {
239  using envelope_t = ::so_5::extra::revocable_timer::details::envelope_t;
240 
241  ::so_5::intrusive_ptr_t< envelope_t > envelope{
242  std::make_unique< envelope_t >( std::move(payload) ) };
243 
244  auto actual_id = ::so_5::low_level_api::schedule_timer(
245  msg_type,
246  envelope,
247  to,
248  pause,
249  period );
250 
251  return timer_id_maker_t::make(
252  std::move(envelope), std::move(actual_id) );
253  }
254 
255 /*
256  * This is helpers for send_delayed and send_periodic implementation.
257  */
258 
259 template< class Message, bool Is_Signal >
261  {
262  template< typename... Args >
263  [[nodiscard]] static ::so_5::extra::revocable_timer::timer_id_t
265  const ::so_5::mbox_t & to,
266  std::chrono::steady_clock::duration pause,
267  std::chrono::steady_clock::duration period,
268  Args &&... args )
269  {
272  std::forward< Args >( args )...)
273  };
274 
276 
278  to,
280  std::move(payload),
281  pause,
282  period );
283  }
284  };
285 
286 template< class Message >
287 struct instantiator_and_sender_base< Message, true >
288  {
289  //! Type of signal to be delivered.
291 
292  [[nodiscard]] static so_5::extra::revocable_timer::timer_id_t
294  const so_5::mbox_t & to,
295  std::chrono::steady_clock::duration pause,
296  std::chrono::steady_clock::duration period )
297  {
299  to,
301  message_ref_t{},
302  pause,
303  period );
304  }
305  };
306 
307 template< class Message >
310  Message,
312  {};
313 
314 } /* namespace impl */
315 
316 /*!
317  * \brief A utility function for creating and delivering a periodic message
318  * to the specified destination.
319  *
320  * Agent, mbox or mchain can be used as \a target.
321  *
322  * \note
323  * Message chains with overload control must be used for periodic messages
324  * with additional care because exceptions can't be thrown during
325  * dispatching messages from timer.
326  *
327  * Usage example 1:
328  * \code
329  * namespace timer_ns = so_5::extra::revocable_timer;
330  * class my_agent : public so_5::agent_t {
331  * timer_ns::timer_id_t timer_;
332  * ...
333  * void so_evt_start() override {
334  * ...
335  * // Initiate a periodic message to self.
336  * timer_ = timer_ns::send_periodic<do_some_task>(*this, 1s, 1s, ...);
337  * ...
338  * }
339  * ...
340  * };
341  * \endcode
342  *
343  * Usage example 2:
344  * \code
345  * so_5::wrapped_env_t sobj; // SObjectizer is started here.
346  * // Create a worker and get its mbox.
347  * so_5::mbox_t worker_mbox = sobj.environment().introduce_coop(
348  * [&](so_5::coop_t & coop) {
349  * auto worker = coop.make_agent<worker_agent>(...);
350  * return worker->so_direct_mbox();
351  * });
352  * // Send revocable periodic message to the worker.
353  * auto timer_id = so_5::extra::revocable_timer::send_periodic<tell_status>(
354  * worker_mbox(),
355  * 1s, 1s,
356  * ... );
357  * ... // Do some work.
358  * // Revoke the tell_status message.
359  * timer_id.release();
360  * \endcode
361  *
362  * \note
363  * The return value of that function must be stored somewhere. Otherwise
364  * the periodic timer will be cancelled automatically just right after
365  * send_periodic returns.
366  *
367  * \attention
368  * Values of \a pause and \a period should be non-negative.
369  *
370  * \tparam Message type of message or signal to be sent.
371  * \tparam Target can be so_5::agent_t, so_5::mbox_t or so_5::mchain_t.
372  * \tparam Args list of arguments for Message's constructor.
373  *
374  * \since
375  * v.1.2.0
376  */
377 template< typename Message, typename Target, typename... Args >
378 [[nodiscard]] timer_id_t
380  //! A destination for the periodic message.
381  Target && target,
382  //! Pause for message delaying.
383  std::chrono::steady_clock::duration pause,
384  //! Period of message repetitions.
385  std::chrono::steady_clock::duration period,
386  //! Message constructor parameters.
387  Args&&... args )
388  {
391  pause,
392  period,
393  std::forward< Args >( args )... );
394  }
395 
396 /*!
397  * \brief A utility function for delivering a periodic
398  * from an existing message hood.
399  *
400  * \attention Message must not be a mutable message if \a period is not 0.
401  * Otherwise an exception will be thrown.
402  *
403  * \tparam Message a type of message to be redirected (it can be
404  * in form of Msg, so_5::immutable_msg<Msg> or so_5::mutable_msg<Msg>).
405  *
406  * Usage example:
407  * \code
408  namespace timer_ns = so_5::extra::revocable_timer;
409  class redirector : public so_5::agent_t {
410  ...
411  void on_some_immutable_message(mhood_t<first_msg> cmd) {
412  timer_id = timer_ns::send_periodic(
413  another_mbox,
414  std::chrono::seconds(1),
415  std::chrono::seconds(15),
416  cmd);
417  ...
418  }
419 
420  void on_some_mutable_message(mhood_t<mutable_msg<second_msg>> cmd) {
421  timer_id = timer_ns::send_periodic(
422  another_mbox,
423  std::chrono::seconds(1),
424  std::chrono::seconds(20),
425  std::move(cmd));
426  // Note: cmd is nullptr now, it can't be used anymore.
427  ...
428  }
429  };
430  * \endcode
431  *
432  * \note
433  * The return value of that function must be stored somewhere. Otherwise
434  * the periodic timer will be cancelled automatically just right after
435  * send_periodic returns.
436  *
437  * \attention
438  * Values of \a pause and \a period should be non-negative.
439  *
440  * \since
441  * v.1.2.0
442  */
443 template< typename Message >
444 [[nodiscard]]
445 typename std::enable_if<
446  !::so_5::is_signal< Message >::value,
447  timer_id_t >::type
449  //! Mbox for the message to be sent to.
450  const ::so_5::mbox_t & to,
451  //! Pause for message delaying.
453  //! Period of message repetitions.
455  //! Existing message hood for message to be sent.
456  ::so_5::mhood_t< Message > mhood )
457  {
459  to,
462  pause,
463  period );
464  }
465 
466 /*!
467  * \brief A utility function for periodic redirection of a signal
468  * from existing message hood.
469  *
470  * \tparam Message a type of signal to be redirected (it can be
471  * in form of Sig or so_5::immutable_msg<Sig>).
472  *
473  * Usage example:
474  * \code
475  class redirector : public so_5::agent_t {
476  ...
477  void on_some_immutable_signal(mhood_t<some_signal> cmd) {
478  timer_id = so_5::extra::revocable_timer::send_periodic(
479  another_mbox,
480  std::chrono::seconds(1),
481  std::chrono::seconds(10),
482  cmd);
483  ...
484  }
485  };
486  * \endcode
487  *
488  * \note
489  * The return value of that function must be stored somewhere. Otherwise
490  * the periodic timer will be cancelled automatically just right after
491  * send_periodic returns.
492  *
493  * \attention
494  * Values of \a pause and \a period should be non-negative.
495  *
496  * \since
497  * v.1.2.0
498  */
499 template< typename Message >
500 [[nodiscard]]
501 typename std::enable_if<
502  ::so_5::is_signal< Message >::value,
503  timer_id_t >::type
505  //! Mbox for the message to be sent to.
506  const ::so_5::mbox_t & to,
507  //! Pause for message delaying.
509  //! Period of message repetitions.
511  //! Existing message hood for message to be sent.
512  ::so_5::mhood_t< Message > /*mhood*/ )
513  {
515  to,
517  message_ref_t{},
518  pause,
519  period );
520  }
521 
522 /*!
523  * \brief A helper function for redirection of a message/signal as a periodic
524  * message/signal.
525  *
526  * This function can be used if \a target is a reference to agent or if
527  * \a target is a mchain
528  *
529  * Example usage:
530  * \code
531  * namespace timer_ns = so_5::extra::revocable_timer;
532  * class my_agent : public so_5::agent_t {
533  * ...
534  * so_5::mchain_t target_mchain_;
535  * timer_ns::timer_id_t periodic_msg_id_;
536  * ...
537  * void on_some_msg(mhood_t<some_msg> cmd) {
538  * if( ... )
539  * // Message should be resend as a periodic message.
540  * periodic_msg_id_ = timer_ns::send_periodic(target_mchain_, 10s, 20s, std::move(cmd));
541  * }
542  * \endcode
543  *
544  * \note
545  * The return value of that function must be stored somewhere. Otherwise
546  * the periodic timer will be cancelled automatically just right after
547  * send_periodic returns.
548  *
549  * \attention
550  * Values of \a pause and \a period should be non-negative.
551  *
552  * \since
553  * v.1.2.0
554  */
555 template< typename Message, typename Target >
558  //! A target for periodic message/signal.
559  //! It can be a reference to a target agent or a mchain_t.
560  Target && target,
561  //! Pause for the first occurence of the message/signal.
563  //! Period of message repetitions.
565  //! Existing message hood for message/signal to be sent.
566  ::so_5::mhood_t< Message > mhood )
567  {
570  pause,
571  period,
572  std::move(mhood) );
573  }
574 
575 /*!
576  * \brief A utility function for creating and delivering a delayed message
577  * to the specified destination.
578  *
579  * Agent, mbox or mchain can be used as \a target.
580  *
581  * \note
582  * Message chains with overload control must be used for periodic messages
583  * with additional care because exceptions can't be thrown during
584  * dispatching messages from timer.
585  *
586  * Usage example 1:
587  * \code
588  * namespace timer_ns = so_5::extra::revocable_timer;
589  * class my_agent : public so_5::agent_t {
590  * timer_ns::timer_id_t timer_;
591  * ...
592  * void so_evt_start() override {
593  * ...
594  * // Initiate a delayed message to self.
595  * timer_ = timer_ns::send_periodic<kill_youself>(*this, 60s, ...);
596  * ...
597  * }
598  * ...
599  * };
600  * \endcode
601  *
602  * Usage example 2:
603  * \code
604  * so_5::wrapped_env_t sobj; // SObjectizer is started here.
605  * // Create a worker and get its mbox.
606  * so_5::mbox_t worker_mbox = sobj.environment().introduce_coop(
607  * [&](so_5::coop_t & coop) {
608  * auto worker = coop.make_agent<worker_agent>(...);
609  * worker_mbox = worker->so_direct_mbox();
610  * });
611  * // Send revocable delayed message to the worker.
612  * auto timer_id = so_5::extra::revocable_timer::send_periodic<kill_yourself>(
613  * worker_mbox(),
614  * 60s,
615  * ... );
616  * ... // Do some work.
617  * // Revoke the kill_yourself message.
618  * timer_id.release();
619  * \endcode
620  *
621  * \note
622  * The return value of that function must be stored somewhere. Otherwise
623  * the delayed timer will be cancelled automatically just right after
624  * send_delayed returns.
625  *
626  * \attention
627  * Value of \a pause should be non-negative.
628  *
629  * \tparam Message type of message or signal to be sent.
630  * \tparam Target can be so_5::agent_t, so_5::mbox_t or so_5::mchain_t.
631  * \tparam Args list of arguments for Message's constructor.
632  *
633  * \since
634  * v.1.2.0
635  */
636 template< typename Message, typename Target, typename... Args >
639  //! A destination for the periodic message.
640  Target && target,
641  //! Pause for message delaying.
643  //! Message constructor parameters.
644  Args&&... args )
645  {
648  pause,
650  std::forward<Args>(args)... );
651  }
652 
653 /*!
654  * \brief A helper function for redirection of existing message/signal
655  * as delayed message.
656  *
657  * Usage example:
658  * \code
659  * namespace timer_ns = so_5::extra::revocable_timer;
660  * class my_agent : public so_5::agent_t {
661  * const so_5::mbox_t another_worker_;
662  * timer_ns::timer_id_t timer_;
663  * ...
664  * void on_some_msg(mhood_t<some_message> cmd) {
665  * // Redirect this message to another worker with delay in 250ms.
666  * timer_ = timer_ns::send_delayed(
667  * another_worker_,
668  * std::chrono::milliseconds(250),
669  * std::move(cmd));
670  * ...
671  * }
672  * };
673  * \endcode
674  *
675  * \note
676  * The return value of that function must be stored somewhere. Otherwise
677  * the delayed timer will be cancelled automatically just right after
678  * send_delayed returns.
679  *
680  * \attention
681  * Value of \a pause should be non-negative.
682  *
683  * \tparam Message type of message or signal to be sent.
684  *
685  * \since
686  * v.1.2.0
687  */
688 template< typename Message >
691  //! Mbox for the message to be sent to.
692  const so_5::mbox_t & to,
693  //! Pause for message delaying.
695  //! Message to redirect.
696  ::so_5::mhood_t< Message > cmd )
697  {
699  to,
700  pause,
702  std::move(cmd) );
703  }
704 
705 /*!
706  * \brief A helper function for redirection of existing message/signal
707  * as delayed message.
708  *
709  * Agent or mchain can be used as \a target.
710  *
711  * Usage example:
712  * \code
713  * namespace timer_ns = so_5::extra::revocable_timer;
714  * class my_agent : public so_5::agent_t {
715  * const so_5::mchain_t work_queue_;
716  * timer_ns::timer_id_t timer_;
717  * ...
718  * void on_some_msg(mhood_t<some_message> cmd) {
719  * // Redirect this message to another worker with delay in 250ms.
720  * timer_ = timer_ns::send_delayed(work_queue_,
721  * std::chrono::milliseconds(250),
722  * std::move(cmd));
723  * ...
724  * }
725  * };
726  * \endcode
727  *
728  * \note
729  * The return value of that function must be stored somewhere. Otherwise
730  * the delayed timer will be cancelled automatically just right after
731  * send_delayed returns.
732  *
733  * \attention
734  * Value of \a pause should be non-negative.
735  *
736  * \tparam Message type of message or signal to be sent.
737  *
738  * \since
739  * v.1.2.0
740  */
741 template< typename Message, typename Target >
744  //! A destination for the periodic message.
745  Target && target,
746  //! Pause for message delaying.
748  //! Message to redirect.
749  ::so_5::mhood_t< Message > cmd )
750  {
753  pause,
755  std::move(cmd) );
756  }
757 
758 } /* namespace revocable_timer */
759 
760 } /* namespace extra */
761 
762 } /* 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:293
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:743
void release() noexcept
Revoke the message and release the timer.
Definition: pub.hpp:192
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:379
void revoke() noexcept
Revoke the message and release the timer.
Definition: pub.hpp:208
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:232
bool is_active() const noexcept
Is message delivery still in progress?
Definition: pub.hpp:181
::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:264