Skip to content

so5extra 1.4 Revocable Timers

Yauheni Akhotnikau edited this page Jan 15, 2020 · 1 revision

The Problem

SObjectizer Core has send_delayed and send_periodic functions. The so_5::send_periodic function returns an instance of so_5::timer_id_t. This instance can be used for cancellation of delayed/periodic message delivery. For example:

auto id = so_5::send_periodic<my_message>(mbox, 10s, 0s, ...);
...
if(some_condition())
    // Delivery of delayed message should be canceled.
    id.release();

But there is a problem: the cancellation will actually be performed only if the message is under control of SObjectizer's timer thread/manager. But if the timer thread/manager already sent the message and the message is waiting in an event queue then delivery won't be cancelled. It is becase there is no way to extract a message from an event queue.

The Solution

Since v.1.2.0 so_5_extra provides own timer_id_t, send_delayed and send_periodic versions those allow guaranteed revocation of a delayed/periodic messages. For example:

auto id = so_5::extra::revocable_timer::send_delayed<my_message>(mbox, 10s, ...);
...
if(some_condition())
    // Delivery of delayed message will be canceled.
    // Even if the message is already in an event queue now.
    id.release();

Header File

All stuff related to revocable timer messages/signals are defined in so_5_extra/revocable_timer/pub.hpp header file. So to use this functionality it is necessary to include that file and so_5/all.hpp file:

#include <so_5_extra/revocable_timer/pub.hpp>

#include <so_5/all.hpp>

How To Send And Revoke Delayed/Periodic Messages?

To send a delayed message/signal it is necessary to use so_5::extra::revocable_timer::send_delayed and to store the so_5::extra::revocable_timer::timer_id_t instance returned:

// Note that this is so_5::extra::revocable_timer::timer_id_t, not so_5::timer_id_t
so_5::extra::revocable_timer::timer_id_t id =
    so_5::extra::revocable_timer::send_delayed<my_message>(mbox, delay,
        ... /* Arguments for my_message's constructor */
        );

To send a periodic message/signal it is necessary to use so_5::extra::revocable_timer::send_periodic and to store the so_5::extra::revocable_timer::timer_id_t instance returned:

// Note that this is so_5::extra::revocable_timer::timer_id_t, not so_5::timer_id_t
so_5::extra::revocable_timer::timer_id_t id =
    so_5::extra::revocable_timer::send_periodic<my_message>(mbox, delay, period,
        ... /* Arguments for my_message's constructor */
        );

It is important to note that if the return value of send_delayed() or send_periodic() is not stored then the message sent will be revoked automatically.

To revoke message/signal sent it is possible to use those ways:

  1. Destruction of timer_id_t object. Destructor of timer_id_t automaticaly revokes message/signal sent.
  2. Calling timer_id_t::revoke() or timer_id_t::release() method. These methods revoke message/signal sent. It is safe to call revoke()/release() several times.
  3. Assigning a new value to timer_id_t object. Previously sent message/signal is revoked in this case.

For example:

class worker final : public so_5::agent_t {
    ...
    so_5::extra::revocable_timer::timer_id_t check_timer_;
    ...
    void on_do_something(mhood_t<some_cmd> cmd) {
        ... // Initiate some actions to be performed by other agents.
        // Start a timer for checking actions results after some time.
        check_timer_ = so_5::extra::revocable_timer::send_delayed<check_results>(
                *this, 10s, ...);
    }
    void on_results(mhood_t<some_result> cmd) {
        ... // Some handling.
        // Timer is no more needed.
        check_timer_.revoke();
    }
    ...
};

There are several forms of send_delayed functions:

// Construct and send a delayed message to an arbitrary mbox.
auto id1 = so_5::extra::revocable_timer::send_delayed<my_message>(mbox, 10s, ...);

// Construct and send a delayed message to the direct mbox of an agent.
auto id2 = so_5::extra::revocable_timer::send_delayed<my_message>(agent, 10s, ...);

// Construct and send a delayed message to a mchain.
auto id3 = so_5::extra::revocable_timer::send_delayed<my_message>(ch, 10s, ...);

// Resend an existing message to an arbitrary mbox:
void some_agent::on_some_message(mhood_t<some_message> cmd) {
    id_ = so_5::extra::revocable_timer::send_delayed(mbox, 10s, std::move(cmd));
}

// Resend an existing message to the direct mbox of an agent:
void some_agent::on_some_message(mhood_t<some_message> cmd) {
    id_ = so_5::extra::revocable_timer::send_delayed(*this, 10s, std::move(cmd));
}

// Resend an existing message to a mchain:
void some_agent::on_some_message(mhood_t<some_message> cmd) {
    id_ = so_5::extra::revocable_timer::send_delayed(ch, 10s, std::move(cmd));
}

The same is for send_periodic:

// Construct and send a periodic message to an arbitrary mbox.
auto id1 = so_5::extra::revocable_timer::send_periodic<my_message>(mbox, 10s, 5s, ...);

// Construct and send a periodic message to the direct mbox of an agent.
auto id2 = so_5::extra::revocable_timer::send_periodic<my_message>(agent, 10s, 5s, ...);

// Construct and send a periodic message to a mchain.
auto id3 = so_5::extra::revocable_timer::send_periodic<my_message>(ch, 10s, 5s, ...);

// Resend an existing message to an arbitrary mbox:
void some_agent::on_some_message(mhood_t<some_message> cmd) {
    id_ = so_5::extra::revocable_timer::send_periodic(mbox, 10s, 5s, std::move(cmd));
}

// Resend an existing message to the direct mbox of an agent:
void some_agent::on_some_message(mhood_t<some_message> cmd) {
    id_ = so_5::extra::revocable_timer::send_periodic(*this, 10s, 5s, std::move(cmd));
}

// Resend an existing message to a mchain:
void some_agent::on_some_message(mhood_t<some_message> cmd) {
    id_ = so_5::extra::revocable_timer::send_periodic(ch, 10s, 5s, std::move(cmd));
}

Some Techical Details

Envelope

A message/signal that sent via so_5::extra::revocable_timer::send_*() functions is enveloped into a special envelope with atomic revocation flag inside. A smart pointer to that envelope is stored inside timer_id_t object. That smart pointer is released when message is revoked (by any way).

Revocation means setting a special flag value inside the envelope. The envelope checks that flag in access_hook() method and provides the access to enveloped message/signal only if that flag is not set.

Message Limits

Revocation of message/signal doesn't affect message limits. It means that actual delivery of message/signal from timer to receiver's queue increments message counter, but revoke() doesn't decrement it. Message counter will be decremented only on extraction of envelope with message from an event queue.

Revocation Is Impossible If Message Was Transformed

If an instance of message is transformed during delivery (as part of limit_then_transform overlimit reaction or because message was collected by collecting mbox) then transformed message can't be revoked. Transformed message regarded as a new one, that has no relation to original revocable message.

Clone this wiki locally