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