I'm a Boost C++ newbie and, using it to write a Server-like application I am wondering if it is possible to concurrently use boost::asio::ip::tcp::socket::async_read_some(...) and boost::asio::ip::tcp::socket::write_some(...).
In my scenario a Connection object listens continuously via:
void Connection::doRead()
{
auto self(shared_from_this());
socket_.async_read_some(boost::asio::buffer(data_rx_, max_length),
[this, self](boost::system::error_code ec, std::size_t length)
{
if (!ec)
{
afterReading(length);
doRead();
}
});
}
At the same time, an asynchronous function callback (running in a different thread) could invoke socket_.read_write while Connection is "reading".
I've read various Boost::Asio docs but this scenario was never covered.
Is this allowed? What should be done to avoid it if not?
EDIT:
I have read, as suggested, various answers including this: Why do I need strand per connection when using boost::asio?, but still can't find an answer as it is not specified wether mixing async and sync (called by different threads) calls is safe or not.
It is not safe.
This answer goes into details, but to summarize, it is not safe for multiple threads to make calls concurrently, with the exception that multiple synchronous calls may be safe if supported by the OS. If one thread initiates an asynchronous operation on a socket while another thread is performing a synchronous operation on the same socket, then it falls into the former case and is not safe.
The most elegant solution is to avoid mixing synchronous and asynchronous operations.
If using synchronous operations and the OS does not support concurrent synchronous operations, then perform explicit locking, such as by using mutexes.
If using asynchronous operations and multiple threads, then use an explicit strand to prevent concurrent invocation of handlers.
Also, one can synchronously block waiting for an asynchronous operation to complete by leveraging Boost.Asio's support for futures. This approach can allow for one to expose a synchronous blocking API to a user, but use asynchronous operations internally. The initiating operation (async_*) would need to be invoked within a strand, and if the caller is not running within the context of the strand, then some form of synchronization will need to be used to allow the caller to wait for Asio to create the future object.
Related
I know that OpenSSL, boost asio SSL implementation is based on, doesn't allow concurrent SSL_read() and SSL_write() (i.e. SSL_read() and SSL_write() executed by different threads).
Is it safe to call boost asio async_read() and async_write() on SSL socket from the same thread?
Thanks
The requirement for boost::asio::ssl:::stream is for thread safety; it does not place a requirement as to which thread may initiate operations:
Distinct objects: Safe.
Shared objects: Unsafe. The application must also ensure that all asynchronous operations are performed within the same implicit or explicit strand.
If the application only has one thread processing the io_service, and async_read() and async_write() are initiated from within that thread, then it is safe, as the operation and completion handler(s) are running within an implicit strand.
On the other hand, if multiple threads are processing the io_service, then an explicit strand is necessary. The async_read() and async_write() operations need to be initiated from within a strand, and the completion handlers need to be wrapped by the same strand.
For more details on Boost.Asio's thread safety requirements, strands, and composed operations, consider reading this answer.
It is safe to call async_read() and async_write() on SSL socket from the same thread, but in general case it is not enough to avoid concurrency issues with ssl::stream. The actual requirement is provided in ssl::stream documentation:
Thread Safety (...) Shared objects: Unsafe. The application must also ensure that all asynchronous operations are performed within the same implicit or explicit strand.
Of course, the standard boost::asio requirement to ensure that:
no other read operations are performed until async_read handler gets
called, and
no other write operations are performed until async_write handler gets called.
must also be met.
Note, that it is allowed to schedule a read operation while a write operation is underway and conversely. Simultaneous asynchronous read and write operations are possible in ssl::stream thanks to handling OpenSSL's network needs asynchronously through BIO mechanism. SSL_read() and SSL_write() manifest their needs to communicate by returning SSL_ERROR_WANT_READ and SSL_ERROR_WANT_WRITE error codes. These error codes are used by ssl::stream implementation to schedule the network operations asynchronously. A read or write operation on ssl::stream may need multiple both read and write operations on the underlying network socket and multiple calls to SSL_read() / SSL_write(), which will be performed from asynchronous network operation completion handlers (particularly not from the original async_read/async_write call), this is why it is not enough to ensure that async_read and async_write are not called simultaneously, but a strand is needed.
It is safe. But simulateous 2 or more async_write-s on same socket are unsafe and will segfault often(at least for SSL case).
A way of doing this is to construct the ssl::stream using a strand as it's execution context. You can then perform async_read and async_write operations from that strand. Both of these operations can be scheduled at the same time.
You just have to make sure that the context the stream was constructed with and the context that async_read and async_write are called from are effectively an explicit or implicit strand.
Related question: boost asio ssl async_read + async_write within a strand is failing
I have been trying to understand the logic in boost's http server 3 example. The request in this example is read inside connection.cpp, in the start() method, which calls:
socket_.async_read_some(boost::asio::buffer(buffer_),
strand_.wrap(
boost::bind(&connection::handle_read, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
Note that the async_read_some method is documented to return immediately. Then inside the read handler (connection::handle_read()), we may again call async_read_some if parse returns boost::indeterminate. What benefit does this provide over socket_.read_some(buffer), given that we already know we are working in a separate thread. The reason I ask is I want to change the message parsing a bit to call read_some on demand, but the method I have in mind won't work with an async read.
Also, a related question: is there any difference between
async_read_some()
and
boost::thread th([](){ ret = read_some(); handle_read(ret) });?
Boost.Asio's HTTP Server 3's example is coded in a way that it remains agnostic to the size of the thread pool. As such, there is no guarantee that work will be done in separate threads. Nevertheless, the benefit in being agnostic is that it scales better with more connections. For example, consider the C10K problem that examines 10000 clients simultaneously connected. A synchronous solution may run into various performance issues or resource limitations with 10000 clients. Additionally, the asynchronous nature helps insulate the program from behavior changes in the network. For instance, consider a synchronous program that has 3 clients and 2 threads, but 2 of the clients have high latency due to an increase in noise on the network. The 3rd client could be inadvertently affected if both of the threads are blocked waiting for data from the other clients.
If there is a low and finite number of connections, with each connection serviced by a thread, then the performance difference between a synchronous and asynchronous server may be minimal. When possible, it is often advised to avoid mixing asynchronous and synchronous programming, as it can turn a complex solution into a complicated one. Furthermore, most synchronous algorithms can be written asynchronously.
There are two major differences between an asynchronous operation and a synchronous operation (even those running within a dedicated thread):
Thread safety. As noted in the documentation:
In general, it is safe to make concurrent use of distinct objects, but unsafe to make concurrent use of a single object.
Therefore, asynchronous and synchronous operations cannot safely be initiated while a synchronous operation is in progress, even if the operation is invoked within its own thread. This may be minimal in a half duplex protocol, but should be considered with full duplex protocols.
Ability to cancel an operation. As noted in this answer, synchronous operations cannot be cancelled through the cancel() member functions Boost.Asio provides. Instead, the application may need to use lower level mechanics, such as signals.
I'm developing a network server based on Boost::Asio.
I have a boost::thread_group of IO worker threads which I use to call boost::asio::io_service::run( )
When network activity occurs ASIO uses one of these worker threads to process the activity (eg. Accept or Receive).
My application then does some work, possibly some calculation, possibly some other IO (via boost) and possibly some database activity.
I'd like to know what the implications are of doing said work within these threads. Specifically:
Does carrying out ( possibly significant work ) on the IO threads cause
the io_service any grief?
And less specifically: any other issues I should be thinking about.
Does carrying out ( possibly significant work ) on the IO threads
cause the io_service any grief?
It really depends what you mean by grief. Performing long running operations in a handler invoked by an io_service can block additional handlers from being invoked by the io_service. Consider the simplest example with a single thread invoking io_service::run(). If the handler invoked by an asynchronous operation, say async_read() then performs some database operations that could be long running, additional outstanding asynchronous operations will not have their handlers invoked until the long running operation is complete and the handler returns control to the io_service.
This is typically mitigated by invoking io_service::run() from multiple threads, and using a strand to ensure exclusive access to handlers that used shared data. Though if all of your handlers perform some long running operations, you might need to investigate alternative designs.
I would like to have a way to add async tasks form multiple threads and execute them sequentially in a c++ boost::asio application.
Update: I would like to make a server-to-server communication with only one persistent socket between them and I need to sequence the multiple requests trough it. It needs to keep the incoming request in a queue, fire the top one / wait for it response and pick up the next. I'm trying to avoid using zeromq because it needs a dedicated thread.
Update2: Ok, Here is with what I ended up: The concurrent worker threads are "queued" for the use of the server-to-server socket with a simple mutex. The communication is blocking write/wait for response/read then release the mutex. Simple isn't it :)
From the ASIO documentation:
Asynchronous completion handlers will only be called from threads that
are currently calling io_service::run().
If you're already calling io_service::run() from multiple threads, you can wrap your async calls in an io_service::strand as described here.
Not sure if I understand you correctly either, but what's wrong with the approach in the client chat example? Messages are posted to the io_service thread, queued while a write is in progress and popped/sent in the write completion handler. If more messages were added in the meantime, the write handler launches the next async write.
Based on your comment to Sean, I also don't understand the benefit of having multiple threads calling io_service::run since you can only execute one async_write/async_read on one persistent socket at a time i.e. you can only call async_write again once the handler has returned? The number of calling threads might require you to lock the queue with a mutex though.
AFAICT the benefit of having multiple threads calling io_service::run is to increase the scalability of a server that is serving multiple requests simultaneously.
Trying to learn asio, and I'm following the examples from the website.
Why is io_service needed and what does it do exactly? Why do I need to send it to almost every other functions while performing asynchronous operations, why can't it "create" itself after the first "binding".
Asio's io_service is the facilitator for operating on asynchronous functions. Once an async operation is ready, it uses one of io_service's running threads to call you back. If no such thread exists it uses its own internal thread to call you.
Think of it as a queue containing operations. It guarantees you that those operations, when run, will only do so on the threads that called its run() or run_once() methods, or when dealing with sockets and async IO, its internal thread.
The reason you must pass it to everyone is basically that someone has to wait for async operations to be ready, and as stated in its own documentation io_service is ASIO's link to the Operating System's I/O service so it abstracts away the platform's own async notifiers, such as kqueue, /dev/pool/, epoll, and the methods to operate on those, such as select().
Primarily I end up using io_service to demultiplex callbacks from several parts of the system, and make sure they operate on the same thread, eliminating the need for explicit locking, since the operations are serialized. It is a very powerful idiom for asynchronous applications.
You can take a look at the core documentation to get a better feeling of why io_service is needed and what it does.