常见坑
每一章对着一个报错或一个现象:先用最小的程序把它复现出来,Linux(epoll,容器里跑,必要时带 strace)和 macOS(kqueue)各跑一遍,再到源码里找它从哪一行来,最后给出修法、检查办法,以及 tokio 在这里替你做了什么。机制在前面几部分讲过的,这里只链过去。
mio 的坑大多有同一个来源:它只做「把就绪从内核取回来」这一件事(mio 解决什么问题),文档里写成「unspecified」的地方,结果就是平台 selector 的结果。所以同一个误用,在 debug 和 release、Linux 和 macOS 上常常表现不同。
- 没读到 WouldBlock 就回去 poll,程序卡住:边沿触发只报一次,一次事件只读一块、只
accept一个,剩下的数据和连接就再也等不到事件;两个平台都卡住,poll(2)后备更严。 - I/O source already registered with a
Registry:同一个 source 注册两次或注册到两个Poll,debug 下是 mio 自己的报错,Linux release 是File exists (os error 17),macOS release 返回Ok;tokio 的AsyncFd绕过了这道检查。 - from_std 之后事件循环卡在 read 里:
from_std不设非阻塞,读干净后的那次read阻塞住整个循环;tokio 1.44.0 起在 debug 下直接 panic。 - Socket not connected (os error 107):
connect返回Ok不等于连上;先peer_addr后take_error,Linux 上被拒的连接会永远「还在连」;只注册READABLE等不到成功。 - drop 之后还收到旧 token 的事件:fd 被 dup 过时,Linux 上旧 token 继续触发,fd 号复用后一次
poll里新旧 token 同时出现;macOS 关 fd 即移除。 - Only a single
Wakercan be active perPollinstance:debug 下 panic;release 下 epoll 两个都能用,kqueue 和poll(2)只剩后一个。 - wake() 了,poll 却等到超时:Linux 上这次唤醒丢了,macOS 还在;
poll(2)后备里register会吞掉没被取走的wake()。 - 可读事件读到 0 字节,或者事件里只有错误位:对端关闭、连着的 UDP 发到关闭的端口、只关注写时对端
shutdown(Write),两个平台给的位不一样,都要take_error。 - register 报 Invalid argument,事件照样来:kqueue 逐项处理 changelist、不回滚,失败的
register可能已经装上了一半。 - 同一个 token 一次 poll 来两个事件:同一个 socket 关注读写,Linux 1 个、macOS 2 个;
Events::with_capacity(0)在 Linux 上是EINVAL、在 macOS 上是忙等;poll(2)后备不看容量。
按现象找:
flowchart TB
s{看到了什么} -->|"poll 一直不返回"| h{线程在哪}
h -->|"在 epoll_wait 或 kevent"| p1["1 没读到 WouldBlock"]
h -->|"在 read 或 recvfrom"| p3["3 from_std 阻塞"]
h -->|"等 Waker 的事件"| p7["6 和 7 Waker"]
s -->|"register 返回错误"| r{哪个错误}
r -->|"already registered"| p2["2 注册两次"]
r -->|"kqueue 上的 Err"| p9["9 半截注册"]
s -->|"连接状态不对"| p4["4 connect 未完成"]
s -->|"事件对不上"| e{哪种}
e -->|"旧 token 还来"| p5["5 没 deregister"]
e -->|"读到 0 或只有错误位"| p8["8 关闭与错误"]
e -->|"个数和预想不同"| p10["10 事件数"]
💬 关联讨论
发起讨论加载中...