Liviu Stancu
Liviu Stancu

Reputation: 45

Can a io_context::strand guarantee the order between an async_* completion handler and custom functor?

I have this situation where I need to have an async_read operation "prepared" for reading before I call a custom function which is sending something on the counter-part of the websocket. I have the completion handler of the async_read wrapped in the same strand to which I post my functor.

The problem is that sometimes on::read is not called, so basically I think that the writeFromCounterpart() is called before the ws_1 was in "reading" state.

My understanding is that strand is guaranteeing the order between completion handlers, but I don't understand if guarantees that the async_* operation is "ready" (running in its thread and reading) before continuing with other operation from the strand FIFO.

Full code bellow: When running it, most of the time I've seen the following output:

on_read called
Run 2 Handlers

I was able to see the following also a few times (~1 in 20 in stress condition),

on_write called
Run 1 Handlers

I never seen both on_write and on_read

Code:

#include <boost/asio.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/core.hpp>
#include <string>

using tcp = boost::asio::ip::tcp;               // from <boost/asio/ip/tcp.hpp>
namespace websocket = boost::beast::websocket;  // from <boost/beast/websocket.hpp>

boost::beast::multi_buffer buffer;
boost::asio::io_context ioc_1;
boost::asio::io_context ioc_2;
websocket::stream<tcp::socket> ws_1(ioc_1);
websocket::stream<tcp::socket> ws_2(ioc_2);


void on_write(boost::system::error_code, std::size_t) {
  std::cout << "on_write called" << std::endl << std::flush;
}

void writeFromCounterpart() {
  ws_2.async_write(boost::asio::buffer(std::string("Hello")), on_write);
}

void on_read(boost::system::error_code, std::size_t) {
  std::cout << "on_read called" <<std::endl << std::flush;
}

int main() {
  std::thread t([](){
    auto const address = boost::asio::ip::make_address("127.0.0.1");
    tcp::acceptor acceptor{ioc_2, {address, 30000}};
    tcp::socket socket{ioc_2};

    acceptor.accept(socket);
    websocket::stream<tcp::socket> ws{std::move(socket)};
    ws.accept();
    ws_2 = std::move(ws);
    ioc_2.run();
  });
  t.detach();

  // allow acceptor to accept
  std::this_thread::sleep_for(std::chrono::milliseconds(200));

  tcp::resolver resolver_(ioc_1);
  boost::asio::io_context::strand strand_(ioc_1);
  auto const results = resolver_.resolve("127.0.0.1", "30000");
  boost::asio::connect(ws_1.next_layer(), results.begin(), results.end());
  ws_1.handshake("127.0.0.1", "/");

  ws_1.async_read(buffer, strand_.wrap(on_read));
  strand_.post(writeFromCounterpart);
  auto handlers = ioc_1.run_for(std::chrono::milliseconds(5000));
  std::cout << "Run " + std::to_string(handlers) + " Handlers" << std::endl << std::flush;
}

Upvotes: 0

Views: 553

Answers (1)

sehe
sehe

Reputation: 393537

The short answer is: yes.

The documentation for it is here:

The problem is that sometimes when I run this flow, on::read is not called, so basically I think that the SendSomethingOnTheWs is called before the ws_ was in "reading" state.

Why would that matter? Streaming sockets (ws or otherwise) behave like streams. If the socket was open the data will just be buffered.

Upvotes: 1

Related Questions