Search code examples
c++boostthread-safetyboost-asiomjpeg

boost::asio random header missing when using a C++ MJPEG streaming server


I think I'm facing a boost::asio threadsafety issue. Let me explain:

Context:

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:

Code Explanations

(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)

Actual code

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)
            }
        }
    ));
}

TL;DR

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:

1/ When everything is ok

here when everything is OK

2/ When it's not OK

here when it goes wrong

or here also

This is why is suspect a threadsafe issue in boost asio, but I dont have a clue where to look at...


Solution

  • 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),