套接字选择中死锁的可能原因

时间:2012-06-24 16:16:30

标签: c++ linux select deadlock gloox

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

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

使用netstat,套接字仍然连接在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 ?? ()

你知道吗,即使我们还在发送给他,也会导致选择停止接收消息。 在通过套接字接收和发送大量消息时,linux中是否有缓冲限制?

由于

1 个答案:

答案 0 :(得分:1)

有几种可能性。

超过FD_SETSIZE

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

  • 破坏自己的筹码
  • fd_set提出空select,这将导致挂起

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

您的代码中有可疑评论表明可能存在泄漏:

// 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 或者某个特定进程在客户端上执行控制而不是自己启动它。