Skip to content

so5extra 1.5 Asio OneThread Dispatcher

eao197 edited this page Dec 29, 2021 · 3 revisions

Purpose

The purpose of asio_one_thread dispatcher is to provide a possibility to handle IO-events from Asio and SO-events from SObjectizer on the context of the Asio's io_context. It means that SObjectizer-agents can perform IO-operations during ordinary processing of SObjectizer-messages. For example:

class request_sender : public so_5::agent_t
{
    asio::io_context & io_ctx_;
public:
    request_sender(context_t ctx, asio::io_context & io_svc)
        : so_5::agent_t(std::move(ctx)), io_ctx_(io_svc)
    {...}
    
    ...
private:
    void on_perform_request(mhood_t<perform_request> req)
    {
        using asio::ip::tcp;
        tcp::socket s(io_ctx_);
        tcp::resolver resolver(io_ctx_);
        asio::connect(s, resolver.resolve(req->host(), req->port()));
        
        asio::write(s, asio::buffer(req->payload()));
        
        std::array<char, 1024> reply_buf;
        const auto n = asio::read(s, asio::buffer(reply_buf));
        so_5::send<request_result>(req->reply_to(), reply_buf.data(), n);
    }
};

Working Principle

An instance of asio_one_thread dispatcher starts one worker thread and calls asio::io_context::run() on it. Then the dispatcher dispatches event handlers of all agents bound to the dispatcher via asio::post(). It means that Asio's io_context handles the agent's events just like any other Asio's events (like results of async IO-operations).

The asio_one_thread closes and joins this worker thread when the dispatcher is no more used (as well as result of SObjectizer Environment's shutdown).

How To Use

A header file so_5_extra/disp/asio_one_thread/pub.hpp must be included. The main SObjectizer's header file so_5/pub.hpp can also be necessary:

// Definition of asio_one_thread dispatcher.
#include <so_5_extra/disp/asio_one_thread/pub.hpp>

// Main SObjectizer header.
#include <so_5/pub.hpp>

Then disp_params_t instance must be prepared. For example:

namespace asio_disp = so_5::extra::disp::asio_one_thread;
asio_disp::disp_params_t params;
// Dispatcher must use its own io_context object.
params.use_own_io_context();

Then an instance of dispatcher must be created:

auto disp = asio_disp::make_dispatcher(
    // SObjectizer Environment to work in.
    env,
    // Name for data source.
    "my_asio_disp",
    // Parameters for new dispatcher.
    std::move(params) );

Then binders for agents can be obtained via dispatcher_handle_t::binder() method:

env.introduce_coop([&](so_5::coop_t & coop) {
    // Create agent and add it to the dispatcher.
    coop.make_agent_with_binder<request_sender>(
         disp.binder(),
         disp.io_context());
});

There Is No Need To Specify A Strand Object

Unlike asio_thread_pool dispatcher there is no need to specify a strand object for agents bound to asio_one_thread dispatcher. It's because those agents will be invoked on the context of just one worker thread.

Own Or External Asio's io_context

asio_one_thread can use its own copy of Asio's io_context. This must be specified by disp_params_t::use_own_io_context method:

so_5::extra::disp::asio_one_thread::disp_params_t params;
params.use_own_io_context(); // New instance of io_context will be created dynamically here.

This io_context instance will be destroyed with the instance of asio_one_thread dispatcher.

But asio_one_thread can also use an io_context created by someone else. For example:

int main()
{
    // An io_context which will live to the end of the whole application.
    asio::io_context io_svc;
    ...
    // Start of SObjectizer Environment.
    so_5::launch([&](so_5::environment_t & env) {
        // Create an asio_thread_pool dispatcher.
        so_5::extra::disp::asio_one_thread::disp_params params;
        params.use_external_io_context(io_svc);
        auto disp = so_5::extra::disp::asio_one_thread::make_dispatcher(
            env, "my_asio_disp", std::move(params));
        ...
    });
    ...
}

In such case asio_one_thread won't control lifetime of io_context instance. But asio_one_thread will call io_context::run() and io_context::stop() methods.

Custom Traits

asio_one_thread is implemented by template-classes which behaviour is controlled by specific traits. By default default_traits_t is used as traits type. It means that a call:

auto disp = so_5::extra::disp::asio_one_thread::make_dispatcher(...);

is equivalent to that call:

auto disp = so_5::extra::disp::asio_one_thread::make_dispatcher<
        so_5::extra::disp::asio_one_thread::default_traits_t>(...);

The type so_5::extra::disp::asio_one_thread::default_traits_t is empty at the moment. It wasn't empty in so5extra-1.4, but its content was removed during the development of v.1.5.0. Because default_traits_t is empty and the implementation of asio_one_thread dispatcher doesn't take anything from it, there is no sense to specify a custom traits type (at the current moment).

Despite the fact that the type so_5::extra::disp::asio_one_thread::default_traits_t it empty it is kept here to have a possibility to extend it in future versions.

Custom Thread Type

If a user wants to use custom thread type with asio_one_thread dispatcher then he/she has to use the standard SObjectizer's mechanism with abstract_work_thread_t/abstract_work_thread_factory_t interfaces. That mechanism was introduced in SObjectizer-5.7.3 and so5exta-1.5 utilizes it.

Clone this wiki locally