boost::async_write在写入一段时间后失败

boost::async_write fails after writing for some time

本文关键字:一段时间 失败 async write boost      更新时间:2023-10-16

我遇到了一个非常特殊的问题。我编写了一个服务器,它将从第三方接收的数据写入连接的客户端。服务器向客户端写入操作在一段时间内很好,但过了一段时间,async_write要么失败,要么写操作永远不会返回。对于我的程序,如果async_write永远不会返回,那么就不会进行后续写入,并且我的服务器将对从第三方接收的数据进行排队,直到一切都崩溃。

我在下面包含了我的代码:

void ClientPartitionServer::HandleSignal(const CommonSessionMessage& message, int transferSize) {
  boost::lock_guard<boost::mutex> lock(m_mutex);
  if(m_clientSockets.size() != 0) {
    TransferToQueueBuffer(message.GetData(), transferSize);
  }
  if(m_writeCompleteFlag) {
    // TransferToWriteBuffer();
    for(vector<boost::asio::ip::tcp::socket*>::const_iterator i = m_clientSockets.begin(); i != m_clientSockets.end(); ++i) {
      WriteToClient(*i);
    }
  }
}
void ClientPartitionServer::WriteToClient(boost::asio::ip::tcp::socket* clientSocket) {
  m_writeCompleteFlag = false;
  cout << "Iniating write: " << m_identifier << endl;
  boost::asio::async_write(
    *clientSocket,
    boost::asio::buffer(m_queueBuffer.get(), m_queueBufferSize),
    boost::bind(
      &ClientPartitionServer::HandleWrite, this,
      boost::asio::placeholders::error,
      boost::asio::placeholders::bytes_transferred
  ));
}
void ClientPartitionServer::HandleWrite(const boost::system::error_code& ec, size_t bytes_transferred) {
  boost::lock_guard<boost::mutex> lock(m_mutex);
  if(ec != 0) {
    cerr << "Error writing to client: " << ec.message() << " " << m_identifier << endl;
    // return;
    cout << "HandleWrite Error" << endl;
    exit(0);
  }
  cout << "Write complete: " << m_identifier << endl;
  m_writeCompleteFlag = true;
  m_queueBuffer.reset();
  m_queueBufferSize = 0;
}

如有任何帮助,我们将不胜感激。

谢谢。

如果没有看到所有的代码,很难说,但对我来说,在多个(甚至一个)WriteToClient调用中持有互斥体是一个危险信号。通常,在I/O之间持有任何类型的锁(甚至是异步的),往好了说对性能不利,往坏了说会导致负载下出现奇怪的死锁。例如,如果异步写入以内联方式完成,并且在同一线程/调用堆栈中的HandleWrite上被调用,会发生什么?

我会尝试重构它,以便在写调用期间释放锁。

无论解决方案是什么,更普遍的建议是:

  • 不要跨I/O锁定
  • 添加一些诊断输出-什么线程调用每个处理程序,并且在什么顺序
  • 点击后尝试调试静止状态。应该能够从
    诊断死锁进程状态

使用链序列化对特定连接对象的访问。特别是,请检查strand::wrap()。要查看使用链的其他示例,请查看一些不同的计时器示例(尽管该代码适用于任何async_*()调用)。

首先,我不同意那些指出在异步操作中持有锁是个问题的评论。

持有锁定:

  1. 任何调用回调的函数都是坏的。

  2. 任何阻塞操作都是错误的。

async_write显式地保证既不阻塞也不调用处理程序,所以对我来说持有锁看起来不错。

但是,我可以在您的代码中看到一个错误,它违反了async_write的另一个要求。在调用完完成处理程序之前,不允许调用async_write。这就是你违反的。

每当调用其中一个处理程序时,m_writeCompleteFlag就会设置为true。这意味着您可能会在高负载下违反其他N-1套接字的async_write规则。