I'm working on a multithreaded application in which one thread acts as a tcp server which receives commands from a client. The thread uses a Boost socket and acceptor to wait for a client to connect, receives a command from the client, passes the command to the rest of the application, then waits again. Here's the code:
void ServerThreadFunc()
{
using boost::asio::ip::tcp;
boost::asio::io_service io_service;
tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), port_no));
for (;;)
{
// listen for command connection
tcp::socket socket(io_service);
acceptor.accept(socket);
// connected; receive command
boost::array<char,256> msg_buf;
socket.receive(boost::asio::buffer(msg_buf));
// do something with received bytes here
}
}
This thread spends most of its time blocked on the call to acceptor.accept()
. At the moment, the thread only gets terminated when the application exits. Unfortunately, this causes a crash after main() returns - I believe because the thread tries to access the app's logging singleton after the singleton has been destroyed. (It was like that when I got here, honest guv.)
How can I shut this thread down cleanly when it's time for the application to exit? I've read that a blocking accept() call on a raw socket can be interrupted by closing the socket from another thread, but this doesn't appear to work on a Boost socket. I've tried converting the server logic to asynchronous i/o using the Boost asynchronous tcp echo server example, but that just seems to exchange a blocking call to acceptor::accept()
for a blocking call to io_service::run()
, so I'm left with the same problem: a blocked call which I can't interrupt. Any ideas?
Simply call shutdown with native handle and the SHUT_RD option, to cancel the existing receive(accept) operation.
If it comes to it, you could open a temporary client connection to it on localhost - that will wake it up. You could even send it a special message so that you can shut down your server from the pub - there should be an app for that:)
In short, there are two options:
acceptor::async_accept()
andasync_read
), run within the event loop viaio_service::run()
, and cancel viaio_service::stop()
.I would recommend the first option, as it is more likely to be the portable and easier to maintain. The important concept to understand is that the
io_service::run()
only blocks as long as there is pending work. Whenio_service::stop()
is invoked, it will try to cause all threads blocked onio_service::run()
to return as soon as possible; it will not interrupt synchronous operations, such asacceptor::accept()
andsocket::receive()
, even if the synchronous operations are invoked within the event loop. It is important to note thatio_service::stop()
is a non-blocking call, so synchronization with threads that were blocked onio_service::run()
must use another mechanic, such asthread::join()
.Here is an example that will run for 10 seconds and listens to port 8080:
While running, I opened two connections. Here is the output:
When you receive an event that it's time to exit, you can call
acceptor.cancel()
, which will cancel the pending accept (with an error code ofoperation_canceled
). On some systems, you might also have toclose()
the acceptor as well to be safe.