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
Related
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.
TLDR: Strands serialise resources shared across completion handlers: how does that prevent the ssl::stream implementation from concurrent access of the SSL context (used internally) for concurrent read/write requests (stream::ssl is not full duplex)? Remember, strands only serialise the completion handler invocation or the original queueing of the read/write requests. [Thanks to sehe for helping me express this better]
I've spent most of a day reading about ASIO, SSL and strands; mostly on stackoverflow (which has some VERY detailed and well expressed explanations, e.g. Why do I need strand per connection when using boost::asio?), and the Boost documentation; but one point remains unclear.
Obviously strands can serialise invocation of callbacks within the same strand, and so also serialise access to resources shared by those strands.
But it seems to me that the problem with boost::asio::ssl::stream isn't in the completion handler callbacks because it's not the callbacks that are operating concurrently on the SSL context, but the ssl::stream implementation that is.
I can't be confident that use of strands in calling async_read_some and async_write_some, or that use of strands for the completion handler, will prevent the io engine from operating on the SSL context at the same time in different threads.
Clearly strand use while calling async_read_some or async_write_some will mean that the read and write can't be queued at the same instant, but I don't see how that prevents the internal implementation from performing the read and write operations at the same time on different threads if the encapsulated tcp::socket becomes ready for read and write at the same time.
Comments at the end of the last answer to this question boost asio - SSL async_read and async_write from one thread claim that concurrent writes to ssl::stream could segfault rather than merely interleave, suggesting that the implementation is not taking the necessary locks to guard against concurrent access.
Unless the actual delayed socket write is bound to the thread/strand that queued it (which I can't see being true, or it would undermine the usefulness of worker threads), how can I be confident that it is possible to queue a read and a write on the same ssl::stream, or what that way could be?
Perhaps the async_write_some processes all of the data with the SSL context immediately, to produce encrypted data, and then becomes a plain socket write, and so then can't conflict with a read completion handler on the same strand, but it doesn't mean that it can't conflict with the internal implementations socket-read-and-decrypt before the completion handler gets queued on the strand. Never mind transparent SSL session re-negotiation that might happen...
I note from: Why do I need strand per connection when using boost::asio? "Composed operations are unique in that intermediate calls to the stream are invoked within the handler's strand, if one is present, instead of the strand in which the composed operation is initiated." but I'm not sure if what I am refering to are "intermediate calls to the stream". Does it mean: "any subsequent processing within that stream implementation"? I suspect not
And finally, for why-oh-why, why doesn't the ssl::stream implementation use a futex or other lock that is cheap when there is no conflict? If the strand rules (implicit or explicit) were followed, then the cost would be almost non-existent, but it would provide safety otherwise. I ask because I've just transitioned the propaganda of Sutter, Stroustrup and the rest, that C++ makes everything better and safer, to ssl::stream where it seems easy to follow certain spells but almost impossible to know if your code is actually safe.
The answer is that the boost ssl::stream implementation uses strands internally for SSL operations.
For example, the async_read_some() function creates an instance of openssl_operation and then calls strand_.post(boost::bind(&openssl_operation::start, op)).
[http://www.boost.org/doc/libs/1_57_0/boost/asio/ssl/old/detail/openssl_stream_service.hpp]
It seems reasonable to assume that all necessary internal ssl operations are performed on this internal strand, thus serialising access to the SSL context.
Q. but I'm not sure if what I am refering to are "intermediate calls to the stream". Does it mean: "any subsequent processing within that stream implementation"? I suspect not
The docs spell it out:
This operation is implemented in terms of zero or more calls to the stream's async_read_some function, and is known as a composed operation. The program must ensure that the stream performs no other read operations (such as async_read, the stream's async_read_some function, or any other composed operations that perform reads) until this operation completes. doc
And finally, for why-oh-why, why doesn't the ssl::stream implementation use a futex or other lock that is cheap when there is no conflict?
You can't hold a futex across async operations because any thread may execute completion handlers. So, you'd still need the strand here, making the futex redundant.
Comments at the end of the last answer to this question boost asio - SSL async_read and async_write from one thread claim that concurrent writes to ssl::stream could segfault rather than merely interleave, suggesting that the implementation is not taking the necessary locks to guard against concurrent access.
See previous entry. Don't forget about multiple service threads. Data races are Undefined Behaviour
TL;DR
Long story short: async programming is different. It is different for good reasons. You will have to adapt your thinking to it though.
Strands help the implementation by abstracting sequential execution over the async scheduler.
This makes it so that you don't have to know what the scheduling is, how many service threads are running etc.
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.
Is I am looking at writing a multithreaded tcp server using boost ASIO. I have read through the tutorials and had a look at some of the examples and just want to check that my understanding is correct.
The server will accept connections then service requests from multiple clients.
My understanding is as follows:
The server uses "a single io_service and a thread pool calling io_service::run()"
All threads call io_service::run().
The calls to io_service::run() are not within a strand, ergo completion handlers can run simultaneously.
When a request arrives one of the threads is chosen, its read handler will be called
Another request may arrive,starting the read handler on a second thread
When one of the threads has finished handling the request it calls async_write, from within a strand
Another thread also finishes processing its request, it also calls async_write, from within a strand
The writes to the io_service are serialised via the strand, ergo they are thread safe.
When the write operation completes the thread calls async_read()
This call is not protected by a strand and the thread will be used for handling requests
Is my understanding correct? Is this solution vulnerable to race conditions?
As Sam miller said, your assumptions are quite correct.
However I would like to point out an issue that you may have not spotted.
It is right that strands will serialize async_write(s) and therefore there will be thread safe.
But the issue is not here, async_write is by itself thread safe if not used on the same socket. And strands will not help here since you should not interleave async_write on the same socket.
Strands will not wait the previous async_write to finish before calling the next one. you will have to create a structure that async_write only if none is already in action on the socket.
If I use close and not cancel, there are some problems.
The close function can close the socket, and any outstanding asynchronous operations is stopped by returning boost::asio::error::operation_aborted error.
Why should I use cancel instead of close?
I worry if some asynchronous operations is executing, the cancel could not cancel it, yes?
Like asio::ip::tcp::resolve::cancel, I try many times to cancel the resolve_handler after calling async_resolve, but resolve_handler always returns with no boost::asio::error::operation_aborted error.
I think resolve_handler is being executed?
Yes?
Cancel is useful if you want to stop pending operations without closing down the socket.
Note that the Boost documentation recommends using close for greater portability (from doc page):
...
For portable cancellation, consider
using one of the following
alternatives:
Disable asio's I/O completion port
backend by defining
BOOST_ASIO_DISABLE_IOCP.
Use the
close() function to simultaneously
cancel the outstanding operations and
close the socket.
cancel won't close the socket, so use cancel if you intend to continue using the socket object. In particular, if you have code in asynchronous handler methods that references the socket's member functions, you may not want to close the socket until you are guaranteed that your currently executing asynchronous handlers have completed.
cancel doesn't guarantee anything about currently executing asynchronous handlers, it only guarantees (per the boost documentation) that "This function causes all outstanding asynchronous connect, send and receive operations to finish immediately" in the case of the socket::cancel() call, or "This function forces the completion of any pending asynchronous operations on the host resolver" in the case of the resolver::cancel() call. This "completion" means that boost will call your asynchronous handler method, it has no jurisdiction to inject any cancellation logic into your asynchronous handler (not to mention it doesn't know about the handler's implementation to begin with).
I would suggest adding your own logic into your asynchronous handler method to handle the case where the socket/resolver/etc. is canceled. If you are calling the cancel method, then you likely have the ability to communicate this cancellation to the asynchronous handler method.