I think I'm facing a boost::asio threadsafety issue. Let me explain:
I'm using Boost 1.68 with G++ 4.8.4 for Ubuntu 14.04
I'm writing a C++ code to make a MJPEG server.
Here are some samples of my code, how it works is pretty simple:
(1) Filling-in the HTTP header
(2) Calling doWrite with HandleMJPEG as callback
(3) The doWrite calls async_write with HandleMJPEG as callback
(4) The HandleMJPEG :
(4.1) adds the --jpegBoundary to the stream
(4.2) callbacks doWrite which calls HandleMJPEG again (4.3)
void Connection::main()
{
streamInitReply(_reply); // (1)
doWrite(
std::bind(&net::Reply::toBuffers, _reply),
std::bind(&Connection::handleMJPEG, this),
"'MJPEG Stream init header'"); // (2)
}
void streamInitReply(net::Reply& rep)
{
rep.status = net::Reply::Status::ok;
rep.headers.clear();
rep.content.clear();
rep.headers.push_back(net::Header("Connection", "close"));
rep.headers.push_back(net::Header("Max-Age", "0"));
rep.headers.push_back(net::Header("Expires", "0"));
rep.headers.push_back(net::Header("Cache-Control", "no-cache"));
rep.headers.push_back(net::Header("Pragma", "no-cache"));
rep.headers.push_back(
net::Header(
"Content-Type",
(boost::format("multipart/x-mixed-replace; boundary=%1%") % jpegBoundary).str()
)
);
void Connection::handleMJPEG()
{
// Write Boundaries, then write the actual image buffer
// then recall handleMJPEG() to loop and keep writing incoming image
streamBoundariesReply(_reply); // (4.1)
auto self(shared_from_this());
doWrite( // (4.2)
std::bind(&net::Reply::contentToBuffers, _reply),
[this, self](){
imageReply(_server.getImage(), _reply);
doWrite(
std::bind(&net::Reply::toBuffersWithoutStatus, _reply),
std::bind(&Connection::handleMJPEG, this), // 4.3
"'MJPEG Image'"
);
},
"'MJPEG Boundary'"
);
}
void Connection::doWrite(
std::function<std::vector<boost::asio::const_buffer>()> toBuffer,
std::function<void()> callbackOnSuccess,
const std::string& logInfo)
{
auto self(shared_from_this());
boost::asio::async_write(
_socket,
toBuffer(),
boost::asio::bind_executor(strand_, [this, self, logInfo, callbackOnSuccess](const boost::system::error_code& ec, std::size_t)
{
if(ec != boost::system::errc::success)
{
doStop();
}
else
{
callbackOnSuccess(); // (3)
}
}
));
}
The problem is the following: this code works like 90% of the time. Sometime when I want to see the stream on a browser (Firefox 66.0.3 for Linux), instead of displaying the images there is a "download" popup.
When I investigate the packages, it seems like there are some header missing or misspelled:
This is why is suspect a threadsafe issue in boost asio, but I dont have a clue where to look at...
You have dangling reference. Your problem is std::bind
.
Brief introduction: when you have a class Foo
which has member function bar
:
class Foo {
void bar() {}
};
Foo f;
and you call std::bind(&Foo::bar, f)
, std::bind
returns you a functor which stores f
as A COPY.
Now lets return to your code:
doWrite(
std::bind(&net::Reply::toBuffers, _reply), // <---- [1]
std::bind(&Connection::handleMJPEG, this),
"'MJPEG Stream init header'");
in [1] you call bind
which creates functor that stores copy of _reply
.
In doWrite
:
void Connection::doWrite(
std::function<std::vector<boost::asio::const_buffer>()> toBuffer, // [2]
std::function<void()> callbackOnSuccess,
const std::string& logInfo)
{
auto self(shared_from_this());
boost::asio::async_write(
_socket,
toBuffer(), // [3]
boost::asio::bind_executor( ....
...
));
// [4]
}
in [2] toBuffer
is a wrapper for functor returned from std::bind
. toBuffer
is local variable. In line [3] you call toBuffer()
which returns const-buffers for a copy of _reply
- it doesn't refer to original _reply
! async_write
returns immediately [4]. doWrite
ends and toBuffer
is destroyed whereas somewhere in Boost-lib mechanism task initiated by async_write
works and it is accessing const-buffers returned from toBuffer
- and here you have dangling reference.
You can pass _reply
by pointer when calling bind
- in this way you will refer to the same one _reply
instance:
std::bind(&net::Reply::toBuffers, &_reply),