title: boost::asio::strand fails to make async_write thread safe
body: <p>I have a websocket server that writes messages to a client using</p>
  
  <pre><code>ws_.async_write(
      boost::asio::buffer( msg, msg.length()),
      boost::asio::bind_executor(
          strand_,
          std::bind(
              &amp;cSession::on_write,
              shared_from_this(),
              std::placeholders::_1,
              std::placeholders::_2)));
  </code></pre>
  
  <p>strand_ is initialized in the CTOR <code>strand_(make_strand(ioc))</code>  The asio documentation states that this should make the writes thread safe.</p>
  
  <p>Running in two other threads, I have message generators that send a message every three seconds.  So every three seconds the websocket attempts to execute two async_write calls almost simultaneously.  After a several, or sometimes on the first, attempts, an assertion fails</p>
  
  <pre><code>Assertion failed!
      Program: C:\Users\James\code\echoer\bin\WProcessServer.exe
  File: C:\Users\James\code\boost\boost_1_70_0/boost/beast/websocket/detail/soft_mutex.hpp, Line 75
  
  Expression: id_ == T::id
  </code></pre>
  
  <p>which the source code says</p>
  
  <pre><code>    // If this assert goes off it means you are attempting to
      // simultaneously initiate more than one of same asynchronous
      // operation, which is not allowed. For example, you must wait
      // for an async_read to complete before performing another
      // async_read.
  </code></pre>
  
  <p>So my use of strand has not made the server thread safe.  Why not?</p>
  
  <p>Here is the code for a complete minimal program that demos the problem:</p>
  
  <pre><code>    #include &lt;iostream&gt;
      #include &lt;algorithm&gt;
      #include &lt;thread&gt;
  
      #include &lt;boost/beast/core.hpp&gt;
      #include &lt;boost/beast/websocket.hpp&gt;
      #include &lt;boost/asio/ip/tcp.hpp&gt;
      #include &lt;boost/asio/strand.hpp&gt;
      #include &lt;boost/asio/bind_executor.hpp&gt;
  
      using tcp = boost::asio::ip::tcp;               // from &lt;boost/asio/ip/tcp.hpp&gt;
  
      class cServer;
  
      // Report a failure
      void
      fail(boost::system::error_code ec, char const* what)
      {
          std::cerr &lt;&lt; what &lt;&lt; ": " &lt;&lt; ec.message() &lt;&lt; "\n";
      }
  
      /** Send messages at regular intervals to client from own thread
  
       Used to test the server is thread safe
      */
  
      class cMessenger
      {
      public:
          /** CTOR
              @param[in] io  io context
          */
          cMessenger(
              boost::asio::io_context&amp; ioc,
              cServer&amp; myServer,
              int id
          );
  
          /// Startup ( never returns - call in own thread )
          void Run();
  
          /// Schedule next message
          void Schedule();
  
          /// Send Message, then schedule next
          void onTimer();
  
      private:
          std::thread myThread;
          boost::asio::steady_timer myTimer;     /// timer controlling when massages are sent
          boost::asio::io_context&amp; myIOC;
          cServer&amp; myServer;
          int myID;
      };
  
      /// Websocket connection
      class cSession : public std::enable_shared_from_this&lt;cSession&gt;
      {
          /** The stream ( actually a TCP socket ) used to communicate with the client */
          boost::beast::websocket::stream&lt;tcp::socket&gt; ws_;
  
          /** The strand used to synchronize writes to the client
          Prevents a new write starting on the socket until previous write completes
          */
          boost::asio::strand&lt;
          boost::asio::io_context::executor_type&gt; strand_;
  
          /** Buffer storage for incoming messages from client */
          boost::beast::multi_buffer buffer_;
  
          cServer * myWebSocket;
  
      public:
          // Take ownership of the socket
          explicit
          cSession(
              boost::asio::io_context&amp; ioc,
              tcp::socket socket,
              cServer * webSocket )
              : ws_(std::move(socket))
              , strand_(make_strand(ioc))
              , myWebSocket( webSocket )
          {
          }
  
          /** Start the asynchronous operation */
          void run();
  
          /** Handle websocket handshake completion */
          void on_accept(boost::system::error_code ec);
  
          /** Wait for next message from client */
          void do_read();
  
          /** Handle reception of message from client */
          void on_read(
              boost::system::error_code ec,
              std::size_t bytes_transferred);
  
          /** Write message to connection that came from elsewhere */
          void Write( const std::string&amp; msg );
  
          /** Handle completion of write message from elsewhere */
          void on_write(
              boost::system::error_code ec,
              std::size_t bytes_transferred)
          {
              // Clear the buffer
              buffer_.consume(buffer_.size());
              //do_read();
          }
      };
      /// Accepts incoming connections and launches the sessions
      class cListener : public std::enable_shared_from_this&lt;cListener&gt;
      {
          boost::asio::io_context&amp; ioc;        // io context
          boost::asio::ip::tcp::tcp::acceptor acceptor_;
          boost::asio::ip::tcp::tcp::socket socket_;
          cServer * myServer;
  
      public:
          cListener(
              boost::asio::io_context&amp; ioc,
              boost::asio::ip::tcp::tcp::endpoint endpoint );
  
          void Set( cServer* server )
          {
              myServer = server;
          }
  
          /// Start accepting incoming connections
          void run()
          {
              if(! acceptor_.is_open())
                  return;
              do_accept();
          }
  
          /// wait for client connection request
          void do_accept();
  
          /// handle a client connection request
          void on_accept(boost::system::error_code ec);
      };
  
      /** A process Server */
      class cServer
      {
      public:
          /** CTOR
              @param[in] port to listen for client connections
  
              Runs in its own thread
              Starts listening on port for client connections
              Starts boost asio io_context
          */
          cServer(
              boost::asio::io_context&amp; ioc,
              const std::string&amp; port );
  
          /** Returns when thread ends */
          void Join();
  
          /** New connection to client */
          void Set( cSession * session );
  
          /** Client connection lost */
          void SessionClosed();
  
          /** Receive message from the client
              @param[in] msg
          */
          void ClientMsg( const std::string&amp; msg );
  
          /** Send message to client
              @param[in] msg
              @param[in] store true if message should be stored for client recconection, default true
          The message will be sent to client
  
          */
          void SendToClient(
              const std::string&amp; msg,
              bool store = true );
  
          /// Get IO Context
          boost::asio::io_context&amp; IOC()
          {
              return myIOC;
          }
  
      private:
          boost::asio::io_context&amp; myIOC;
          unsigned short myPort;
          std::thread myThread;
          std::shared_ptr&lt;cListener&gt; myListener;
          cSession * mySession;
  
          void Run();
      };
  
  
  
      cListener::cListener(
          boost::asio::io_context&amp; ioc_ref,
          tcp::endpoint endpoint )
          : ioc( ioc_ref )
          , acceptor_(ioc_ref)
          , socket_(ioc_ref)
      {
          boost::system::error_code ec;
  
          // Open the acceptor
          acceptor_.open(endpoint.protocol(), ec);
          if(ec)
          {
              fail(ec, "open");
              return;
          }
  
          // Allow address reuse
          acceptor_.set_option(boost::asio::socket_base::reuse_address(true));
          if(ec)
          {
              fail(ec, "set_option");
              return;
          }
  
          // Bind to the server address
          acceptor_.bind(endpoint, ec);
          if(ec)
          {
              fail(ec, "bind");
              return;
          }
  
          // Start listening for connections
          acceptor_.listen(
              boost::asio::socket_base::max_listen_connections, ec);
          if(ec)
          {
              fail(ec, "listen");
              return;
          }
      }
  
      void cListener::do_accept()
      {
          acceptor_.async_accept(
              socket_,
              std::bind(
                  &amp;cListener::on_accept,
                  shared_from_this(),
                  std::placeholders::_1));
      }
  
      void cListener::on_accept(boost::system::error_code ec)
      {
          if(ec)
          {
              fail(ec, "accept");
          }
          else
          {
              // Create the session
              auto s = std::make_shared&lt;cSession&gt;(
                           ioc,
                           std::move(socket_),
                           myServer );
  
              // run the session
              s-&gt;run();
          }
      }
  
      void cSession::run()
      {
          // Accept the websocket handshake
          ws_.async_accept(
              boost::asio::bind_executor(
                  strand_,
                  std::bind(
                      &amp;cSession::on_accept,
                      shared_from_this(),
                      std::placeholders::_1)));
      }
  
      void cSession::on_accept(boost::system::error_code ec)
      {
          if(ec)
              return fail(ec, "accept");
  
          // let websocket know connection is up and running
          myWebSocket-&gt;Set( this );
  
          // Wait for first message from client
          //do_read();
      }
  
      void cSession::do_read()
      {
          // Read a message into our buffer
          ws_.async_read(
              buffer_,
              boost::asio::bind_executor(
                  strand_,
                  std::bind(
                      &amp;cSession::on_read,
                      shared_from_this(),
                      std::placeholders::_1,
                      std::placeholders::_2)));
      }
  
      void cSession::on_read(
          boost::system::error_code ec,
          std::size_t bytes_transferred)
      {
          boost::ignore_unused(bytes_transferred);
  
          // This indicates that the session was closed
          if(ec == boost::beast::websocket::error::closed)
          {
              myWebSocket-&gt;SessionClosed();
              return;
          }
  
          if(ec)
          {
              myWebSocket-&gt;SessionClosed();
              return;
          }
  
  
          std::cout &lt;&lt; "rcvd from client " &lt;&lt; boost::beast::buffers_to_string(buffer_.data()) &lt;&lt; "\n";
  
          // ???
          ws_.text(ws_.got_text());
  
          // wait for next message
          do_read();
  
      }
  
      void cSession::Write(
          const std::string&amp; msg )
      {
          ws_.async_write(
              boost::asio::buffer( msg, msg.length()),
              boost::asio::bind_executor(
                  strand_,
                  std::bind(
                      &amp;cSession::on_write,
                      shared_from_this(),
                      std::placeholders::_1,
                      std::placeholders::_2)));
      }
  
      cServer::cServer(
          boost::asio::io_context&amp; ioc,
          const std::string&amp; port )
          : myIOC( ioc )
          , myPort( static_cast&lt;unsigned short&gt;(std::atoi(port.c_str())) )
          , mySession( 0 )
      {
          std::cout &lt;&lt; "Server starting...";
          myThread = std::thread(
                         &amp;cServer::Run,
                         this );
          std::cout &lt;&lt; "Server started\n";
      }
      void cServer::Run()
      {
          // Create and launch a listening port
          myListener = std::make_shared&lt;cListener&gt;(
                           myIOC,
                           tcp::endpoint
          {
              boost::asio::ip::make_address("0.0.0.0"),
              myPort
          } );
          myListener-&gt;Set( this );
          myListener-&gt;run();
  
          // start event manager
          myIOC.run();
      }
  
      void cServer::Join()
      {
          myThread.join();
      }
  
      void cServer::Set( cSession * session )
      {
          std::cout &lt;&lt; "New connection from client\n";
  
          mySession = session;
  
          mySession-&gt;do_read();
      }
  
      void cServer::SessionClosed()
      {
          std::cout &lt;&lt; "Client connection lost\n";
  
          mySession = 0;
  
          // listen for a reconnection
          myListener-&gt;do_accept();
      }
  
      void cServer::ClientMsg( const std::string&amp; msg )
      {
      }
  
      void cServer::SendToClient(
          const std::string&amp; msg,
          bool store )
      {
          //std::cout &lt;&lt; "SendToClient: " &lt;&lt; msg &lt;&lt; "\n";
          if( ! mySession )
          {
  
          }
          else
          {
              mySession-&gt;Write( msg );
          }
      }
  
      cMessenger::cMessenger(
          boost::asio::io_context&amp; ioc,
          cServer&amp; server,
          int id  )
          : myTimer( ioc )
          , myIOC( ioc )
          , myServer( server )
          , myID( id )
      {
          //std::cout &lt;&lt; "Messenger starting ...";
          myThread = std::thread(
                         &amp;cMessenger::Run,
                         this );
      }
      void cMessenger::Run()
      {
          std::cout &lt;&lt; "Messenger is running in its own thread\n";
          Schedule();
          myIOC.run();
      }
      void cMessenger::Schedule()
      {
          myTimer.expires_after( std::chrono::seconds(3) );
          myTimer.async_wait(std::bind(&amp;cMessenger::onTimer, this ));
      }
  
      void cMessenger::onTimer()
      {
          //std::cout &lt;&lt; " cMessenger::onTimer\n";
  
          myServer.SendToClient(
              "Hello World from thread " + std::to_string( myID ),
              false );
  
          Schedule();
      }
  
  
      int main(int argc, char* argv[])
      {
          boost::asio::io_context ioc( 3 );
  
          cServer Server(
              ioc,
              "8080"
          );
  
          cMessenger Messenger1(
              ioc,
              Server,
              1 );
  
          cMessenger Messenger2(
              ioc,
              Server,
              2 );
  
          Server.Join();
  
      }
  
  
  </code></pre>