SO 5.7 InDepth Stop Guards - Stiffstream/sobjectizer GitHub Wiki

The Problem

Sometimes an agent can want to prevent the shutdown of SObjectizer for some time. For example if the agent is caching some data in memory and it is necessary to store that data to the disk when the application finishes its work. But there is a problem: if someone calls so_5::environment_t::stop() method then there was no possibility to start some rather long operation like data saving.

There was just one solution for that problem in versions prior to 5.5.19.2: an application-wise policy to not to call so_5::environment_t::stop() directly. Some shutdown-coordinator can be used instead. For example: an agent-coordinator which receives shutdown-requests, makes shutdown-announces, collects shutdown-acks and performs actual shutdown.

This scheme is rather simple but is has a major drawback: every agent in an application must follow this scheme. It is very hard to integrate several different agents libraries into one application if they uses different shutdown prevention schemes.

The Solution

Since v.5.5.19.2 SObjectizer provides the basic building blocks for uniform shutdown prevention scheme: stop_guards.

Stop_guard is an object which implements so_5::stop_guard_t interface. A stop_guard must be set into SObjectizer Environment by method so_5::environment_t::setup_stop_guard(). When someone starts the shutdown by calling so_5::environment_t().stop() SObjectizer Environment calls so_5::stop_guard_t::stop() method for every installed stop_guard and waits until all stop_guards will be removed.

An user then must remove all installed stop_guards. When all installed stop_guards are removed SObjectizer Environment starts the shutdown operation. At this point it is impossible to prevent the shutdown (just like in previous versions of SObjectizer).

An Example

The is a very simple example of preventing SObjectizer's shutdown until all cached data will be stored:

// An agent which caches data and needs some form of shutdown prevention.
class cache_agent : public so_5::agent_t
{
    // A stop guard type for that agent.
    class shutdown_guard final : public so_5::stop_guard_t
    {
    public:
        // A signal to be sent when shutdown is requested.
        struct shutdown_requested final : public so_5::signal_t {};

        // Constructor receives direct mbox of cache_agent.
        shutdown_agent(so_5::mbox_t cache) : m_cache(std::move(cache)) {}

        void stop() noexcept override
        {
            // Inform cache_agent about shutdown request.
            so_5::send<shutdown_requested>(m_cache);
        }
    private:
        const so_5::mbox_t m_cache;
    };
    
    // A pointer to cache's stop_guard.
    const so_5::stop_guard_shptr_t m_shutdown_guard;
    ...
public:
    cache(context_t ctx, ...)
        : so_5::agent_t(std::move(ctx))
        , m_shutdown_guard(std::make_shared<shutdown_guard>(so_direct_mbox()))
        ...
    {
        // Shutdown guard must be installed into SObjectizer Environment.
        so_environment().setup_stop_guard(m_shutdown_guard);
        ...
    }

    void so_define_agent() override
    {
        // Cache agent must be subscribed to shutdown request.
        so_subscribe_self().event(&cache_agent::on_shutdown_request);
        ...
    }
private:
    void on_shutdown_request(mhood_t<shutdown_guard::shutdown_requested>)
    {
        // Shutdown has been requested. Cache data must be stored to disk.
        ... // Initiate a data storing process.
    }

    void on_data_store_completed(mhood_t<store_completed>)
    {
        // All data stored. It is time to enable the shutdown operation.
        // Just remove out stop_guard.
        so_environment().remove_stop_guard(m_shutdown_guard);
    }
    ...
};

Some Technical Notes

Stop_guards Can't Be Installed If stop() Is Already Called

It is possible to install stop_guards only before the first call to so_5::environment_t::stop() method. If so_5::environment_t::stop() is already called all subsequent calls to so_5::environment_t::setup_stop_guard() will fail.

By default method setup_stop_guard() throws an exception if stop() is already called. But if such policy is not appropriate it can be changed by using second argument to setup_stop_guard():

const auto result = so_environment().setup_stop_guard(
        my_guard,
        so_5::stop_guard_t::what_if_stop_in_progress_t::return_negative_result );
if(so_5::stop_guard_t::setup_result_t::ok != result)
    ... // Handle setup error here.

Why stop_guard_t::stop() is noexcept?

Method so_5::stop_guard_t::stop() shouldn't throw. It is because there is no way to rollback shutdown-specific actions which were done before an exception. It means that shutdown preparation procedure can be broken without a chance to recover.

This is the main reason why so_5::stop_guard_t::stop() is marked as noexcept: it shouldn't throw. If there can be some exceptions inside user's stop_guard stop() method which do not prevent shutdown operation they must be caught and handled inside that stop() method. Otherwise the only way is to terminate the current application.