什么时候用边缘模式和 oneshot 重新武装 epoll?

When to rearm epoll with edge mode & oneshot?

我正在为套接字编写一个协程包装器作为协程用例的演示,我正在努力了解如何安全地使用 epoll(不引入竞争条件)。

我已经想通了,我必须使用边缘模式 EPOLLETEPOLLONESHOT。但是现在我不确定什么时候应该重新装上插座。

我应该在调用非阻塞操作之前还是之后重新准备?我想确保我既不会错过活动,也不会收到幻影。

// epoll & socket setup

int ret;

ret = epoll_ctl(epoll_, EPOLL_CTL_MOD, ...); // rearm here
//...
ret = read(...);
//...
ret = epoll_ctl(epoll_, EPOL_CTL_MOD, ...); // or here?

int ret = epoll_wait(...);

Should I rearm before calling a non-blocking operation or after?

从技术上讲,之后,但它并不那么简单。

不管EPOLLONESHOT,一旦你收到一个边沿触发的事件信号表明给定文件描述符的读准备就绪,你必须认为FD继续准备好直到read()errno 设置为 EAGAIN 失败(因此文件必须处于非阻塞模式)。在这些读取过程中,您可能会用一个 read() 读取所有剩余字节,但在下一个读取之前会有更多字节到达。在这种情况下,如果 FD 仍然处于武装状态,那么它的新事件将排队(或与该 FD 的另一个事件合并,视情况而定)。这种情况可能会导致您在 FD 实际上不再准备就绪时收到事件。

你应该考虑只接受那些“幻影”事件。由于您的文件将处于非阻塞模式,因此它们不会导致不必要的停顿,只是需要一些额外的工作。而且你的代码会更简单。但是,如果您确实使用 EPOLLONESHOT 来避免接收幻象事件,那么在您确定 FD 未就绪之前(通过 readEAGAIN 失败),您不得重新武装 FD,否则你打败了目的。

因此,完整的答案是确定FD未就绪后。这将至少需要两个 read(),甚至可能更多。如果文件在最后一次读取之后和重新准备之前准备就绪,那么重新准备应该会导致适当的事件排队。