Skip to content

so5extra 1.4 Shutdowner

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

The Problem

Sometimes it is not possible to shut down a big and complex application right now. A simple call to so_5::environment_t::stop() doesn't leave enough time to perform long shutdown activities. For example, some agent may require to dump cached data to disk. Or to commit or rollback a transaction in a database. Or to remove some files from the filesystem. Or to turn some equipment off...

If we simply call so_5::environment_t::stop() then SObjectizer deregisters all coops and it is impossible to send new messages to agents in deregistered coops. It means that there is no possibility to perform long-lasting operations which require message-based interaction between several agents when SObjectizer is shutting down.

Because of that, there is a need in some mechanism which allows the graceful shutdown of application before the start of SObjectizer's shutdown procedure.

Solutions

There are several solutions to that problem. One is based on SObjectizer' stop_guards and another is provided by so5extra.

SObjectizer's Stop Guards

Since v.5.5.19.2 SObjectizer provides stop_guards: so-5.5.19 Stop guards. They prevent the shutdown of SObjectizer until all installed stop_guards will be removed. It is a simple solution. There is no need to use something else except the SObjectizer itself. But this solution has some drawbacks:

  • it requires some work from a developer. It is necessary to define a class for stop guard (this class should implement so_5::stop_guard_t interface). It is necessary to create an instance of that class. It is necessary to install that instance into SObjectizer Environment. A shutdown signal must be transferred from that stop_guard to an applied agent. That stop_guard must be removed from SObjectizer Environment manually and so on;
  • there is no time limit for the shutdown operation. If some agent forgets to remove its stop_guard from SObjectizer Environment then shutdown won't be completed.

so5extra's Shutdowner

so5extra provides its own solution for the shutdown problem. This solution is based on SObjectizer's stop_guards, but:

  • it doesn't require the creation of stop_guard. It is only necessary to create a subscription for a message from a special mbox. While such subscriptions exist the shutdown can't be finished;
  • there is a maximum allowed time for the shutdown operation. If the shutdown is not finished in the specified time interval then the whole application will be terminated by calling std::abort().

It means that an agent in an application which requires some long-lasting actions during the shutdown operation must perform the following steps:

  • create a subscription to a special message for so5extra's mbox;
  • handle that message and do shutdown-related actions;
  • deregister its own coop (this will remove the subscription automatically) or manually destroy the subscription.

so5extra Shutdowner

Working Principle

so5extra's Shutdowner is implemented as SObjectizer's layer. It means that Shutdowner implements a special protocol which is used by SObjectizer for starting and stopping layers.

At the start, Shutdowner creates a special notification mbox and installs a special stop_guard.

When someone calls so_5::environment_t::stop() method that special stop_guard sends a message of type so_5::extra::shutdowner::shutdown_initiated_t into that notififcation mbox. Then Shutdowner starts the shutdown timer. When that timer expires Shutdowner terminates the application.

Shutdowner knows how many subscribers are here for notification mbox. If there are no subscribers then Shutdowner removes the stop_guard from SObjectizer Environment (and this allows the shutdown operation to complete). If there is any subscription then Shutdowner waits while all subscription will be removed. Or when the shutdown timer expires.

How to Use

Addition of Shutdowner into SObjectizer Environment

First of all an instance of Shutdowner must be created and passed to SObjectizer Environment. Usually it is done via so_5::environment_params_t before launching SObjectizer Environment:

#include <so_5_extra/shutdowner/shutdowner.hpp>

#include <so_5/all.hpp>

int main() {
    so_5::launch( [](so_5::environment_t & env) {
            ... // Some initial actions.
        },
        [](so_5::environment_params_t & params) {
            // Create and add Shutdowner as SObjectizer's layer.
            params.add_layer( so_5::extra::shutdowner::make_layer<>(
                // Maximum time for the shutdown.
                std::chrono::seconds(15)) );
            ... // Additional tuning.
        } );
}

If Shutdowner layer must be added to already running SObjectizer Environment it can be done via add_extra_layer() method of so_5::environment_t:

class transaction_manager : public so_5::agent_t {
public :
    transaction_manager(context_t ctx) : so_5::agent_t(std::move(ctx)) {
        // Add Shutdowner to already running SObjectizer.
        so_environment().add_extra_layer( so_5::extra::shutdowner::make_layer<>(
                std::chrono::seconds(15)) );
    }
};

Note: once Shutdowner layer is installed into SObjectizer Environment it can't be removed. It is the common property for all SObjectizer's layers. They can't be removed.

Subscription to Shutdowner' Notification Mbox

Every agent which needs a notification about the shutdown operation should create a subscription to message of type so_5::extra::shutdowner::shutdown_initiated_t from Shutdowner's notification mbox. That mbox can be obtained from Shutdowner layer.

For example:

class data_cache final : public so_5::agent_t {
public :
    data_cache(context_t ctx) : so_5::agent_t(std::move(ctx)) {
        // Get Shutdowner's notification mbox.
        const auto notify_mbox =
                so_5::extra::shutdowner::layer(so_environment()).notify_mbox();
        // Subscribe to that mbox.
        so_subscribe(notify_mbox).event( &data_cache::on_shutdown );
        ...
    }
    ...
private :
    void on_shutdown(mhood_t<so_5::extra::shutdowner::shutdown_initiated_t>) {
        ... // Do some shutdown-specific actions.
    }
};

Note: subscription will fail if the shutdown operation is in progress (it means that an attempt to make subscription after so_5::environment_t::stop() was called will lead to an exception).

Removing the Subscription

so5extra's Shutdowner allows the shutdown operation to be finished only when there are no more subscribers to notification mbox. It means that all subscription must be destroyed to allow SObjectizer to finish its work.

Usually the subscription to Shutdowner's notification mbox is destroyed automatically when a coop is deregistered. For example in this case there is no need to destroy such subscription manually:

class data_cache final : public so_5::agent_t {
public :
    data_cache(context_t ctx) : so_5::agent_t(std::move(ctx)) {
        // Get Shutdowner's notification mbox.
        const auto notify_mbox =
                so_5::extra::shutdowner::layer(so_environment()).notify_mbox();
        // Subscribe to that mbox.
        so_subscribe(notify_mbox).event( &data_cache::on_shutdown );
        ...
    }
    ...
private :
    void on_shutdown(mhood_t<so_5::extra::shutdowner::shutdown_initiated_t>) {
        store_data_to_disk();
        // Now the coop is no more needed.
        so_deregister_agent_coop_normally();
    }
};

But if a coop can't be deregistered the subscription must be dropped manually by using so_5::agent_t::so_drop_subscription() or so_5::agent_t::so_drop_subscription_for_all_states() method. For example:

class manager final : public so_5::agent_t {
public:
    ...
    virtual void so_define_agent() override {
        // Get the notification mbox.
        const auto notify_mbox =
            so_5::extra::shutdowner::layer(so_environment()).notify_mbox();
        // Make subscription in differen states...
        so_subscribe(notify_mbox).in(first_state).event(...);
        so_subscribe(notify_mbox).in(second_state).event(...);
        ...
    }
    ...
private:
    ...
    void on_shutdown_actions_completed() {
        // No more actions to do on shutdown.
        // Subscription can be dropped.
        so_drop_subscription_for_all_states<so_5::extra::shutdowner::shutdown_initiated_t>(
            so_5::extra::shutdowner::layer(so_environment()).notify_mbox()));
    }

make_layer Template Function

The make_layer function which creates a Shutdowner layer instance is a template function with the following format:

template< typename Lock_Type = std::mutex >
std::unique_ptr< ::so_5::extra::shutdowner::layer_t >
make_layer(
   std::chrono::steady_clock::duration shutdown_max_time );

It has a single template parameter: Lock_Type. This parameter specifies a type of lock object to protect Shutdowner's internals in a multithreaded application. By default, this type is std::mutex. But it can be changed to something that more appropriate for the user's environment.

For example a custom implementation of spin lock can be used as Lock_Type. Or it can so_5::null_mutex_t for single-threaded environments:

so_5::launch( [](so_5::environment_t & env) {...}
    [](so_5::environment_params_t & params) {
        // Launch SObjectizer in single-threaded mode without
        // any protection from access from several threads.
        params.infrastructure_factory(
            so_5::env_infrastructures::simple_not_mtsafe::factory() );
        // There is no need to protect Shutdowner in such environment.
        params.add_layer(
            so_5::extra::shutdowner::make_layer<so_5::null_mutex_t>(
                    std::chrono::seconds(15)) );
    } );
Clone this wiki locally