【问题标题】:Possible causes of a deadlock in socket select套接字选择中死锁的可能原因
【发布时间】:2012-06-24 16:16:29
【问题描述】:

我有一个 jabber 服务器应用程序和另一个 C++ 中的 jabber 客户端应用程序。

当客户端接收和发送大量消息(每秒超过 20 条)时,选择就会冻结,永远不会返回。

使用 netstat,socket 仍然在 linux 上连接,使用 tcpdump,消息仍然发送到客户端,但 select 永远不会返回。

这里是选择的代码:

bool ConnectionTCPBase::dataAvailable( int timeout )
  {
    if( m_socket < 0 )
      return true; // let recv() catch the closed fd

    fd_set fds;
    struct timeval tv;

    FD_ZERO( &fds );
    // the following causes a C4127 warning in VC++ Express 2008 and possibly other versions.
    // however, the reason for the warning can't be fixed in gloox.
    FD_SET( m_socket, &fds );

    tv.tv_sec = timeout / 1000000;
    tv.tv_usec = timeout % 1000000;

    return ( ( select( m_socket + 1, &fds, 0, 0, timeout == -1 ? 0 : &tv ) > 0 )
             && FD_ISSET( m_socket, &fds ) != 0 );
  }

而死锁是gdb:

Thread 2 (Thread 0x7fe226ac2700 (LWP 10774)):
#0  0x00007fe224711ff3 in select () at ../sysdeps/unix/syscall-template.S:82
#1  0x00000000004706a9 in gloox::ConnectionTCPBase::dataAvailable (this=0xcaeb60, timeout=<value optimized out>) at connectiontcpbase.cpp:103
#2  0x000000000046c4cb in gloox::ConnectionTCPClient::recv (this=0xcaeb60, timeout=10) at connectiontcpclient.cpp:131
#3  0x0000000000471476 in gloox::ConnectionTLS::recv (this=0xd1a950, timeout=648813712) at connectiontls.cpp:89
#4  0x00000000004324cc in glooxd::C2S::recv (this=0xc5d120, timeout=10) at c2s.cpp:124
#5  0x0000000000435ced in glooxd::C2S::run (this=0xc5d120) at c2s.cpp:75
#6  0x000000000042d789 in CNetwork::run (this=0xc56df0) at src/Network.cpp:343
#7  0x000000000043115f in threading::ThreadManager::threadWorker (data=0xc56e10) at src/ThreadManager.cpp:15
#8  0x00007fe2249bc9ca in start_thread (arg=<value optimized out>) at pthread_create.c:300
#9  0x00007fe22471970d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:112
#10 0x0000000000000000 in ?? ()

您知道什么会导致 select 停止接收消息,即使我们仍在向他发送消息。 通过socket接收和发送大量消息时,linux中是否有缓冲区限制?

谢谢

【问题讨论】:

  • 可能对方崩溃或关闭。我建议始终对select 设置非无限超时(例如一秒超时)。我还建议使用poll 而不是select。你可以strace你的程序来找出select是如何“错误地”表现出来的。
  • 另一端(服务器)仍在运行,仍在发送消息。使用 tcpdump,我可以看到发送到客户端端口的那些消息。我们不在 select 中使用 timeout 的原因是不要在没有发送任何内容的时候过度消耗 cpu。此外,当我在客户端上运行 strace 时,他仍在套接字上等待。并且套接字在 /proc//fd/ 中仍然可用
  • "使用 tcpdump,我可以看到发送到客户端端口的那些消息"。那么你在服务器端运行 tcpdump 吗?如果你在本地机器上嗅探流量怎么办?那些数据包到达了吗?
  • 您至少可以检查 select() 的返回值,如果是 -1,请检查 errno。可以是 EINTR / EAGAIN 或 EINVAL ... 或任何东西。
  • 服务器和客户端在同一台机器上运行。所以客户端连接到服务器到端口5222,客户端端口就像41919。所以当我在接口'lo'上运行tcpdump时,我可以看到一个数据包已经从5222传递到端口41919

标签: c++ linux select deadlock gloox


【解决方案1】:

有几种可能。

超过FD_SETSIZE

您的代码正在检查是否存在负文件描述符,但不会超过上限 FD_SETSIZE(通常为 1024)。每当发生这种情况时,您的代码就是

  • 损坏自己的堆栈
  • select 提供一个空的fd_set,这将导致挂起

假设您不需要需要这么多同时打开的文件描述符,解决方案可能包括找到删除文件描述符泄漏的方法,尤其是处理关闭废弃描述符的堆栈代码。

您的代码中有一条可疑注释表明可能存在泄漏:

// let recv() catch the closed fd

如果此评论意味着有人将m_socket 设置为-1,并希望recv 将捕获关闭的套接字并关闭它,谁知道呢,也许我们正在关闭-1 而不是真正的关闭套接字。 (注意在网络级别关闭和在文件描述符级别关闭之间的区别需要单独的close 调用。)

这也可以通过转移到 poll 来解决,但是操作系统施加的一些其他限制使得这条路线非常具有挑战性。

带外数据

您说服务器正在“发送”数据。如果这意味着使用send 调用(而不是write 调用)发送数据,请使用strace 来确定发送标志参数。如果使用 MSG_OOB 标志,则数据将作为带外数据到达 - 并且您的 select 调用不会注意到这些,直到您将 fds 的副本作为另一个参数传递。

fd_set fds_copy = fds;
select( m_socket + 1, &fds, 0, &fds_copy, timeout == -1 ? 0 : &tv )

进程饥饿

如果盒子严重过载,则服务器正在执行而没有任何阻塞调用,并且具有实时优先级(使用top 进行检查) - 而客户端不是 - 客户端可能会被饿死。

暂停进程

理论上,客户端可能会以SIGSTOP 停止。您可能会知道是否是这种情况,按下了某处 ctrl-Z 或有一些特定进程对客户端进行控制,而不是您自己启动它。

【讨论】:

  • 感谢您的回答。客户端应用程序没有饿死。并且该过程不会暂停。另外,我添加了 fds_copy 来获取异常,但我从来没有得到它们并且问题仍然存在
  • @ruddy - 感谢您的系统研究。我编辑了答案以添加另一种值得关注的可能性。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-09
  • 1970-01-01
  • 2021-10-03
  • 2023-03-27
  • 1970-01-01
相关资源
最近更新 更多