-
-
Save semmel/32a66cb9091a7f6749a3b02daae8e712 to your computer and use it in GitHub Desktop.
/* Just my current implementation of Sam Miller's answer on Stack Overflow | |
* @see https://stackoverflow.com/questions/7754695/boost-asio-async-write-how-to-not-interleaving-async-write-calls | |
*/ | |
/* | |
* File: NonInterleavingAsyncSocketWrite.hpp | |
* Author: Matthias Seemann <seemann@visisoft.de> | |
* @see https://stackoverflow.com/questions/7754695/boost-asio-async-write-how-to-not-interleaving-async-write-calls | |
* | |
* When using boost::asio::async_write: | |
* 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. | |
* see the code for a send queue: https://stackoverflow.com/questions/7754695/boost-asio-async-write-how-to-not-interleaving-async-write-calls | |
* see also https://stackoverflow.com/questions/12794107/why-do-i-need-strand-per-connection-when-using-boostasio/12801042#12801042 | |
* | |
* Created on September 7, 2018, 5:46 PM | |
*/ | |
#ifndef NONINTERLEAVINGASYNCSOCKETWRITE_HPP | |
#define NONINTERLEAVINGASYNCSOCKETWRITE_HPP | |
#include <boost/asio.hpp> | |
#include <deque> | |
#include <iostream> | |
#include <string> | |
#include <memory> | |
template <class SocketClass> | |
class NonInterleavingAsyncSocketWrite { | |
typedef std::deque<std::string> Outbox; | |
std::unique_ptr<SocketClass>& socketPtr; | |
boost::asio::io_context& ioContext; | |
boost::asio::io_context::strand strand; | |
Outbox outbox; | |
public: | |
NonInterleavingAsyncSocketWrite(boost::asio::io_context& ioContext_, std::unique_ptr<SocketClass>& socketPtr_) : | |
socketPtr(socketPtr_), | |
ioContext(ioContext_), | |
strand(ioContext_) | |
{} | |
void write(const std::string& message) { | |
boost::asio::post(strand, | |
[this, message]() { writeImpl(message); } | |
); | |
} | |
private: | |
void writeImpl(const std::string& message) { | |
outbox.push_back(message); | |
if (outbox.size() > 1) { | |
// outstanding async_write | |
return; | |
} | |
this->write(); | |
} | |
void write() { | |
const std::string message = outbox.front(); | |
if (!socketPtr || !socketPtr->is_open()) { | |
std::cerr << "Could not write to a socket which is not ready! Payload:'" << message << "'" << std::endl; | |
outbox.pop_front(); | |
return; | |
} | |
boost::asio::async_write( | |
*socketPtr, | |
boost::asio::buffer(message.c_str(), message.size()), | |
boost::asio::bind_executor( | |
strand, | |
[this](const boost::system::error_code &ec, std::size_t bytes_transferred) { | |
outbox.pop_front(); | |
if (ec) { | |
std::cerr << "could not write: " << boost::system::system_error(ec).what() << std::endl; | |
return; | |
} | |
if (!outbox.empty()) { | |
// more messages to send | |
this->write(); | |
} | |
} | |
) | |
); | |
} | |
}; | |
#endif /* NONINTERLEAVINGASYNCSOCKETWRITE_HPP */ |
@hytano Yes copying in line 47 seems to be an error. Good catch! 👍
And a typo as well since in line 16 it's called Outbox
. I don't know how I assembled that gist. I do post my slightly different production code as Revision 2. In my production code that line is changed as I indeed take a reference with const std::string message = outbox.front();
Thanks, feedback much appreciated!
Great code! What does the // outstanding async_write
comment on line 53 stands for?
What does the // outstanding async_write comment on line 53 stands for?
I have no clue. I haven't done much async C++ since then. Sorry.
Great code! What does the
// outstanding async_write
comment on line 53 stands for?
The continuation of the async_write
will call write()
in line 84 again if the outbox is NOT empty so there is no need to kick off another async_write. This does the trick preventing interleaving writes.
Is there a typo in line 47 (like a missing
&
for taking a reference tooutbox[0]
) or why do you create a copy of the message to be send?With a copy I think there is a possible error in the code.
I checked with the asio documentation and when I understand it correctly: Neither does
boost::asio::buffer(...)
take ownership of the underlying memory nor doesboost::asio::async_write(...)
take ownership of the buffer (and by that of the underlying memory).async_write
expects the caller to ensure that the underlying memory is valid until the completion handler is called. Butmessage
will go out of scope, freeing the memory, right after the call toasync_write
.