How to safely cancel Boost ASIO asynchronous receive operation?

Everything that I read in the Boost ASIO docs, and here, in StackOverflow, I can stop the operation async_acceptby calling closethe acceptor on the socket. However, I get an intermittent error not_socketin the handler async_acceptwhen I try to do this. Am I doing something wrong or does Boost ASIO not support this?

(Related questions: here and here .)

(Note: I am running on Windows 7 and using the Visual Studio 2015 compiler.)

The main problem I am facing is the race condition between the operation async_acceptaccepting the incoming connection and my call close. This happens even when using a string, explicit or implicit.

Please note that my call async_acceptis done strictly before my call close. I will conclude that the race condition is between my call closeand the code under the hood in Boost ASIO, which accepts an incoming connection.

I have included code demonstrating the problem. The program repeatedly creates an acceptor, connects to it and immediately closes the acceptor. He expects the operation to async_acceptbe successfully completed or canceled. Any other error leads to the interruption of the program, which I see intermittently.

For synchronization, the program uses an explicit chain. Nevertheless, the call is not closesynchronized with the effect of the operation async_accept, so sometimes the acceptor closes before it accepts the incoming connection, sometimes it closes later, and sometimes it is not a problem.

Here is the code:

#include <algorithm>
#include <boost/asio.hpp>
#include <cstdlib>
#include <future>
#include <iostream>
#include <memory>
#include <thread>

int main()
{
  boost::asio::io_service ios;
  auto work = std::make_unique<boost::asio::io_service::work>(ios);

  const auto ios_runner = [&ios]()
  {
    boost::system::error_code ec;
    ios.run(ec);
    if (ec)
    {
      std::cerr << "io_service runner failed: " << ec.message() << '\n';
      abort();
    }
  };

  auto thread = std::thread{ios_runner};

  const auto make_acceptor = [&ios]()
  {
    boost::asio::ip::tcp::resolver resolver{ios};
    boost::asio::ip::tcp::resolver::query query{
      "localhost",
      "",
      boost::asio::ip::resolver_query_base::passive |
      boost::asio::ip::resolver_query_base::address_configured};
    const auto itr = std::find_if(
      resolver.resolve(query),
      boost::asio::ip::tcp::resolver::iterator{},
      [](const boost::asio::ip::tcp::endpoint& ep) { return true; });
    assert(itr != boost::asio::ip::tcp::resolver::iterator{});
    return boost::asio::ip::tcp::acceptor{ios, *itr};
  };

  for (auto i = 0; i < 1000; ++i)
  {
    auto acceptor = make_acceptor();
    const auto saddr = acceptor.local_endpoint();

    boost::asio::io_service::strand strand{ios};
    boost::asio::ip::tcp::socket server_conn{ios};

    // Start accepting.
    std::promise<void> accept_promise;
    strand.post(
      [&]()
    {
      acceptor.async_accept(
        server_conn,
        strand.wrap(
          [&](const boost::system::error_code& ec)
          {
            accept_promise.set_value();
            if (ec.category() == boost::asio::error::get_system_category()
              && ec.value() == boost::asio::error::operation_aborted)
              return;
            if (ec)
            {
              std::cerr << "async_accept failed (" << i << "): " << ec.message() << '\n';
              abort();
            }
          }));
    });

    // Connect to the acceptor.
    std::promise<void> connect_promise;
    strand.post(
      [&]()
    {
      boost::asio::ip::tcp::socket client_conn{ios};
      {
        boost::system::error_code ec;
        client_conn.connect(saddr, ec);
        if (ec)
        {
          std::cerr << "connect failed: " << ec.message() << '\n';
          abort();
        }
        connect_promise.set_value();
      }
    });
    connect_promise.get_future().get();   // wait for connect to finish

    // Close the acceptor.
    std::promise<void> stop_promise;
    strand.post([&acceptor, &stop_promise]()
    {
      acceptor.close();
      stop_promise.set_value();
    });
    stop_promise.get_future().get();   // wait for close to finish
    accept_promise.get_future().get(); // wait for async_accept to finish
  }

  work.reset();
  thread.join();
}

:

async_accept failed (5): An operation was attempted on something that is not a socket

, .

# 1: Tanner Sansbury std::promise async_accept. , .

# 2: not_socket setsockopt, call_setsockopt, socket_ops::setsockopt boost\asio\detail\impl\socket_ops.ipp (Boost 1.59). :

socket_ops::setsockopt(new_socket, state,
  SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT,
  &update_ctx_param, sizeof(SOCKET), ec);

Microsoft setsockopt SO_UPDATE_ACCEPT_CONTEXT:

.

, , , - , . , Windows close , async_accept.

, - , , .

+4
1

async_accept. , async_accept ​​ . acceptor.close().

undefined. async_accept peer:

, . , , , .

, server_conn for. , async_accept , server_conn . server_conn :

  • a std::future accept std::promise,
  • server_conn accept
+4

Source: https://habr.com/ru/post/1611864/


All Articles