点对点 epoll 客户端和死锁

peer-to-peer epoll clients and deadlock

假设一个点对点程序使用 epoll 对多个点执行异步 TCP 读取和写入。当然,这意味着每个文件描述符都设置为非阻塞,以允许调用 epoll_wait 并检查多个套接字。

但是,有一个潜在的问题。假设有两个对等点:ABA 尝试向 B 写入消息,但 B 拥塞或发生其他情况,因此调用write returns -1,errno 设置为 EAGAIN。此时,A 在调用 epoll_wait 时进入休眠状态。

但是请注意 B 已经卡在了它自己对 epoll_wait 的调用上。如果 B 从未收到关于 A 发送消息失败的通知,那么 B 将永远不要醒来并尝试在 A 的套接字上执行读取,整个事情将陷入僵局。所以我的问题是,即使 A 放弃最初的 write 通话并去睡觉?

即使上面的答案是"yes",像这样的系统会不会因为应用层不同步而无限期死锁?即 A 尝试写入 B 但失败,因此它进入睡眠状态。然后 B 醒来并尝试从 A 读取,但失败了,因为 A 进入睡眠状态。等等

任何具有允许双方在读取之前等待另一方读取的可能状态的协议都将是一个从根本上被破坏的协议。对于 peer-to-peer 协议,通常每一端都被禁止延迟读取,只是因为它不能写入。

在实现方面,通常每次调用 epoll_wait(或发现就绪 I/O 的等效方式)都会检查程序正在使用的所有描述符的输入。读取 从不 延迟,除非应用程序具有已读取的未处理数据,并且在处理该数据后立即停止延迟。在阅读之前等待网络 activity 通常是一个非常糟糕的主意。

这就是典型的 protocol-neutral TCP 代理使用两个进程或两个线程的原因。您不能只从 A 读取然后对 B 进行阻塞写入,因为您不知道 B 在写入之前是否需要读取。

这也是为什么用 MSG_WAITALL 调用 recv 几乎总是一个坏主意。另一端可能正在等待您接收它已经发送的字节,然后再发送。任何协议都不能允许一方在读取任何字节之前等待所有字节被发送,如果它也允许另一方等到一些字节被读取后再发送其余字节!