Martijn Otto
Martijn Otto

Reputation: 938

Boost asio handler that does not keep the io_service running

I want to add a signal handler to my boost io_service, allowing the application to shut down cleanly when the user presses Ctrl-C. This is of course easily done by stopping the loop, something like this:

boost::asio::io_service service;
boost::asio::signal_set signals{ service, SIGINT, SIGTERM };

signals.async_wait(std::bind(&boost::asio::io_service::stop, &service));

This stops the loop normally, allowing the destructors to do their routine clean-up behaviour.

The problem is, once the application runs out of work it does not stop because the signal handler still has a handler registered and thus the io_service never stops running.

I have not found a clean way around this. I could of course do the signal handling myself and then just stop the loop, but this kind of defeats the idea of using boost (portability).

Upvotes: 2

Views: 1685

Answers (2)

Martijn Otto
Martijn Otto

Reputation: 938

I am probably going to hell for this, but I found a workaround to get a handler that doesn't coun't towards the number of running handlers. It seriously abuses both the work_guard boost provides, calls destructors by hand and misuses placement new, but it works.

#pragma once

#include <boost/asio/io_service.hpp>
#include <utility>
#include <memory>

template <typename HANDLER>
class unwork
{
    public:
        unwork(boost::asio::io_service &service, HANDLER &&handler) :
            _work_guard(std::make_unique<boost::asio::io_service::work>(service)),
            _handler(std::forward<HANDLER>(handler))
        {
            // wait for the handler to be installed
            service.post([work_guard = _work_guard.operator->()]() {
                // remove the work guard and the handler that has now been installed
                work_guard->~work();
                work_guard->~work();
            });
        }

        unwork(const unwork &that) :
            unwork(that._work_guard->get_io_service(), that._handler)
        {}

        unwork(unwork &&that) :
            _work_guard(std::move(that._work_guard)),
            _handler(std::move(that._handler))
        {}

        ~unwork()
        {
            // was the work guard not moved out?
            if (_work_guard) {
                // add the work guard reference and the handler reference again
                new (_work_guard.operator->()) boost::asio::io_service::work{ _work_guard->get_io_service() };
                new (_work_guard.operator->()) boost::asio::io_service::work{ _work_guard->get_io_service() };
            }
        }

        template <class ...Arguments>
        auto operator()(Arguments ...parameters)
        {
            return _handler(std::forward<Arguments>(parameters)...);
        }
    private:
        std::unique_ptr<boost::asio::io_service::work>  _work_guard;
        HANDLER                                         _handler;
};

// maker function, for c++ < c++17
template <typename HANDLER>
unwork<HANDLER> make_unwork(boost::asio::io_service &service, HANDLER &&handler)
{
    // create the new unwork wrapper
    return { service, std::forward<HANDLER>(handler) };
}

It is used by wrapping your handler in a make_unwork() call if you are using c++14. In c++17 the constructor can be used directly.

Upvotes: 1

kenba
kenba

Reputation: 4549

In the following code, http_server has a "listening socket" to accept multiple connections. The listening socket constantly runs async_accept so the io_service never runs out of work. The http_server.shutdown() function closes the listening socket and all open connections, so the io_service has no more work and stops running:

void handle_stop(ASIO_ERROR_CODE const&, // error,
                 int, // signal_number,
                 http_server_type& http_server)
{
  std::cout << "Shutting down" << std::endl;
  http_server.shutdown();
}

...

ASIO::io_service io_service;
http_server_type http_server(io_service);

...


// The signal set is used to register termination notifications
ASIO::signal_set signals_(io_service);
signals_.add(SIGINT);
signals_.add(SIGTERM);
#if defined(SIGQUIT)
signals_.add(SIGQUIT);
#endif // #if defined(SIGQUIT)

// register the handle_stop callback
signals_.async_wait([&http_server]
  (ASIO_ERROR_CODE const& error, int signal_number)
{ handle_stop(error, signal_number, http_server); });

...

io_service.run();
std::cout << "io_service.run complete, shutdown successful" << std::endl;

This method also works for thread pools, see:thread_pool_http_server.cpp

Upvotes: 2

Related Questions