【问题标题】:select() and poll() missing a closed pipe on Mac OSselect() 和 poll() 在 Mac OS 上缺少一个封闭的管道
【发布时间】:2014-05-09 04:27:14
【问题描述】:

我在 Mac OS 10.9 上看到 select() 和 poll() 的行为,我无法解释。帮助我了解我可能做错了什么,或者这可能是一个操作系统错误(难以置信......)

我在做什么

我的程序使用 select() 或 poll() 在后台线程中监视文件描述符是否变得可读。两种实现都存在相同的错误,因此我将仅描述 poll 发生的情况。

有问题的文件描述符是用 forkpty() 创建的,所以它是伪终端的一端。我调用 execvp() 在子进程中运行一个 shell,最终它终止。这会导致父文件描述符在读取所有缓冲输出后从 read() 生成文件结束结果(即 read 返回 0)。

正常的操作过程是在此文件描述符上交替调用 poll() 和调用 read()。最终 read() 返回 0,我可以清理,知道子进程已完成。

怎么了

出乎意料的是:有时 poll() 不会在子进程完成并且文件描述符处于 EOF 状态时返回。

为什么我认为 poll() 行为不端

这里有一个 lldb 中的会话来演示。在子进程完成后,我在 poll() 中阻塞时停止了它。

(lldb) bt
* thread #11: tid = 0x24ee79, 0x00007fff904a594a libsystem_kernel.dylib`poll + 10
    frame #0: 0x00007fff904a594a libsystem_kernel.dylib`poll + 10
  * frame #1: 0x00000001001f2672 iTerm`-[iTermPollHelper poll](self=0x000060800042db00, _cmd=0x00007fff9349800b) + 194 at iTermPollHelper.m:117
    frame #2: 0x000000010014a87b iTerm`-[TaskNotifier run](self=0x000060000045c620, _cmd=0x00007fff8fda8066) + 4251 at TaskNotifier.m:216
    frame #3: 0x00007fff8c86c76b Foundation`__NSThread__main__ + 1318
    frame #4: 0x00007fff934a8899 libsystem_pthread.dylib`_pthread_body + 138
    frame #5: 0x00007fff934a872a libsystem_pthread.dylib`_pthread_start + 137

好的,所以线程 11 在 poll() 中阻塞了。以下是我的投票电话:

numDescriptors = poll(pollfds, count, -1);

让我们检查一下:

(lldb) p count
(int) $2 = 2
(lldb) p pollfds[0]
(pollfd) $3 = (fd = 6, events = 1, revents = 0)
(lldb) p pollfds[1]
(pollfd) $4 = (fd = 5, events = 1, revents = 0)

事件字段的值 1 对应于 POLLIN。在这种情况下,fd 5 是感兴趣的。我们已经证明 poll() 正在监视文件描述符 5,如果它处于 EOF 状态,则 poll 现在应该已经返回。我可以这样做:

(lldb) finish

并且 poll() 不会返回。所以它肯定被屏蔽了,并且必须相信 fd 5 上没有什么可读的。

我的程序包含这个功能:

void TryReadingFromFd(int fd) {
    char buffer[1];
    int n = read(fd, buffer, 1);
    NSLog(@"Read returns %d, errno=%d", n, errno);
}

虽然仍然在 poll() 中停止,但我从调试器运行它:

(lldb) expr (void)TryReadingFromFd(5)
2014-03-26 21:53:43.684 iTerm[48604:af07] Read returns 0, errno=35

如果 read 返回 0,这是 poll 应该捕获的文件结束条件。

进一步的证据

如果我给 poll() 一个超时并在循环中运行它,像这样:

do {
  numDescriptors = poll(pollfds, count, 1000);
} while (numDescriptors == 0);

然后问题就消失了,我可以看到 poll() 块,然后找到 EOF 文件描述符,但延迟永远不会超过 1 秒。大概当文件描述符在调用之前已经关闭时 poll() 可以工作,但是当它在 poll() 中关闭时会感到困惑。

还会发生什么?

这是一个复杂的程序,我无法在一个微不足道的小示例中重现此问题。所以主线程上可能会发生一些事情来混淆这个问题。我希望有人可以建议以这种方式干扰 poll() 的内容。从主线程中删除代码可以减少问题发生的频率,所以在找到确凿证据之前,我无法解决问题。

来源 如果你真的很好奇,可以在这里找到源代码(注意分支是“pollHelper”): https://github.com/gnachman/iTerm2/tree/pollHelper

要重现,请打开一个新终端并使用 Control-D 关闭。对我来说,这种情况大约有 20-30% 的时间发生。

在后台运行以下程序会使它在我的 2013 13" Macbook Pro 上更频繁地发生:

int main() { while (1); return 0; }

【问题讨论】:

  • poll 失败时使用errnoperror
  • 不清楚事情的顺序是什么。如果 read() 确实返回 0,那么如果在 read() 返回 0 之后再次查看文件描述符,则不能保证 poll/select 会发出任何信号。
  • 您在 poll/select 之前是否将文件描述符设置为非阻塞?
  • 澄清一下:当这个错误很明显时,read() 不会返回 0。最后一次调用 read() 返回 -1 且 errno=EAGAIN,然后永远轮询块。文件描述符是非阻塞的[我在 forkpty 返回后执行 fcntl(fd,F_SETFL,O_NONBLOCK)]。不可能有一个杂散的 read() 调用,否则数据会在正常操作期间丢失。
  • 我没有查看您的完整源代码,因为它确实很大,但终端驱动程序有时会故意丢弃数据,以达到去抖动的目的。作为一个纯粹的猜测,我怀疑存在一个错误,即某些代码具有与您正在轮询的文件描述符相匹配的陈旧文件描述符。

标签: c macos unix select file-descriptor


【解决方案1】:

(lldb) expr (void)TryReadingFromFd(5) 2014-03-26 21:53:43.684 iTerm[48604:af07] Read returns 0, errno=35

如果 read 返回 0,这是 poll 应该捕获的文件结束条件。

你确定吗? errno=35 表示 EAGAIN - 对我来说这看起来不像 EOF 条件。

(作为答案发布,因为 cmets 似乎不支持引号?)

【讨论】:

  • Errno 仅在 read() 返回 -1 时有效。在其他情况下,它包含一些随机(以前的)值。
  • 这当然是真的。快速拍摄有时会错过,抱歉。
猜你喜欢
  • 2013-08-20
  • 1970-01-01
  • 2013-09-24
  • 1970-01-01
  • 1970-01-01
  • 2013-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多