Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to explicitly destroy all handlers pending on a given boost::asio::io_context?

Tags:

c++

boost-asio

According to my knowledge and as far as I checked the boost::asio documentation and source code there is no way to destroy explicitly all pending handlers on a given io_context aside from destroying the context itself?

I need to be able, if possible, to stop the io_context, destroy the pending handlers on the io_context, then do some other things and finally destroy all io objects (timers, pipes, etc) associated with the given io_context and the io_context itself.

I know that I can use work_guard::reset and let all pending handlers to run and then the io_context will stop by itself, but the problem is that many of the handlers may produce (post/defer/etc) new pending handlers, etc i.e. each such handler will need to be guarded with something like 'if stopped'.

I think that the io_context::shutdown does exactly this but there is no way, aside from inheritance maybe, to call explicitly the shutdown function because it's not public.

Thanks.

like image 897
Pavel Vazharov Avatar asked Sep 03 '18 09:09

Pavel Vazharov


People also ask

What is Io_context?

The io_context class provides the core I/O functionality for users of the asynchronous I/O objects, including: boost::asio::ip::tcp::socket. boost::asio::ip::tcp::acceptor. boost::asio::ip::udp::socket. deadline_timer .

Does boost ASIO use Epoll?

Asio (besides cross-platform work) is, that on each platform, it uses most effective strategy ( epoll on Linux 2.6, kqueue on FreeBSD/MacOSX, Overlapped IO on MS Windows).

What is ASIO boost?

Boost. Asio is a cross-platform C++ library for network and low-level I/O programming that provides developers with a consistent asynchronous model using a modern C++ approach. Overview. An overview of the features included in Boost. Asio, plus rationale and design information.

What is boost ASIO post?

boost::asio::post takes any callable object. Requirements for such object you can find here. There are many ways to achive what you want: [1] lambda expressions boost::asio::post(tp, [i]{ printProduct(i); });


1 Answers

Trying your suggestion using the protected shutdown results in a segfault on my system. I think it's protected for a reason :)

Anyhow, it looks like a judicious combination of restart/stop/reset might do the job. It's weird that some of the handler queue apparently stays around UNLESS one does a (empty) run/run_one. In fact even a poll_one seems to suffice. So, by all means, include that.

Here's my test bed code, you might find it useful:

Live On Coliru

#include <boost/asio.hpp>
#include <iostream>
using namespace std::chrono_literals;

struct Handler {
    void operator()(boost::system::error_code ec) { std::cout << "Handler invoked: " << ec.message() << std::endl; }

    struct Instance { // logging only unique instance to avoid noise of moved handlers
        Instance()  { std::cout << "Created handler instance"   << std::endl; }
        ~Instance() { std::cout << "Destroyed handler instance" << std::endl; }
    };
    std::unique_ptr<Instance> _instance = std::make_unique<Instance>();
};

int main()
{
    struct Hack : boost::asio::io_context { 
        using boost::asio::io_context::shutdown;
    } io;
    auto work = make_work_guard(io);

    std::cout << " -- run" << std::endl;
    auto t = std::thread([&]{ io.run(); });

    {
        boost::asio::high_resolution_timer tim(io, 2s);
        tim.async_wait(Handler{});
        work.reset(); // no longer needed

        std::this_thread::sleep_for(500ms);

#if 1
        io.stop();
#else
        io.shutdown(); // segfaults
#endif
    }

    std::cout << " -- timer destructed" << std::endl;
    std::cout << " -- joining" << std::endl;
    t.join();

    std::cout << " -- empy run to flush handler queue" << std::endl;
    io.reset();
    //io.run();
    //io.run_one();
    io.poll_one();

    std::cout << " -- bye" << std::endl;
}

Prints

 -- run
Created handler instance
 -- timer destructed
 -- joining
 -- empy run to flush handler queue
Handler invoked: Operation canceled
Destroyed handler instance
 -- bye

UPDATE

Here's my best suggestion (apart from, I guess, not sharing io at all):

Live On Coliru

#include <boost/asio.hpp>
#include <iostream>
using namespace std::chrono_literals;

struct Handler {
    void operator()(boost::system::error_code ec) { std::cout << "Handler invoked: " << ec.message() << std::endl; }

    struct Instance { // logging only unique instance to avoid noise of moved handlers
        Instance()  { std::cout << "Created handler instance"   << std::endl; }
        ~Instance() { std::cout << "Destroyed handler instance" << std::endl; }
    };
    std::unique_ptr<Instance> _instance = std::make_unique<Instance>();
};

int main()
{
    std::unique_ptr<boost::asio::io_context> io;

    int i = 1;
    for (auto delay : { 1500ms, 500ms }) {
        std::cout << " ------------------- reinitialized -------------- \n";
        io = std::make_unique<boost::asio::io_context>();
        boost::asio::high_resolution_timer tim(*io, 1s);

        std::cout << i << " -- run" << std::endl;
        auto t = std::thread([&]{ io->run(); });

        tim.async_wait(Handler{});

        std::this_thread::sleep_for(delay);

        std::cout << i << " -- stop" << std::endl;
        io->stop();

        std::cout << i << " -- joining" << std::endl;
        t.join();

        std::cout << " ------------------- destruct ------------------- \n";
        io.reset();
    }

    std::cout << "Bye" << std::endl;
}

Prints

 ------------------- reinitialized -------------- 
1 -- run
Created handler instance
Handler invoked: Success
Destroyed handler instance
1 -- stop
1 -- joining
 ------------------- destruct ------------------- 
 ------------------- reinitialized -------------- 
1 -- run
Created handler instance
1 -- stop
1 -- joining
 ------------------- destruct ------------------- 
Destroyed handler instance
Bye
like image 200
sehe Avatar answered Nov 13 '22 11:11

sehe