【问题标题】:Standalone ASIO library has different behaviour on OSX and Linux systems after write error写入错误后,独立 ASIO 库在 OSX 和 Linux 系统上的行为不同
【发布时间】:2020-03-02 19:30:42
【问题描述】:

我注意到在 OSX 上的 asio::async_write 函数总是调用处理程序回调。但是在 linux (Ubuntu 18.04) 上,在 async_write 操作完成并出现错误 3 次后(由对等或断开管道重置连接)处理程序回调在下一次调用 async_write 后不再调用。

请看代码示例:

    asio::io_service ioService;

    asio::ip::tcp::resolver resolver(ioService);

    // ---- Initialize server -----
    auto acceptor = make_unique<asio::ip::tcp::acceptor>(ioService,
        resolver.resolve(asio::ip::tcp::resolver::query(asio::ip::tcp::v4(), "localhost", "12345"))->endpoint());;
    asio::ip::tcp::socket serverSocket(ioService);
    std::promise<void> connectedPromise;
    std::promise<void> disconnectedPromise;
    std::vector<uint8_t> readBuffer(1);
    acceptor->async_accept(serverSocket, [&](asio::error_code errorCode) {
        std::cout << "Socket accepted!" << std::endl;
        connectedPromise.set_value();
        serverSocket.async_read_some(asio::buffer(readBuffer), [&](asio::error_code errorCode, std::size_t length) {
            if (errorCode) {
                std::cout << "Read error: " << errorCode.message() << std::endl;
                disconnectedPromise.set_value();
            }
        });
    });

    // ----- Initialize client --------
    asio::ip::tcp::socket clientSocket(ioService);
    asio::connect(clientSocket, resolver.resolve({asio::ip::tcp::v4(), "localhost", "12345"}));

    // ----- Start io service loop
    std::thread mainLoop([&]() {
        ioService.run();
    });

    connectedPromise.get_future().get(); // Wait until connected

    // ----- Perform 10 async_write operations with 100 ms delay --------

    std::promise<void> done;
    std::atomic<int> writesCount{0};
    std::vector<uint8_t> writeBuffer(1);

    std::function<void (const asio::error_code&, std::size_t)> writeHandler = [&](const asio::error_code& errorCode, std::size_t) -> void {
        if (errorCode) {
            std::cout << errorCode.message() << std::endl;
        }
        if (++writesCount < 10) {
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
            asio::async_write(serverSocket, asio::buffer(writeBuffer), writeHandler);
        } else {
            done.set_value();
        }
    };

    asio::async_write(serverSocket, asio::buffer(writeBuffer), writeHandler);

    clientSocket.close(); // Perform disconnect from client side
    disconnectedPromise.get_future().get(); // Wait until disconnected

    std::cout << "Waiting for all operations complete" << std::endl;
    done.get_future().get(); // Wait until all 10 async_write operations complete
    std::cout << "All operations complete" << std::endl;

    ioService.stop();
    mainLoop.join();

OSX 上的输出:

Socket accepted!
Broken pipe
Read error: Connection reset by peer
Broken pipe
Waiting for all operations complete
Broken pipe
Broken pipe
Broken pipe
Broken pipe
Broken pipe
Broken pipe
Broken pipe
All operations complete

在 Ubuntu 18.04 上的输出:

Socket accepted!
Read error: End of file
Connection reset by peer
Waiting for all operations complete
Broken pipe
Broken pipe

Linux 版本在 done.get_future().get() 行挂起,因为在几次 Broken pipe 错误后未调用 async_write 完成处理程序。我希望任何 async_write 操作都应该导致处理程序调用,而不管 OSX 版本中的套接字状态如何。 是linux版本的bug吗?

Asio 版本:1.14.0(独立)

【问题讨论】:

  • 我没有找到解决方案。最后我只是在写处理程序中处理错误代码并关闭套接字并在出现错误时停止发送数据。

标签: c++ sockets networking boost-asio asio


【解决方案1】:

您在ioService.run() 上存在条件竞赛。

reference 声明:

run() 函数一直阻塞,直到 所有工作完成 并且 没有 要分派更多处理程序

如果服务因缺少处理程序而停止工作,您必须在 ioService 上调用 reset()

run() 函数的正常退出意味着 io_context 对象已停止(stopped() 函数返回 true)。随后的 对 run()、run_one()、poll() 或 poll_one() 的调用将返回 除非事先调用了 restart(),否则立即立即执行。

下图显示了条件竞争发生的位置:

main thread                                   background thread

[1] async_accept

[2] ioService.run()
                                              [3] handler for async_accept is called 
                                                  connectedPromise.set_value();
                                              [4] async_read_some 

[5] connectedPromise.get_future().get();

---> now here is a problem 
                   What [6.a] or [6.b] will be called as first?

[6.a] async_write which can push 
      a new handler to be processed      
                                       or 
                                          [6.b] handler for async_read_some 
                                               if this handler was called,
                                               ioService::run() ends, and you have to call reset on 
                                               it to accept new incoming handlers

(方括号内为按时间顺序排列的所有步骤)

在你的情况下 6.b 发生了。 async_read_some 的处理程序首先被调用,并且在此处理程序中您不会启动任何新的处理程序。结果,ioService::run() 停止,async_write 的处理程序将不会被调用。

尝试使用executor_work_guard 来防止ioService::run() 在没有要调度的处理程序时停止。

【讨论】:

  • 实际上没有竞争条件。因为没有人向套接字发送数据。这就是为什么 async_read_some 的处理程序只能在主线程调用clientSocket.close() 后调用错误(文件结束)。当它发生时, async_write 已经被调用。我以防万一添加了executor_work_guard 并得到了相同的结果。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-22
  • 1970-01-01
  • 2015-12-03
  • 2017-04-15
  • 2015-07-27
  • 2014-01-25
相关资源
最近更新 更多