Reputation: 938
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
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
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