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  */
42 using envelope_t = so_5::extra::revocable_msg::details::envelope_t;
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 	{
108 		friend struct ::so_5::extra::revocable_timer::impl::timer_id_maker_t;
109 
110 	private :
111 		//! The envelope that was sent.
112 		/*!
113 		 * \note Can be nullptr if default constructor was used.
114 		 */
115 		::so_5::intrusive_ptr_t< details::envelope_t > m_envelope;
116 
117 		//! Timer ID for the envelope.
118 		::so_5::timer_id_t m_actual_id;
119 
timer_id_t(::so_5::intrusive_ptr_t<details::envelope_t> envelope,::so_5::timer_id_t actual_id)120 		timer_id_t(
121 			::so_5::intrusive_ptr_t< details::envelope_t > envelope,
122 			::so_5::timer_id_t actual_id )
123 			:	m_envelope{ std::move(envelope) }
124 			,	m_actual_id{ std::move(actual_id) }
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 		 */
~timer_id_t()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
swap(timer_id_t & a,timer_id_t & b)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
is_active() const180 		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
release()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
revoke()207 		revoke() noexcept { release(); }
208 	};
209 
210 namespace impl {
211 
212 /*
213  * This is helper for creation of initialized timer_id objects.
214  */
215 struct timer_id_maker_t
216 	{
217 		template< typename... Args >
218 		[[nodiscard]] static auto
makeso_5::extra::revocable_timer::impl::timer_id_maker_t219 		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
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)231 make_envelope_and_initiate_timer(
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 >
259 struct instantiator_and_sender_base
260 	{
261 		template< typename... Args >
262 		[[nodiscard]] static ::so_5::extra::revocable_timer::timer_id_t
send_periodicso_5::extra::revocable_timer::impl::instantiator_and_sender_base263 		send_periodic(
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 			{
269 				message_ref_t payload{
270 						so_5::details::make_message_instance< Message >(
271 								std::forward< Args >( args )...)
272 				};
273 
274 				so_5::details::mark_as_mutable_if_necessary< Message >( *payload );
275 
276 				return make_envelope_and_initiate_timer(
277 						to,
278 						message_payload_type< Message >::subscription_type_index(),
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.
289 		using actual_signal_type = typename message_payload_type< Message >::subscription_type;
290 
291 		[[nodiscard]] static so_5::extra::revocable_timer::timer_id_t
send_periodicso_5::extra::revocable_timer::impl::instantiator_and_sender_base292 		send_periodic(
293 			const so_5::mbox_t & to,
294 			std::chrono::steady_clock::duration pause,
295 			std::chrono::steady_clock::duration period )
296 			{
297 				return make_envelope_and_initiate_timer(
298 						to,
299 						message_payload_type< Message >::subscription_type_index(),
300 						message_ref_t{},
301 						pause,
302 						period );
303 			}
304 	};
305 
306 template< class Message >
307 struct instantiator_and_sender
308 	:	public instantiator_and_sender_base<
309 			Message,
310 			is_signal< typename message_payload_type< Message >::payload_type >::value >
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
send_periodic(Target && target,std::chrono::steady_clock::duration pause,std::chrono::steady_clock::duration period,Args &&...args)378 send_periodic(
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 	{
388 		return impl::instantiator_and_sender< Message >::send_periodic(
389 				::so_5::send_functions_details::arg_to_mbox( target ),
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
send_periodic(const::so_5::mbox_t & to,std::chrono::steady_clock::duration pause,std::chrono::steady_clock::duration period,::so_5::mhood_t<Message> mhood)447 send_periodic(
448 	//! Mbox for the message to be sent to.
449 	const ::so_5::mbox_t & to,
450 	//! Pause for message delaying.
451 	std::chrono::steady_clock::duration pause,
452 	//! Period of message repetitions.
453 	std::chrono::steady_clock::duration period,
454 	//! Existing message hood for message to be sent.
455 	::so_5::mhood_t< Message > mhood )
456 	{
457 		return impl::make_envelope_and_initiate_timer(
458 				to,
459 				message_payload_type< Message >::subscription_type_index(),
460 				mhood.make_reference(),
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
send_periodic(const::so_5::mbox_t & to,std::chrono::steady_clock::duration pause,std::chrono::steady_clock::duration period,::so_5::mhood_t<Message>)503 send_periodic(
504 	//! Mbox for the message to be sent to.
505 	const ::so_5::mbox_t & to,
506 	//! Pause for message delaying.
507 	std::chrono::steady_clock::duration pause,
508 	//! Period of message repetitions.
509 	std::chrono::steady_clock::duration period,
510 	//! Existing message hood for message to be sent.
511 	::so_5::mhood_t< Message > /*mhood*/ )
512 	{
513 		return impl::make_envelope_and_initiate_timer(
514 				to,
515 				message_payload_type< Message >::subscription_type_index(),
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 >
555 [[nodiscard]] timer_id_t
send_periodic(Target && target,std::chrono::steady_clock::duration pause,std::chrono::steady_clock::duration period,::so_5::mhood_t<Message> mhood)556 send_periodic(
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.
561 	std::chrono::steady_clock::duration pause,
562 	//! Period of message repetitions.
563 	std::chrono::steady_clock::duration period,
564 	//! Existing message hood for message/signal to be sent.
565 	::so_5::mhood_t< Message > mhood )
566 	{
567 		return ::so_5::extra::revocable_timer::send_periodic< Message >(
568 				::so_5::send_functions_details::arg_to_mbox( target ),
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 >
636 [[nodiscard]] timer_id_t
send_delayed(Target && target,std::chrono::steady_clock::duration pause,Args &&...args)637 send_delayed(
638 	//! A destination for the periodic message.
639 	Target && target,
640 	//! Pause for message delaying.
641 	std::chrono::steady_clock::duration pause,
642 	//! Message constructor parameters.
643 	Args&&... args )
644 	{
645 		return ::so_5::extra::revocable_timer::send_periodic< Message >(
646 				::so_5::send_functions_details::arg_to_mbox( target ),
647 				pause,
648 				std::chrono::steady_clock::duration::zero(),
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 >
688 [[nodiscard]] timer_id_t
send_delayed(const so_5::mbox_t & to,std::chrono::steady_clock::duration pause,::so_5::mhood_t<Message> cmd)689 send_delayed(
690 	//! Mbox for the message to be sent to.
691 	const so_5::mbox_t & to,
692 	//! Pause for message delaying.
693 	std::chrono::steady_clock::duration pause,
694 	//! Message to redirect.
695 	::so_5::mhood_t< Message > cmd )
696 	{
697 		return ::so_5::extra::revocable_timer::send_periodic< Message >(
698 				to,
699 				pause,
700 				std::chrono::steady_clock::duration::zero(),
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 >
741 [[nodiscard]] timer_id_t
send_delayed(Target && target,std::chrono::steady_clock::duration pause,::so_5::mhood_t<Message> cmd)742 send_delayed(
743 	//! A destination for the periodic message.
744 	Target && target,
745 	//! Pause for message delaying.
746 	std::chrono::steady_clock::duration pause,
747 	//! Message to redirect.
748 	::so_5::mhood_t< Message > cmd )
749 	{
750 		return ::so_5::extra::revocable_timer::send_periodic(
751 				std::forward<Target>(target),
752 				pause,
753 				std::chrono::steady_clock::duration::zero(),
754 				std::move(cmd) );
755 	}
756 
757 } /* namespace revocable_timer */
758 
759 } /* namespace extra */
760 
761 } /* namespace so_5 */
762 
763