The documentation for boost::asio::ssl::stream states the following regarding thread safety:
Thread Safety
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 i compare this to the documentation for the boost::asio::ip::tcp::socket type, then the statement about strands is not included.
Questions
If access to the stream object is controlled by a mutex, making sure that only one thread operates on the ssl stream at a given time, what is the need for using an implicit/explicit strand?
Also, what does "asynchronous operations" mean in this context? Is the document referring to calls to for example boost::asio::async_read/boost::asio::async_read, or to the handler callbacks I pass to these operations?
If access to the stream object is controlled by a mutex, making sure that only one thread operates on the ssl stream at a given time, what is the need for using an implicit/explicit strand?
There is no need then. The mutex makes the operations serialize as on a "logical strand". Asio's strands are merely a mechanism to achieve such serialization without the explicit synchronization code in case you have more than one service running the io_service
Also, what does "asynchronous operations" mean in this context? Is the document referring to calls to for example boost::asio::async_read/boost::asio::async_read, or to the handler callbacks I pass to these operations?
Boost refers to it's implementation of those member functions/free functions indeed, because they operate on the service objects that aren't threadsafe. The completion handlers are your own concern: if you make them threadsafe then there is no more need for strands indeed. Be aware that you cannot start asynchronous operations directly from such "unserialized" completion handlers, which leads to code like:
void completionhandler(error_code const& ec) {
if (!ec) {
io_service_.post([] { boost::asio::async_...(...); });
// or:
strand_.post([] { boost::asio::async_...(...); });
}
}
This leverages the fact that the strand and io_service objects are threadsafe.
Related
I have a question regarding to the usage of strand in boost::asio framework.
The manuals refer the following
In the case of composed asynchronous operations, such as async_read()
or async_read_until(), if a completion handler goes through a strand,
then all intermediate handlers should also go through the same strand.
This is needed to ensure thread safe access for any objects that are
shared between the caller and the composed operation (in the case of
async_read() it's the socket, which the caller can close() to cancel
the operation). This is done by having hook functions for all
intermediate handlers which forward the calls to the customisable hook
associated with the final handler:
Let's say that we have the following example
Strand runs in a async read socket operation . Socket read the data and forwards them to a async writer socket. Two operation are in the same io_service. Is this write operation thread safe as well?Is is called implicity in the same strand? Or is it needed to explicitly call async_write in the strand
read_socket.async_read_some(my_buffer,
boost::asio::bind_executor(my_strand,
[](error_code ec, size_t length)
{
write_socket.async_write_some(boost::asio::buffer(data, size), handler);
}));
Is the async_write_some sequential executing in the following example or needs strand as well?
Yes, since you bound the completion handler to the strand executor (explicitly, as well), you know it will be invoked on the strand - which includes async_write_some.
Note you can also have an implicit default executor for the completion by constructing the socket on the strand:
tcp::socket read_socket { my_strand };
In that case you don't have to explicitly bind the handler to the strand:
read_socket.async_read_some( //
my_buffer, my_strand, [](error_code ec, size_t length) {
write_socket.async_write_some(asio::buffer(data, size), handler);
});
I prefer this style because it makes it much easier to write generic code which may or may not require strands.
Note that the quoted documentation has no relation to the question because none of the async operations are composed operations.
I am writing an application using boost.asio. I've an object of type boost::asio::ip::tcp::socket and (of course) I've boost::asio::io_context which run's function was called from only one thread. For writing data to the socket there are a couple of ways but currently I use socket's function async_write_some, something like the code below:
void tcp_connection::write(packet_ptr packet)
{
m_socket.async_write_some(boost::asio::buffer(packet->data(), packet->size()),
std::bind(&tcp_connection::on_write, this, std::placeholders::_1, std::placeholders::_2, packet));
}
There is another function in boost::asio namespace - async_write. And the documentation of async_write says:
This operation is implemented in terms of zero or more calls to the stream's async_write_some function, and is known as a composed operation. The program must ensure that the stream performs no other write operations (such as async_write, the stream's async_write_some function, or any other composed operations that perform writes) until this operation completes.
In async_write_some's documentation there is no such kind of 'caution'.
That's a little bit confusing to me and here I've got the following questions:
Is it safe to call async_write_some without waiting for the previous call to be finished? As far as I understood from boost's documentation I shouldn't do that with async_write, but what about async_write_some?
If yes, is the order in which the data is written to the socket the same as the functions were called? I mean if I called async_write_some(packet1) and async_write_some(packet2) - are the packets going to be written to the socket in the same order?
Which function I should use? What is the difference between them?
What is the reason that it's not safe to call async_write while the previous one hasn't finished yet?
no; the reason for that is probably documented with the underlying sockets API (BSD/WinSock).
not applicable. Note that the order in which handlers are invoked is guaranteed to match the order in which they were posted, so you could solve it using an async chain of async_write_some calls where the completion handler posts the next write. This is known as an implicit strand (see https://www.boost.org/doc/libs/master/doc/html/boost_asio/overview/core/async.html and Why do I need strand per connection when using boost::asio?).
99% of the time, use the free function. The difference is that it implements composed operation to send a "unit" of information, i.e. an entire buffer, message, or until a given completion condition is met.
async_write_some is the lowest-level building block, which doesn't even guarantee to write all of the data: remarks:
The write operation may not transmit all of the data to the peer.
Consider using the async_write function if you need to ensure that all
data is written before the asynchronous operation completes.
It's not unsafe¹ in the strictest sense. It just will not lead to correct results: this is because the order in which handlers are invoked leads to data being written to the socket in mixed-up order.
¹(unless you access the shared IO objects concurrently without synchronization)
I have to develop an asynchronous client that talks to a server. The client runs in a separate thread from the main application and just reads what the server sends using a callback chain. Each read handler registers the next one through a strand (it is a bit more complex since I use a class method as a callback so I need to bind *this to match the handler's signature):
_socketObject.async_read_some(
asio::buffer(_recv_buf.data(),_recv_buf.size()),
asio::bind_executor(_strand, std::bind(
&Connection::_handleRead, shared_from_this(),
std::placeholders::_1, std::placeholders::_2)));
To write to the server I'd like the main application to post (https://think-async.com/Asio/asio-1.16.1/doc/asio/reference/post/overload2.html) through the same strand a callback that performs the write to the server (this is to avoid concurrent access to the socket and some shared data).
The thing that I want to know is if it is sufficient to copy the strand object used in the client or it is necessary to keep a reference to the original. In the latter case I am concerned about the thread safety of the operation.
I'd like to avoid an explicit mutex on the strand object, if possible.
I use the header only version of the library (non-Boost).
Yes. See docs
Thread Safety
Distinct objects: Safe.
Shared objects: Safe.
Strands can be copied. In fact, you can create a new strand off another executor and if that was on a strand it will end up representing the same strand identity.
Additionally, a mutex on a strand couldn't possibly work because composed operations need to dispatch work on the thread, and they would not be aware of the need for locking.
In general locking is a no-no in async tasks: Strands: Use Threads Without Explicit Locking
It is obvious from the implementation that IO completion callbacks are invoked in the same order as the actual IO operations when running in a single thread mode, but I cannot find the respective part of the documentation confirming that. Is it written explicitly anywhere?
The documentation of all of the async_xxx methods on io-object classes have a passage like this:
Regardless of whether the asynchronous operation completes immediately or not, the handler will not be invoked from within this function. Invocation of the handler will be performed in a manner equivalent to using boost::asio::io_service::post().
Looking at the documentation of boost::asio::io_service::post()...
This function is used to ask the io_service to execute the given handler, but without allowing the io_service to call the handler from inside this function.
The io_service guarantees that the handler will only be called in a thread in which the run(), run_one(), poll() or poll_one() member functions is currently being invoked.
And that is the full extent of your guarantee.
If your code relies on the temporal ordering of asynchronous events, then it is not asynchronous code.
Even the documentation of run_one() make no guarantees about which handler it will dispatch:
The run_one() function blocks until one handler has been dispatched, or until the io_service has been stopped.
If you must sequence individual async operations (such as reads), then you are obliged to either:
initiate the second operation from the handler of the first, or
keep a flag set while an operations' handler is outstanding, and only initiate another operation when the flag is false.
async_write() is forbidden to be called concurrently from different threads. It sends data by chunks using async_write_some and such chunks can be interleaved. So it is up to the user to take care of not calling async_write() concurrently.
Is there a nicer solution than this pseudocode?
void send(shared_ptr<char> p) {
boost::mutex::scoped_lock lock(m_write_mutex);
async_write(p, handler);
}
I do not like the idea to block other threads for a quite long time (there are ~50Mb sends in my application).
May be something like that would work?
void handler(const boost::system::error_code& e) {
if(!e) {
bool empty = lockfree_pop_front(m_queue);
if(!empty) {
shared_ptr<char> p = lockfree_queue_get_first(m_queue);
async_write(p, handler);
}
}
}
void send(shared_ptr<char> p) {
bool q_was_empty = lockfree_queue_push_back(m_queue, p)
if(q_was_empty)
async_write(p, handler);
}
I'd prefer to find a ready-to-use cookbook recipe. Dealing with lock-free is not easy, a lot of subtle bugs can appear.
async_write() is forbidden to be
called concurrently from different
threads
This statement is not quite correct. Applications can freely invoke async_write concurrently, as long as they are on different socket objects.
Is there a nicer solution than this
pseudocode?
void send(shared_ptr<char> p) {
boost::mutex::scoped_lock lock(m_write_mutex);
async_write(p, handler);
}
This likely isn't accomplishing what you intend since async_write returns immediately. If you intend the mutex to be locked for the entire duration of the write operation, you will need to keep the scoped_lock in scope until the completion handler is invoked.
There are nicer solutions for this problem, the library has built-in support using the concept of a strand. It fits this scenario nicely.
A strand is defined as a strictly
sequential invocation of event
handlers (i.e. no concurrent
invocation). Use of strands allows
execution of code in a multithreaded
program without the need for explicit
locking (e.g. using mutexes).
Using an explicit strand here will ensure your handlers are only invoked by a single thread that has invoked io_service::run(). With your example, the m_queue member would be protected by a strand, ensuring atomic access to the outgoing message queue. After adding an entry to the queue, if the size is 1, it means no outstanding async_write operation is in progress and the application can initiate one wrapped through the strand. If the queue size is greater than 1, the application should wait for the async_write to complete. In the async_write completion handler, pop off an entry from the queue and handle any errors as necessary. If the queue is not empty, the completion handler should initiate another async_write from the front of the queue.
This is a much cleaner design that sprinkling mutexes in your classes since it uses the built-in Asio constructs as they are intended. This other answer I wrote has some code implementing this design.
We've solved this problem by having a seperate queue of data to be written held in our socket object. When the first piece of data to be written is "queued", we start an async_write(). In our async_write's completion handler, we start subsequent async_write operations if there is still data to be transmitted.