Search code examples
c++boostboost-asio

boost::asio - initiating strand as a class member fails


My code crashed when trying to initiate the strand member with adding another io_context

I had a class with io_context and a strand. The existing io_context is a reference, it is received in the constructor. I tried to create a new strand with a new io_context:

Class members:

boost::asio::io_context& io_context_;
boost::asio::io_context io_context_2;
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
boost::asio::strand<boost::asio::io_context::executor_type> strand_2;

Constructor:

Server(
    boost::asio::io_context& ioc,
    : io_context_(ioc),
      io_context_2(),
      strand_(io_context_.get_executor()),
      strand_2(io_context_2.get_executor()),
       {
       }

I wanted to have both strands with different io_context, but because of the crash I was trying to test, and this also doesn't work:

Server(
    boost::asio::io_context& ioc,
    : io_context_(ioc),
      io_context_2(),
      strand_(io_context_.get_executor()),
      strand_2(io_context_.get_executor()),
       {
       }

But it does:

Server(
    boost::asio::io_context& ioc,
    : io_context_(ioc),
      //io_context_2(),
      strand_(io_context_.get_executor()),
      strand_2(io_context_.get_executor()),
       {
       }

The crash is in the line of initiating strand_2, it seems like io_context_2 fails to initiate when looking at the crash dump Unhandled exception at 0x00007FFC8BD7FAAD (ntdll.dll) in server.exe.9124.dmp: 0xC0000005: Access violation reading location 0xFFFFFFFFFFFFFFFF.

in win_mutex.hpp - win_mutex::lock


Solution

  • The code as described is fine:

    Live On Coliru

    #include <boost/asio.hpp>
    
    namespace asio = boost::asio;
    
    struct Server {
        using executor = asio::io_context::executor_type;
        asio::io_context&      io_context_;
        asio::io_context       io_context_2;
        asio::strand<executor> strand_;
        asio::strand<executor> strand_2;
    
        Server(asio::io_context& ioc)
            : io_context_(ioc)
            , io_context_2()
            , strand_(io_context_.get_executor())
            , strand_2(io_context_2.get_executor()) {}
    };
    
    int main() {
        asio::io_context ioc;
    
        Server s(ioc);
    
        ioc.run();
    }
    

    Note how it runs cleanly with ASan and UBsan.

    Guessing The Problem

    Because the code is broken up in fragments, with many syntax errors and also probable typos (the first constructor initializer list should probably say strand_2(io_context_2.get_executor())), I'm pretty confident in saying that your real code looks different.

    And from that I can basically guess what the problem is:

    enter image description here

    You can also see this Live On Coliru

    In case the problem is subtle to spot, you will see that the compiler actually tells you immediately if you enable minimal warnings:

    g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp -fsanitize=address,undefined && ./a.out
    main.cpp: In constructor 'Server::Server(boost::asio::io_context&)':
    main.cpp:10:28: warning: 'Server::strand_' will be initialized after [-Wreorder]
       10 |     asio::strand<executor> strand_;
          |                            ^~~~~~~
    main.cpp:8:28: warning:   'boost::asio::strand<boost::asio::io_context::basic_executor_type<std::allocator<void>, 0> > Server::strand_2' [-Wreorder]
        8 |     asio::strand<executor> strand_2;
          |                            ^~~~~~~~
    main.cpp:12:5: warning:   when initialized here [-Wreorder]
       12 |     Server(asio::io_context& ioc)
    

    The key thing to understand is that fields are initialized in the order in which they are declared. This is especially sensible with default member initializers: https://en.cppreference.com/w/cpp/language/constructor#Initialization_order

    So, enable warnings, and read them!