Boost::Asio - How to Interrupt a Blocked Tcp Server Thread

Boost::asio - how to interrupt a blocked tcp server thread?

In short, there are two options:

  • Change code to be asynchronous (acceptor::async_accept() and async_read), run within the event loop via io_service::run(), and cancel via io_service::stop().
  • Force blocking calls to interrupt with lower level mechanics, such as signals.

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. When io_service::stop() is invoked, it will try to cause all threads blocked on io_service::run() to return as soon as possible; it will not interrupt synchronous operations, such as acceptor::accept() and socket::receive(), even if the synchronous operations are invoked within the event loop. It is important to note that io_service::stop() is a non-blocking call, so synchronization with threads that were blocked on io_service::run() must use another mechanic, such as thread::join().

Here is an example that will run for 10 seconds and listens to port 8080:

#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>
#include <iostream>

void StartAccept( boost::asio::ip::tcp::acceptor& );

void ServerThreadFunc( boost::asio::io_service& io_service )
{
using boost::asio::ip::tcp;
tcp::acceptor acceptor( io_service, tcp::endpoint( tcp::v4(), 8080 ) );

// Add a job to start accepting connections.
StartAccept( acceptor );

// Process event loop.
io_service.run();

std::cout << "Server thread exiting." << std::endl;
}

void HandleAccept( const boost::system::error_code& error,
boost::shared_ptr< boost::asio::ip::tcp::socket > socket,
boost::asio::ip::tcp::acceptor& acceptor )
{
// If there was an error, then do not add any more jobs to the service.
if ( error )
{
std::cout << "Error accepting connection: " << error.message()
<< std::endl;
return;
}

// Otherwise, the socket is good to use.
std::cout << "Doing things with socket..." << std::endl;

// Perform async operations on the socket.

// Done using the socket, so start accepting another connection. This
// will add a job to the service, preventing io_service::run() from
// returning.
std::cout << "Done using socket, ready for another connection."
<< std::endl;
StartAccept( acceptor );
};

void StartAccept( boost::asio::ip::tcp::acceptor& acceptor )
{
using boost::asio::ip::tcp;
boost::shared_ptr< tcp::socket > socket(
new tcp::socket( acceptor.get_io_service() ) );

// Add an accept call to the service. This will prevent io_service::run()
// from returning.
std::cout << "Waiting on connection" << std::endl;
acceptor.async_accept( *socket,
boost::bind( HandleAccept,
boost::asio::placeholders::error,
socket,
boost::ref( acceptor ) ) );
}

int main()
{
using boost::asio::ip::tcp;

// Create io service.
boost::asio::io_service io_service;

// Create server thread that will start accepting connections.
boost::thread server_thread( ServerThreadFunc, boost::ref( io_service ) );

// Sleep for 10 seconds, then shutdown the server.
std::cout << "Stopping service in 10 seconds..." << std::endl;
boost::this_thread::sleep( boost::posix_time::seconds( 10 ) );
std::cout << "Stopping service now!" << std::endl;

// Stopping the io_service is a non-blocking call. The threads that are
// blocked on io_service::run() will try to return as soon as possible, but
// they may still be in the middle of a handler. Thus, perform a join on
// the server thread to guarantee a block occurs.
io_service.stop();

std::cout << "Waiting on server thread..." << std::endl;
server_thread.join();
std::cout << "Done waiting on server thread." << std::endl;

return 0;
}

While running, I opened two connections. Here is the output:

Stopping service in 10 seconds...
Waiting on connection
Doing things with socket...
Done using socket, ready for another connection.
Waiting on connection
Doing things with socket...
Done using socket, ready for another connection.
Waiting on connection
Stopping service now!
Waiting on server thread...
Server thread exiting.
Done waiting on server thread.

Thread-safely closing a boost::asio::ip::tcp::socket being used synchronously

Having spent some time looking into this there is only 1 thread safe manner in which this can be achieved: by sending a message to the socket (on a thread not waiting on accept()) telling the thread to close the socket and the acceptor. By doing this the socket and acceptor can be wholly owned by a single thread.

As pointed out separately, io_service is only of use for asynchronous operations.

boost asio write operation on client is blocked when server down

There's nothing wrong with your client code, it is behaving correctly as a TCP socket implements reliable in-order delivery of the byte stream. Your client does not realize the server is unresponsive because that's not such a simple task, there are many entities between your client and server applications.

You may wish to enable TCP keep alive. Though be careful as it too is not a silver bullet, it merely indicates the TCP stack on both ends is alive and well; which says nothing about the application availability. The various timeous are also somewhat tricky to configure for all environments.

The most flexible option is to implement a heartbeat protocol in your client and server, which can be fully tailored to your application's performance requirements.

[boost.asio]closing tcp::socket or tcp::acceptor in different thread from the I/O thread

The documentation states that tcp::socket is not thread safe for shared objects.

Don't count on it seemingly working as a guarantee that it will always work.

Moreover, closing a socket from another thread, at the socket layer, is not a portable way of getting the blocking thread to unblock.

Here's what I would suggest:

  • Use the asynchronous APIs for asio.
  • Protect the socket with a mutex to prevent concurrent access or use an asio strand to serialize the access to it.

Quoting the author on a similar question:

Actually...

In practice, it will probably work on the platforms asio currently
supports**. However, I have deliberately specified the interface such
that it is not thread safe. This is to permit implementations to
store additional state in the socket object without needing explicit
synchronisation.

If you want to run multiple concurrent operations on the same socket,
the safe, portable way is to use the asynchronous operations.

**Unless you make the socket non-blocking.

Cheers, Chris



Related Topics



Leave a reply



Submit