Boost Asio,异步 UDP 客户端 - 关机时崩溃

Boost Asio, async UDP client - crash on shutdown

本文关键字:关机 崩溃 客户端 UDP Asio 异步 Boost      更新时间:2023-10-16

我在应用程序中使用 UDP 客户端-服务器进行 IPC

它工作正常,但是当尝试关闭客户端时,会发生一些争用条件,从而导致应用程序崩溃或死锁。

UDP 客户端:

// IOServiceBase class contain boost::asio::io_service instance
// it is accessible by service() protected method
class AsyncUDPClient : public IOServiceBase
{
public:
    /// @brief Create a network client
    AsyncUDPClient(const std::string& host, const std::string port)
        : _host(host)
        , _port(port)
        , _reply()
        , _work(service())
        , _sock(service(), ba_ip::udp::endpoint(ba_ip::udp::v4(), 0)) {
        run();
    }
    /// @brief Start async packets processing
    void run(){
        std::thread t([&]{ service().run(); });
        t.detach();
    }
    /// @brief Async request to server
    void send(uint8_t* message, size_t length) {
        std::vector<uint8_t> packet(message, message + length);
        service().post(boost::bind(&AsyncUDPClient::do_send, this, packet));
    }
    /// @brief Cleanup io_service to dismiss already putted tasks
    ~AsyncUDPClient() {
        close();
        // trying to wait until service is stopped, but it does not help
        while (!service().stopped()){
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
    }
    /// @brief Cleanup io_service to dismiss already putted tasks
    void close(){
        std::thread t([&]{ service().stop(); });
        t.join();
    }
protected:
// send-response methods are pretty standard
private:
    std::string _host;
    std::string _port;
    std::array<uint8_t, max_length> _reply;
    ba::io_service::work _work;
    ba_ip::udp::socket _sock;
};

使用示例:

{
    AsyncUDPClient ipc(addr, port);
    ipc.send(&archive_data[0], archive_data.size());
    // it seems client is destroyed before some internal processing is finished?
}

行为不是确定性的,有时工作正常,有时崩溃,有时冻结。堆栈跟踪显示 boost.asio 内部某处的崩溃点

销毁AsyncUDPClient不会与运行io_service的线程正确同步。 这可能会导致在处理io_service的线程在其生存期结束后尝试与AsyncUDPClient及其io_service交互时调用未定义的行为。

要解决此问题,请不要与处理io_service的线程分离,并在io_service停止后显式加入线程。

class AsyncUDPClient
 : public IOServiceBase
{
public:
  // ...
  void run()
  {
    _threads.emplace_back([&]{ service().run(); })
  }
  // ...
  ~AsyncUDPClient()
  {
    close();
  }
  void close()
  {
    // Stop the io_service.  This changes its state and return immediately.
    service().stop();
    // Explicitly synchronize with threads running the io_service.
    for (auto& thread: _threads)
    {
      thread.join();
    }
  }
private:
  // ...
  std::vector<std::thread> _threads;
};

正如上面的评论所暗示的那样,io_service::stop()不会阻止。 它将io_service的状态更改为"已停止",立即返回,并使run()run_one()的所有调用尽快返回。 调用io_service::stopped()会立即返回io_service的状态。 这些调用都不指示在调用 run()run_one() 时是否存在线程。