pselect块,即使数据可以在套接字上读取

pselect blocks even though data is available for read on socket

本文关键字:套接字 读取 数据 pselect      更新时间:2023-10-16

从POSIX套接字(RHEL6 x86_64 C++icpc)读取时遇到间歇性延迟。我的代码是这样设计的,用户可以提供一个绝对的timespec截止日期(相对于相对超时),以便在多次调用recv时使用。在尝试调用recv之前,我调用pselect以确保数据可供读取。

这通常按预期工作(将等待数据,但不会超过截止日期,如果数据可用于recv,则不会出现明显的延迟)。然而,我有一个用户可以周期性地(大约50%的时间)让他的应用程序进入这样一种状态,即即使套接字上有数据,select也会阻塞大约400-500毫秒。如果我观察/proc/net/tcp,我可以看到RX队列中有可用的数据,并且我可以看到应用程序缓慢地从队列中读取数据。如果我跳过对pselect的调用,只调用recv,行为是类似的(但总体延迟较小,表明recv也在不必要地阻塞)。当应用程序进入这种状态时,它将保持这种状态(每次pselect/recv都会经历一致的延迟)。

我花了几个小时在这里和其他网站上闲逛。这是我能找到的最相似的问题,但没有解决方案。。。

http://developerweb.net/viewtopic.php?id=7458

以前有人遇到过这种行为吗?我不知道该怎么办。我已经插入了代码,以验证延迟是在这里发生的。(编辑:我们实际上只是验证了下面的整个方法很慢,而不是任何特定的系统调用。)这看起来像是内核/OS问题,但我不确定该从哪里看。这是代码。。。

// protected
bool
Message::wait(int socket, const timespec & deadline) {
    // Bail if deadline not provided
    if (deadline.tv_sec == 0 && deadline.tv_nsec == 0) {
        return true;
    }
    // Make sure we haven't already exceeded deadline
    timespec currentTime;
    clock_gettime(CLOCK_REALTIME, &currentTime);
    if (VirtualClock::cmptime(currentTime, deadline) >= 0) {
        LOG_WARNING("Timed out waiting to receive data");
        m_timedOut = true;
        return false;
    }
    // Calculate receive timeout
    timespec timeout;
    memset(&timeout, 0, sizeof(timeout));
    timeout.tv_nsec = VirtualClock::nsecs(currentTime, deadline);
    VirtualClock::fixtime(timeout);
    // Wait for data
    fd_set descSet;
    FD_ZERO(&descSet);
    FD_SET(socket, &descSet);
    int result = pselect(socket + 1, &descSet, NULL, NULL, &timeout, NULL);
    if (result == -1) {
        m_error = errno;
        LOG_ERROR("Failed to wait for data: %d, %s",
                m_error, strerror(m_error));
        return false;
    } else if (result == 0 || !FD_ISSET(socket, &descSet)) {
        LOG_WARNING("Timed out waiting to receive data");
        m_timedOut = true;
        return false;
    }
    return true;
}

VirtualClock是一个与时间相关的实用程序类,仅用于比较/修复时间段(即不引入任何延迟)。如果能对这种行为有所了解,我将不胜感激。

这实际上不是任何系统调用的问题。我们使用strace进行诊断,并看到了大量的clock_gettime呼叫。对调用代码的另一次(第三次)审查揭示了一个编程错误,导致被调用代码引用了损坏的堆栈数据。这是由于API的设计有缺陷,导致最后期限被破坏。

我允许用户传递对ServerConfig类的引用,该类包含配置(包括与截止日期相关的数据)。我的服务器类正在保存引用,而不是复制对象。用户在堆上创建了我的Server类的一个实例,在堆栈上(在方法中)传递了一个ServerConfig引用,导致在方法退出且ServerConfig超出范围时,配置中出现不确定性垃圾。这是一个较旧的代码,在被烧毁后,我已经阻止了这种事情在其他地方发生,但这一次却漏洞百出。

因此,我学到的教训是:小心编写挂在用户提供的引用上的API,重新思考过早的优化(我挂在引用上而不仅仅是复制的全部原因),当你看到这样的非确定性行为时,寻找堆栈损坏(当我怀疑构建被劫持但这次没有怀疑时,我会检查它)。此外,strace是一个很好的工具。。。我见过别人用它,但现在我自己用它很舒服。

感谢您的评论,并对误报表示歉意。