feat(ax-posix-api): improve ArceOS epoll semantics#1034
Conversation
There was a problem hiding this comment.
PR #1034 Review: feat(ax-posix-api): improve ArceOS epoll semantics
变更概述
本 PR 重构了 ArceOS POSIX epoll 实现,主要包括:
- WatchedEvent 结构体:引入独立的 watch 状态,替代原来仅存储
epoll_event的方式,支持 per-fd 跟踪last_ready、disabled等状态 - EPOLLET(边缘触发)支持:
deliverable_events通过ready & !last_ready计算新事件,语义正确 - EPOLLONESHOT 支持:事件触发后设置
disabled = true,需EPOLL_CTL_MOD重新激活 - epoll_create1:新增
EPOLL_CLOEXEC标志支持,包含标志验证 - epoll 自引用检查:
epoll_ctl(ADD, epfd)返回ELOOP,符合 Linux 语义 - epoll fd 的 poll 支持:使 epoll fd 可被
poll/select监控 - 错误码修正:read/write 返回
EINVAL(原来为ENOSYS),null 指针检查等 - ArceOS C 测试:新增
test-suit/arceos/c/epoll/测试用例,覆盖 level-triggered、edge-triggered、oneshot 和参数校验
实现分析
- Edge-triggered 语义正确:通过
last_ready跟踪上一次状态,ready & !last_ready检测新边沿 - Oneshot 语义正确:触发后设
disabled=true,update()重置disabled和last_ready - 事件常量分层定义清晰(READ/WRITE/ERROR/BEHAVIOR/RETURN)
is_epoll_file使用clone().into_any().downcast()检测类型,功能正确但多了一次 Arc clone
阻塞问题
测试 errno 不匹配(BLOCKING):test_epoll_arguments() 第 86 行期望 EINVAL,但实现返回 ELOOP:
expect_errno_int(epoll_ctl(epfd, EPOLL_CTL_ADD, epfd, &event), EINVAL);代码中 is_epoll_file 匹配后返回 LinuxError::ELOOP(errno=40),而测试断言 errno == EINVAL(errno=22)。这将导致测试在 QEMU 中必定失败。
修复方案:将测试中的 EINVAL 改为 ELOOP,符合 Linux epoll_ctl 语义(Linux man page 明确说明 epoll 自引用返回 ELOOP)。
CI 状态
cargo fmt --check:✅ 本地通过cargo clippy --features epoll -D warnings:✅ 本地通过- CI
Test arceos loongarch64 qemu:❌ 失败(base 分支同样存在 loongarch64 相关 CI 失败,因果关系不明确) - CI 其余检查:✅ 通过或因 loongarch64 失败被取消
- base 分支最新 CI(run 26631615654)同样是
failure状态,存在 pre-existing 失败
验证情况
- 作者承认无法在 WSL 中运行 QEMU 测试(缺少
x86_64-linux-musl-gcc),仅做了gcc -fsyntax-only和--list验证 - 本地实际 QEMU 测试无法在当前环境中运行(需要 C 交叉编译器和 QEMU)
- 上述 errno 不匹配问题正是在代码审查中通过静态分析发现的
重复/冲突分析
- 无其他 open PR 涉及 epoll 相关改动
- base 分支无等价 epoll 增强实现
待修复后可完成的工作
- 修复 errno 不匹配后,建议在 CI 环境中实际运行
cargo xtask arceos test qemu --target x86_64-unknown-none --test-group c --test-case epoll验证
Powered by mimo-v2.5-pro
There was a problem hiding this comment.
PR #1034 Review: feat(ax-posix-api): improve ArceOS epoll semantics
变更概述
本 PR 大幅改进了 ArceOS POSIX epoll 实现,新增 EPOLLET/EPOLLONESHOT 支持、epoll_create1、epoll fd 可 poll 等特性,并增加了完整的 C 测试用例。整体设计合理,edge-triggered 和 oneshot 的状态跟踪逻辑正确。
验证结果
- ✅
cargo fmt --check通过 - ✅
cargo clippy --features epoll -D warnings通过 - ❌ QEMU x86_64 实际运行测试失败(见下方阻塞问题)
🔴 阻塞问题:epoll_ctl 自引用 errno 不匹配
测试必定失败。我在 QEMU 中实际运行了 cargo xtask arceos test qemu --target x86_64-unknown-none --test-group c --test-case epoll,得到如下 panic:
sys_epoll_ctl => Err(EINVAL)
Assertion failed: errno == err (main.c: expect_errno_int: 12)
panicked at os/arceos/ulib/axlibc/src/unistd.rs:14:5:
explicit panic
原因分析:sys_epoll_ctl 中有两层检查:
// 第一层:epfd == fd → EINVAL
if epfd == fd {
return Err(LinuxError::EINVAL); // ← 先命中这里
}
// ...
// 第二层(在 control() 内):is_epoll_file → ELOOP
if is_epoll_file(&file) {
return Err(LinuxError::ELOOP);
}测试代码:
// 传入 epfd == fd,会命中第一层返回 EINVAL
expect_errno_int(epoll_ctl(epfd, EPOLL_CTL_ADD, epfd, &event), ELOOP); // ← 期望 ELOOP 但实际是 EINVAL修复方案(二选一):
方案 A(推荐):修改测试,区分两种错误场景:
// 自引用(epfd == fd)→ EINVAL,符合 Linux 语义
errno = 0;
expect_errno_int(epoll_ctl(epfd, EPOLL_CTL_ADD, epfd, &event), EINVAL);
// epoll 嵌套(不同 epoll fd)→ ELOOP
int epfd2 = epoll_create(1);
assert(epfd2 >= 0);
errno = 0;
expect_errno_int(epoll_ctl(epfd, EPOLL_CTL_ADD, epfd2, &event), ELOOP);
close(epfd2);方案 B:移除 sys_epoll_ctl 中的 epfd == fd 检查,让 is_epoll_file 统一返回 ELOOP(但与 Linux EINVAL 语义不完全一致)。
非阻塞建议
-
is_epoll_file可优化:当前file.clone().into_any().downcast()多了一次Arc::clone。如果FileLiketrait 有as_any()方法,可以直接借用而无需 clone。 -
EPOLLHUP未上报:当 pipe 的写端关闭时,Linux 会自动返回EPOLLHUP | EPOLLIN,但当前current_ready()仅在poll()返回Err时设置EPOLLERR,成功路径不会设置EPOLLHUP。这不影响当前测试,但会导致依赖EPOLLHUP的应用行为不符合预期。
Powered by mimo-v2.5-pro
There was a problem hiding this comment.
PR #1034 Review: feat(ax-posix-api): improve ArceOS epoll semantics
变更概述
本 PR 改进了 ArceOS POSIX epoll 实现,新增 EPOLLET(边缘触发)、EPOLLONESHOT 支持,新增 epoll_create1 API,修正 epoll fd 的 read/write 错误码,并新增完整的 C 测试用例。
与前次 Review 对比
✅ 已修复:前两次 review 指出的 errno 不匹配问题已通过提交 2953893ea 修复。测试现在期望 epoll_ctl(epfd, EPOLL_CTL_ADD, epfd, &event) 返回 EINVAL,与实现中 epfd == fd 的前置检查一致。
本次验证结果
- ✅
cargo fmt --check通过 - ✅
cargo clippy --features epoll -D warnings通过(ax-posix-api 和 axlibc 两个 crate) - ❌ QEMU x86_64 实际运行测试失败:120s 超时
🔴 阻塞问题:edge-triggered epoll_wait(timeout=0) 挂起
我添加了 printf 调试信息后运行 cargo xtask arceos test qemu --target x86_64-unknown-none --test-group c --test-case epoll,得到如下结果:
[DBG] arguments test done ✅ 参数校验全部通过
[DBG] epfd=3, pipefd={4,5}
[DBG] level_triggered: ADD ok ✅ EPOLL_CTL_ADD 成功
[DBG] level_triggered: expect_one 1 ok ✅ LT 第一次 EPOLLOUT 正确上报
[DBG] level_triggered: expect_one 2 ok ✅ LT 第二次 EPOLLOUT 正确上报
[DBG] level_triggered: DEL ok ✅ EPOLL_CTL_DEL 成功
[DBG] level_triggered: done ✅ level-triggered 测试通过
[DBG] expect_no_event: OK ✅ ET 空管道无事件
[DBG] expect_one_event: got event for fd=4 ✅ ET 写入后 EPOLLIN 正确上报
[DBG] expect_no_event: calling epoll_wait(epfd=3, timeout=0) ← 此处挂起,永不返回
挂起位置:test_edge_triggered_epoll 中,第一次 expect_one_event 成功返回后,第二次 expect_no_event 调用 epoll_wait(epfd, &event, 1, 0) 永不返回。
分析:此时 pipe 中仍有数据("abc" 未读取),poll_all 中 current_ready() 返回 ready=EPOLLIN,deliverable_events 因 edge-triggered 计算 EPOLLIN & !EPOLLIN = 0,应返回 0 事件。随后 timeout=0 的 deadline 检查应立即返回 Ok(0)。但 epoll_wait 未返回,120s 后 QEMU 超时。
可能原因(需进一步排查):
poll_all内部Mutex锁获取可能死锁(但代码分析未发现锁序反转)wall_time()或 deadline 比较在特定条件下不满足退出条件sys_sched_yield()导致任务未被重新调度(不应影响 timeout=0 的情况)
建议排查方向:在 epoll_wait 的循环中添加迭代计数或 debug 输出,确认 poll_all 的返回值和 deadline 检查结果。
非阻塞建议
-
is_epoll_file可优化:file.clone().into_any().downcast()多了一次 Arc clone。如果FileLiketrait 提供as_any()方法可直接借用。 -
EPOLLHUP/EPOLLERR未在正常路径上报:current_ready()仅在poll()返回Err时设置EPOLLERR,成功路径不会设置EPOLLHUP。不影响当前测试,但依赖EPOLLHUP的应用行为会不符合 Linux 语义。
重复/冲突分析
- 无其他 open PR 涉及 epoll 相关改动
- base 分支无等价 epoll 增强实现
结论
整体设计合理,edge-triggered 和 oneshot 语义逻辑正确(静态分析),errno 问题已修复。但 QEMU 实际测试中 edge-triggered 场景挂起,需修复后方可合并。
Powered by mimo-v2.5-pro
There was a problem hiding this comment.
PR #1034 Review: feat(ax-posix-api): improve ArceOS epoll semantics
变更概述
本 PR 大幅改进了 ArceOS POSIX epoll 实现,包括:
- WatchedEvent 结构体:为每个被监听 fd 保存
FileLike引用、last_ready、disabled状态,替代原来仅存epoll_event的方式 - EPOLLET(边缘触发):通过
ready & !last_ready计算新边沿事件,语义正确 - EPOLLONESHOT:触发后设
disabled = true,需EPOLL_CTL_MOD重新激活 - epoll_create1:新增
EPOLL_CLOEXEC标志支持,包含标志验证 - epoll 自引用检查:
epfd == fd→EINVAL;嵌套 epoll fd →ELOOP,符合 Linux 语义 - epoll fd 的 poll 支持:使 epoll fd 可被
poll/select监控为 readable - 错误码修正:read/write 返回
EINVAL(原为ENOSYS),null 指针检查等 - pipe 修复:读/写达到请求长度或缓冲区耗尽时立即返回,避免 ET epoll drain pipe 时卡死
- C 测试用例:新增
test-suit/arceos/c/epoll/,覆盖 level-triggered、edge-triggered、oneshot、DEL 和参数校验
与前次 Review 对比
✅ 已修复:前两次 review 指出的 errno 不匹配问题已通过提交 2953893ea 修复。epoll_ctl(epfd, ADD, epfd, ...) 现在通过 epfd == fd 检查返回 EINVAL,测试期望与实现一致。
✅ 已修复:第三次 review 指出的 edge-triggered epoll_wait(timeout=0) 挂起问题已通过提交 16ec0f5fe 修复。pipe 读函数现在在 read_size == max_len 或缓冲区耗尽时立即返回,不再阻塞。
实现分析
- Edge-triggered 语义正确:
last_ready跟踪上一次状态,ready & !last_ready检测新边沿 - Oneshot 语义正确:触发后设
disabled=true,update()重置disabled和last_ready epoll_ctl两层检查设计合理:epfd == fd→EINVAL(Linux 语义),is_epoll_file→ELOOP(嵌套 epoll)- 事件常量分层定义清晰(READ/WRITE/ERROR/BEHAVIOR/RETURN)
- pipe 修复符合 POSIX
read()语义:读到数据后不应继续阻塞等待
验证结果
- ✅
cargo fmt --check通过 - ✅
git diff --check通过 - ✅
cargo xtask clippy --package ax-posix-api通过(15 项检查) - ✅
cargo xtask clippy --package ax-libc通过(16 项检查) - ✅
cargo xtask arceos test qemu --arch x86_64 --test-group c --test-case epollPASS(34.11s)- 参数校验全部通过:
epoll_create(0)→EINVAL、epoll_create1(bad flags)→EINVAL、epoll_wait(maxevents=0)→EINVAL、epoll_wait(NULL)→EFAULT、read/write on epfd→EINVAL、null event→EFAULT、自引用→EINVAL - level-triggered、edge-triggered、oneshot 三个场景全部通过
- 参数校验全部通过:
- ✅
cargo xtask arceos test qemu --arch x86_64 --test-group c --test-case pthread-pipePASS(28.77s):pipe 改动未引入回归
CI 状态
当前 head SHA 16ec0f5fe 的 commit status 为 pending(无已完成检查)。本次本地验证已覆盖核心变更面。
重复/冲突分析
- base 分支无等价 epoll 增强实现
- 无其他 open PR 涉及 epoll、pipe 或 ArceOS POSIX API 改动(PR #1035 为 eBPF JIT、PR #1036 为 K230 KPU,均不相关)
- 无合并冲突
非阻塞建议
is_epoll_file可优化:当前file.clone().into_any().downcast()多了一次Arc::clone。如果FileLiketrait 提供as_any()方法可直接借用。EPOLLHUP未在正常 poll 路径上报:current_ready()仅在poll()返回Err时设置EPOLLERR,成功路径不会设置EPOLLHUP。不影响当前测试,但依赖EPOLLHUP的应用行为会偏离 Linux 语义。epoll_create(0)语义差异:Linux 中epoll_create(0)成功返回(hint 被忽略),本 PR 中size <= 0返回 EINVAL。测试已覆盖此行为且epoll_create1为推荐 API,可接受。
结论
所有前次 review 阻塞问题已修复,QEMU 实际运行测试全部通过,无新增阻塞问题。APPROVE。
Powered by mimo-v2.5-pro
ZR233
left a comment
There was a problem hiding this comment.
本次重点按死锁/永久等待风险检查了 epoll 与 pipe 的生命周期路径。现有实现中 WatchedEvent 对被监听 fd 持有强 Arc<dyn FileLike>,这会让已经 close() 的 fd 对象继续存活;对 ArceOS 的 pipe 来说,读端 EOF 依赖写端对象真正释放,因此会出现关闭写端后读端仍认为写端存在、继续等待的情况。
我在当前 head 上做了验证:正式用例 cargo fmt --check、cargo xtask clippy --package ax-posix-api、cargo xtask clippy --package ax-libc、cargo xtask arceos test qemu --arch x86_64 --test-group c --test-case epoll 均通过;但临时加入以下场景会稳定失败:创建 pipe,将写端以 EPOLLOUT 加入 epoll,随后 close(pipefd[1]),再用 epoll_wait(epfd, ..., 0) 期望无事件。Linux 宿主机同场景返回 0 个事件且读端 read() 返回 EOF;当前 ArceOS 仍报告该已关闭写端可写,说明 epoll watch 表强引用阻止了 fd 生命周期结束。这个后续还会使空 pipe 的读端无法观察 EOF,容易表现为永久 yield/卡住。
CI 方面,本 PR 当前容器侧格式、clippy 和 ArceOS QEMU checks 均为通过,host 侧按工作流策略 skipped。重复/重叠检查中,当前 open PR 搜索到的 epoll ax-posix-api 仅本 PR;base 分支没有等价的 ArceOS epoll ready queue/EPOLLET 实现。旧的 errno review thread 已按当前实现状态 resolve。
建议修复方向:epoll 不能仅靠强引用把 fd 对象当作 fd 生命周期本身。需要在 close 时从相关 epoll interest 中移除/失效 watch,或保存可检测关闭状态的 fd 表引用/弱引用,并补一个关闭已注册 fd 的回归测试,尤其覆盖 pipe 写端关闭后读端 EOF/epoll 不再报告已关闭 fd。
|
一个建议: EPOLLET 的 last_ready 不能只在 epoll_wait() 采样时更新;当被监听对象的 readiness 因 read/write/close 等操作发生变化时,epoll 的 watch 状态也需要被失效或通知。否则用户在一次 epoll_wait 后把 pipe 读空,再次写入前没有额外调用空的 epoll_wait,last_ready 仍停留在 EPOLLIN,下一次 ready & !last_ready 会变成 0,从而漏掉新的边沿事件。建议引入 readiness-change 通知/epoch/requeue 机制,或者在 fd 对象状态变化时更新对应 epoll watch,而不是只在 epoll_wait() 中更新 last_ready。 |
There was a problem hiding this comment.
PR #1034 Review: feat(ax-posix-api): improve ArceOS epoll semantics
变更概述
本 PR 大幅改进了 ArceOS POSIX epoll 实现,包括:
- WatchedEvent + Weak 引用:为每个被监听 fd 保存
Weak<dyn FileLike>、last_ready、disabled状态,替代原来仅存epoll_event的方式 - EPOLLET(边缘触发):通过
ready & !last_ready计算新边沿事件,语义正确 - EPOLLONESHOT:触发后设
disabled = true,需EPOLL_CTL_MOD重新激活 - epoll_create1:新增
EPOLL_CLOEXEC标志支持,包含标志验证 - epoll 自引用检查:
epfd == fd→EINVAL;嵌套 epoll fd →ELOOP,符合 Linux 语义 - epoll fd 可 poll:
poll()返回PollState { readable: has_ready_events(), writable: false } - 错误码修正:read/write 返回
EINVAL(原为ENOSYS),null 指针检查返回EFAULT - pipe 修复:读/写达到请求长度或缓冲区耗尽时立即返回,避免 ET epoll drain pipe 时卡死
- 关闭 fd 清理:
poll_all()/has_ready_events()/control()中retain(|_, watch| !watch.is_closed())自动清理已关闭 fd 的 watch 条目 - C 测试用例:新增
test-suit/arceos/c/epoll/,覆盖 level-triggered、edge-triggered、oneshot、DEL、参数校验和已注册 fd 关闭场景
与前次 Review 对比
- ✅ 已修复:mai-team-app[bot] 第一次 review 的 EINVAL/ELOOP errno 不匹配 → 已在提交 4060161 中修复
- ✅ 已修复:mai-team-app[bot] 第二次 review 的 edge-triggered
epoll_wait(timeout=0)挂起 → 已在提交 16ec0f5 中通过 pipe 读写边界修复 - ✅ 已修复:ZR233 第四次 review 的
WatchedEvent持有强Arc<dyn FileLike>导致已关闭 fd 对象继续存活 → 已在最新提交 698af2a 中通过Weak<dyn FileLike>修复
实现分析
- Weak 引用方案正确:
WatchedEvent.file使用Arc::downgrade()创建弱引用。当 fd 被 close 后,fd 表释放强引用,is_closed()检测strong_count() == 0,current_ready()中upgrade()失败返回 0,retain()清理已关闭条目。这与 ZR233 建议的修复方向一致。 - Edge-triggered 语义正确:
last_ready跟踪上一次状态,ready & !last_ready检测新边沿 - Oneshot 语义正确:触发后设
disabled=true,update()重置disabled和last_ready - epoll_ctl 两层检查合理:
epfd == fd→EINVAL(Linux 语义),is_epoll_file→ELOOP(嵌套 epoll) - 事件常量分层清晰:READ/WRITE/ERROR/BEHAVIOR/RETURN
- pipe 修复符合 POSIX:
read()已读到数据后应立即返回,不应继续阻塞等待更多数据
验证
- ✅
cargo fmt --check通过 - ✅
git diff --check通过(无空白错误) - CI 状态:最新提交 698af2a 的所有 check run 显示
skipped(可能因 CI workflow 路径过滤或提交太新)。前次 mai-team-app[bot] 在提交 16ec0f5 上本地验证:cargo xtask arceos test qemu --arch x86_64 --test-group c --test-case epollPASS(34.11s),cargo xtask arceos test qemu --arch x86_64 --test-group c --test-case pthread-pipePASS(28.77s) - 本地环境无法运行 QEMU 测试(缺少交叉编译器和 QEMU),依赖前次验证结果
重复/冲突分析
- base 分支无等价 epoll 增强实现
- 无其他 open PR 涉及 epoll、pipe 或 ArceOS POSIX API 改动
- 无合并冲突
已知限制(非阻塞)
is_epoll_fileArc clone:file.clone().into_any().downcast()多了一次 Arc clone。如果FileLiketrait 提供as_any()方法可直接借用,但功能正确EPOLLHUP未在正常 poll 路径上报:current_ready()仅在poll()返回Err时设置EPOLLERR,成功路径不会设置EPOLLHUP。不影响当前测试,但依赖EPOLLHUP的应用行为会偏离 Linux 语义epoll_create(0)返回 EINVAL:Linux 允许epoll_create(0)成功(hint 被忽略),本 PR 返回 EINVAL。epoll_create1为推荐 API,可接受
结论
所有前次 review 阻塞问题已修复(包括 ZR233 指出的强引用生命周期问题),实现语义正确,测试覆盖完整,无新增阻塞问题。APPROVE。
Powered by mimo-v2.5-pro
ZR233
left a comment
There was a problem hiding this comment.
本轮重新审查了最新提交 698af2a37ba3998d90dfc12b8e50541716c549cc。
此前提到的 epoll watch 持有强 Arc<dyn FileLike> 导致 fd 关闭后对象生命周期被 epoll 延长的问题,这版已经改成 Weak,并新增了 test_registered_fd_close() 覆盖;我已把旧的对应 review thread 标记为 resolved。
当前还剩一个阻塞问题:EPOLLET 的 last_ready 只在 epoll_wait() 采样路径里更新,fd 的 readiness 在 read/write/close 等操作中发生 1->0->1 转换时不会让 watch 状态失效。因此用户在收到一次 EPOLLIN 后把 pipe 读空,并在下一次 epoll_wait() 之前立刻写入新数据,会漏掉新的边沿事件。现有测试在读空后先调用了一次 expect_no_event(),刚好把 last_ready 刷成 0,所以没有覆盖这个真实使用路径。详细场景已留在行内评论。
本地验证:
cargo fmt --all --check:通过git diff --check origin/dev...HEAD:通过gcc -fsyntax-only -Ios/arceos/ulib/axlibc/include test-suit/arceos/c/epoll/c/main.c:通过cargo xtask clippy --package ax-posix-api:15/15 checks passedcargo xtask clippy --package ax-libc:16/16 checks passed- 临时加入“drain 后不经过空 wait 直接再次 write”的 ET 回归场景后,
cargo xtask arceos test qemu --arch x86_64 --test-group c --test-case epoll在新增的expect_one_event()失败,复现该语义缺口。
开放 PR 重叠检查:看到 StarryOS 侧 epoll 相关 PR(如 #1018/#1024)和发布/基础设施 PR,但没有另一个开放 PR 在实现 ArceOS ax-posix-api 这套 epoll ready-state 逻辑。
ZR233
left a comment
There was a problem hiding this comment.
复审 head fef78a3441a3f9fc77cab0a887cbe571c505b754,这版确实补上了 pipe drain 后不经过空 wait 再写的测试,并且 cargo xtask clippy --package ax-posix-api 15 组本地通过。不过当前 ET readiness 修法会在 fd 仍保持 ready 的部分消费场景重复投递事件,和 Linux EPOLLET 语义不符,见 inline comment。
本地验证:
git diff --check && cargo fmt --checkcargo xtask clippy --package ax-posix-api- 临时加入“EPOLLET 首次事件后只读 1/3 字节,立即
epoll_wait(timeout=0)应无事件”的回归断言后运行cargo xtask arceos test qemu --arch x86_64 --test-group c --test-case epoll,失败在新增的expect_no_event(),说明当前实现会重复报告仍然 readable 的 fd。
远端 CI 当前 run 26935096202 是 failure/cancelled 混合状态;尝试拉 Test axvisor self-hosted x86_64 / run_host 日志时 GitHub 日志存储连接失败,未能进一步分类。
ZR233
left a comment
There was a problem hiding this comment.
复审 head 6cd64a497aead835e30f3ca576b4d3b391cbbf12。最新提交只是 rerun CI,和上次 review 的 head fef78a3441a3f9fc77cab0a887cbe571c505b754 相比没有代码差异;因此 line 101 上指出的 EPOLLET 部分消费后重复投递问题仍然存在,原 inline comment 仍适用。\n\n我已把更早的“drain 后不经过空 wait 再写会漏事件”thread 标记 resolved,因为当前 readiness_version 方案和现有测试确实覆盖了那条路径。但现在的阻塞点是不要把任意 read/write 造成的版本变化都当作新边沿:fd 仍然保持 readable 时,部分 read 后立刻 epoll_wait(timeout=0) 不应再次返回 EPOLLIN。\n\n本地验证:git diff --check origin/dev...HEAD、cargo fmt --check 通过。由于本次提交无代码差异,未重复跑上次已经通过的 clippy/QEMU;远端 CI run 26936437920 评审时仍有多项 job 在运行中。
There was a problem hiding this comment.
PR #1034 Review: feat(ax-posix-api): improve ArceOS epoll semantics
变更概述
本 PR 大幅改进了 ArceOS POSIX epoll 实现,包括:
- WatchedEvent + Weak 引用:为每个被监听 fd 保存
Weak<dyn FileLike>、last_ready、last_readiness_version、disabled状态 - EPOLLET(边缘触发):通过
readiness_version+last_ready组合检测边沿事件 - EPOLLONESHOT:触发后设
disabled = true,需EPOLL_CTL_MOD重新激活 - epoll_create1:新增
EPOLL_CLOEXEC标志支持,包含标志验证 - epoll 自引用检查:
epfd == fd→EINVAL;嵌套 epoll fd →ELOOP,符合 Linux 语义 - epoll fd 可 poll:使 epoll fd 可被
poll/select监控 - 错误码修正:read/write 返回
EINVAL,null 指针检查返回EFAULT - pipe 修复:读/写达到请求长度或缓冲区耗尽时立即返回
- 关闭 fd 清理:
retain()自动清理已关闭 fd 的 watch 条目 - C 测试用例:新增
test-suit/arceos/c/epoll/,覆盖 LT、ET、oneshot、DEL、参数校验和已注册 fd 关闭
与前次 Review 对比
- ✅ errno 不匹配(EINVAL/ELOOP)→ 已在提交 4060161/2953893ea 中修复
- ✅ edge-triggered
epoll_wait(timeout=0)挂起 → 已在提交 16ec0f5 中通过 pipe 读写边界修复修复 - ✅ 强
Arc引用导致已关闭 fd 对象继续存活 → 已在提交 698af2a 中通过Weak引用修复 - ✅ drain 后不经过空 wait 再写漏掉新边沿 → 已在提交 fef78a3 中通过
readiness_version机制修复
🔴 阻塞问题:EPOLLET 部分消费假阳性
与 ZR233 的 review 一致:deliverable_events 中,当 readiness_version != last_readiness_version 时直接返回整个 ready,会把部分消费(fd 仍保持 readable)也当作新边沿重复投递。
复现场景:
- pipe 写入 "abc",EPOLLET watch
EPOLLIN epoll_wait(timeout=0)→ 返回 EPOLLIN(正确),last_ready=EPOLLIN, last_readiness_version=V1read(buf, 1)只读 1 字节(pipe 仍有数据)→readiness_version递增到 V2epoll_wait(timeout=0)→ 因 V2≠V1 返回 EPOLLIN(错误,Linux 不会再次返回,因为 fd 一直处于 readable,没有新的 not-ready→ready 边沿)
根因:PipeRingBuffer::notify_readiness_changed() 在每次成功 read/write 时都递增版本号,无论 readiness 状态是否实际发生了改变(即 Empty↔Normal 或 Full↔Normal 转换)。
建议修复方向:
只在 RingBufferStatus 实际发生状态转换时递增 readiness_version:
Empty → Normal(readable 从 false 变 true)→ 递增Normal → Empty(readable 从 true 变 false)→ 递增Full → Normal(writable 从 false 变 true)→ 递增Normal → Full(writable 从 true 变 false)→ 递增Normal → Normal(仍可读写,仅数量变化)→ 不递增
这样 readiness_version 精确反映实际 readiness 边沿转换,deliverable_events 的版本比较逻辑自然正确。同时建议补充 "部分消费后 epoll_wait(timeout=0) 应返回 0 事件" 的回归测试。
验证结果
- ✅
cargo fmt --check通过 - ✅
git diff --check origin/dev...HEAD通过 - ✅
cargo xtask clippy --package ax-posix-api:15/15 checks passed - ✅
cargo xtask clippy --package ax-libc:16/16 checks passed - ✅
cargo xtask arceos test qemu --arch x86_64 --test-group c --test-case epoll:PASS(75.32s) - ✅
cargo xtask arceos test qemu --arch x86_64 --test-group c --test-case pthread-pipe:PASS(55.38s),pipe 改动未引入回归
CI 状态
当前 head 6cd64a497aead835e30f3ca576b4d3b391cbbf12 的所有 check run 均为 skipped(可能因 CI workflow 路径过滤),commit status 为 pending。本地验证已覆盖核心变更面。
重复/冲突分析
- PR #1018(StarryOS nginx EPOLLEXCLUSIVE)已合并到 dev,仅涉及 StarryOS 内核 epoll,与 ArceOS
ax-posix-api无重叠 - 无其他 open PR 涉及 ArceOS epoll、pipe 或 POSIX API 改动
- base 分支无等价 epoll 增强实现
- 无合并冲突
非阻塞建议
is_epoll_file可优化:file.clone().into_any().downcast()多了一次 Arc clone,如果FileLiketrait 提供as_any()方法可直接借用EPOLLHUP未在正常 poll 路径上报:current_ready()仅在poll()返回Err时设置EPOLLERR,成功路径不会设置EPOLLHUPepoll_create(0)返回 EINVAL:Linux 允许epoll_create(0)成功(hint 被忽略),epoll_create1为推荐 API,可接受
Powered by glm-5.1
There was a problem hiding this comment.
PR #1034 复审:feat(ax-posix-api): improve ArceOS epoll semantics
变更概述
本 PR 大幅改进 ArceOS POSIX epoll 实现,包括:
- WatchedEvent + Weak 引用:保存
Weak<dyn FileLike>、last_ready、last_readiness_version、disabled状态 - EPOLLET(边缘触发):通过
readiness_version+last_ready组合检测边沿事件 - EPOLLONESHOT:触发后设
disabled=true,需EPOLL_CTL_MOD重新激活 - epoll_create1:新增
EPOLL_CLOEXEC标志支持 - epoll 自引用检查:
epfd == fd→EINVAL;嵌套 epoll fd →ELOOP - epoll fd 可 poll:
poll()返回PollState { readable: has_ready_events() } - pipe 修复:读/写达到请求长度或缓冲区耗尽时立即返回
- 关闭 fd 清理:
retain()自动清理已关闭 fd 的 watch 条目 - pipe readiness_version:读端和写端分开维护,仅在
RingBufferStatus状态转换时递增 - C 测试用例:覆盖 LT、ET、oneshot、DEL、参数校验和已注册 fd 关闭
与前次 Review 对比
- ✅ 已修复:errno 不匹配(EINVAL/ELOOP)→ 已在早期提交修复
- ✅ 已修复:ET
epoll_wait(timeout=0)挂起 → 已通过 pipe 读写边界修复 - ✅ 已修复:强
Arc引用导致已关闭 fd 对象继续存活 → 已通过Weak引用修复 - ✅ 已修复:drain 后不经过空 wait 再写漏掉新边沿 → 已通过
readiness_version机制修复
🔴 阻塞问题:EPOLLET 部分消费后假阳性
与 ZR233 最近两次 review 一致。deliverable_events 中,当 readiness_version != last_readiness_version 时直接返回整个 ready,会把部分消费(fd 仍保持 readable)也当作新边沿重复投递。
问题根因:readiness_version 仅在 RingBufferStatus 发生 Empty↔Normal、Full↔Normal 转换时递增。当 pipe 处于 Normal 状态时部分 read 和后续 write 不改变状态,版本号不变。但如果版本号因其他原因变化(例如之前的一次全量消费),deliverable_events 会误判为新边沿。
具体场景:
// 1. pipe 写入 "abc",ET watch EPOLLIN
// 2. epoll_wait → EPOLLIN ✅, last_ready=EPOLLIN, last_version=V
// 3. read(buf, 1) 只读 1 字节 → pipe 仍 Normal, version 不变
// 4. 此时 epoll_wait(timeout=0) → 无事件 ✅ (version 没变, ready & !last_ready=0)
// 但如果之前有其他操作导致 version 变化,则会产生假阳性当前 test_edge_triggered_epoll 能通过是因为它在写 "d" 之前先 read(read_fd, buf, 2) 把 pipe 完全排空,触发了 Normal→Empty 状态转换。这掩盖了部分消费的真实使用路径。
修复建议(ZR233 已给出方向):不要把任意 readiness_version 变化都当作新的 edge。EPOLLET 只应在 fd 从 not-ready → ready 转换时触发。具体方案:
- 方案 A:在
PipeRingBuffer中增加was_drained_empty标志,在Empty状态设置为true,在deliverable_events中检查:仅当was_drained_empty=true且ready != 0时视为新边沿 - 方案 B:将
readiness_version从u64改为(u64, bool)元组,其中bool表示自上次 edge 以来是否经历过 Empty 状态
CI 状态
当前 head 1f27cdd8d 的所有 check run 显示 skipped(可能因路径过滤或新提交太新)。前次 mai-team-app[bot] 本地验证记录:
cargo xtask arceos test qemu --arch x86_64 --test-group c --test-case epollPASScargo xtask arceos test qemu --arch x86_64 --test-group c --test-case pthread-pipePASS
本次本地验证:
git diff --check origin/dev...HEAD✅ 通过cargo fmt --check超时未完成(环境限制)
重复/冲突分析
- base 分支无等价 epoll 增强实现
- 无其他 open PR 涉及 ArceOS
ax-posix-api的 epoll 或 pipe 改动 - PR #1107(cgroup v2)、PR #1109(docs/skills)均不相关
- 无合并冲突
非阻塞建议
is_epoll_fileArc clone 优化:file.clone().into_any().downcast()多了一次 Arc clone。如果FileLiketrait 提供as_any()方法可直接借用EPOLLHUP未在正常 poll 路径上报:current_ready()仅在poll()返回Err时设置EPOLLERR,成功路径不会设置EPOLLHUP。不影响当前测试,但依赖EPOLLHUP的应用行为会偏离 Linux 语义epoll_create(0)返回 EINVAL:Linux 允许epoll_create(0)成功(hint 被忽略),本 PR 返回 EINVAL。epoll_create1为推荐 API,可接受- TCP/UDP socket 的
readiness_version:目前TcpSocket和UdpSocket始终返回readiness_version: 0。如果 epoll 需要对 socket 也支持 ET 语义,后续需要为 socket 实现类似的版本追踪
结论
整体设计合理,Weak 引用修复了 fd 生命周期问题,readiness_version 机制的方向正确。但 EPOLLET 在部分消费场景下会产生假阳性重复投递事件,需要修复 deliverable_events 的边沿判定逻辑。此外,当前测试未覆盖部分消费场景,建议补充回归测试。
Powered by mimo-v2.5-pro
There was a problem hiding this comment.
PR #1034 Review: feat(ax-posix-api): improve ArceOS epoll semantics
变更概述
本 PR 大幅改进 ArceOS POSIX epoll 实现,包括:
- WatchedEvent + Weak 引用:保存
Weak<dyn FileLike>、last_ready、last_readiness_version、disabled状态 - EPOLLET(边缘触发):通过
readiness_version+last_ready组合检测边沿 - EPOLLONESHOT:触发后设
disabled=true,需EPOLL_CTL_MOD重新激活 - epoll_create1:新增
EPOLL_CLOEXEC支持,含标志校验 - epoll 自引用:
epfd == fd→EINVAL;嵌套 epoll fd →ELOOP - epoll fd 可 poll:支持
poll()/select()监控 epoll fd - 错误码修正:read/write 返回
EINVAL,null 指针返回EFAULT - pipe 修复:读/写达到请求长度或缓冲区耗尽时立即返回
- pipe readiness version 拆分:读端和写端分别维护版本号,仅在 Empty↔Normal↔Full 状态转换时递增
- 关闭 fd 清理:
retain()自动清理已关闭 fd 的 watch - C 测试:覆盖 LT、ET、oneshot、DEL、参数校验、fd 关闭、部分消费后不重复投递
- Starry smoke 超时:riscv64/aarch64/loongarch64 从 5s 调至 15s
与前次 Review 对比
- ✅ errno 不匹配(EINVAL/ELOOP)→ 4060161/2953893ea 修复
- ✅ ET
epoll_wait(timeout=0)挂起 → 16ec0f5 通过 pipe 读写边界修复 - ✅ 强 Arc 引用阻止 fd 关闭 → 698af2a 通过 Weak 引用修复
- ✅ drain 后不经过空 wait 再写漏事件 → fef78a3 通过 readiness_version 修复
- ✅ EPOLLET 部分消费假阳性 → 1f27cdd 通过 pipe version 拆分修复
关键修复分析:pipe readiness version 拆分(commit 1f27cdd)
前两次 ZR233 和 mai-team-app[bot] 的 review(在 6cd64a4/fef78a344 上)指出 readiness_version 在每次 read/write 时递增会导致 EPOLLET 部分消费假阳性。最新提交 1f27cdd 彻底解决了此问题:
- 版本拆分:
readiness_version→read_readiness_version+write_readiness_version - 仅在状态转换时递增:
refresh_readiness_versions(old_status)只在is_readable(old) != is_readable(new)或is_writable(old) != is_writable(new)时递增对应版本 - 移除了
notify_readiness_changed():不再在每次成功 read/write 后调用
场景验证(与 ZR233 指出的失败场景对比):
// 场景:EPOLLET,write "abc" → epoll_wait → read 1 → epoll_wait(timeout=0) → 应返回 0
write("abc"): Empty→Normal, read_version: 0→1
epoll_wait: version(1)!=last(0) → EPOLLIN ✅, last_ready=EPOLLIN, last_version=1
read(1): Normal→Normal, 版本不变, read_version 仍为 1
epoll_wait(timeout=0): version(1)==last(1) → EPOLLIN & !EPOLLIN = 0 ✅ 无误报
// 场景:drain 后不经过空 wait 直接写
read(2): Normal→Empty, read_version: 1→2
write("d"): Empty→Normal, read_version: 2→3
epoll_wait(timeout=0): version(3)!=last(1) → EPOLLIN ✅ 正确投递新边沿
本地验证
- ✅
cargo fmt --check通过 - ✅
git diff --check origin/dev...HEAD通过(无空白错误) - ✅
cargo xtask clippy --package ax-posix-api15/15 checks passed - ✅
cargo xtask clippy --package ax-libc16/16 checks passed
CI 状态(run 26947394133)
已通过:
- ✅ Detect changed paths
- ✅ Check formatting / run_host
- ✅ Run sync-lint / run_host
- ✅ Test axvisor self-hosted x86_64 / run_host
- ✅ Test axvisor self-hosted board / run_host
- ✅ Test axvisor aarch64/riscv64/loongarch64 qemu
- ✅ Test axvisor x86_64 svm hosted
- ✅ Test with std / run_host
仍在运行:
- 🔄 Run clippy / run_host
- 🔄 Test starry aarch64/riscv64/x86_64 qemu
- 🔄 Test arceos aarch64/riscv64/loongarch64 qemu
- 🔄 Test starry self-hosted board
非阻塞建议
is_epoll_fileArc clone:file.clone().into_any().downcast()多了一次 Arc clone,FileLiketrait 若提供as_any()可优化EPOLLHUP未上报:写端关闭后当前不会报告EPOLLHUP,依赖此事件的应用行为会偏离 Linuxepoll_create(0)语义差异:Linux 忽略 hint 参数允许成功,本 PR 返回 EINVAL(epoll_create1为推荐 API,可接受)- pipe 单一
readiness_version:PollState只有一个readiness_version字段,pipe 根据self.readable()选择读端或写端版本。若同一 watch 同时监听 EPOLLIN|EPOLLOUT,只跟踪一侧版本,理论上可产生误报。实际使用中很少见,可后续优化。
重复/冲突分析
- base 分支无等价 epoll 增强
- 无其他 open PR 涉及 ArceOS epoll/pipe 改动
- 无合并冲突
结论
所有前次 review 阻塞问题已修复(包括 ZR233 指出的强引用生命周期和 EPOLLET 部分消费问题)。pipe readiness version 拆分方案正确,版本仅在 Empty↔Normal↔Full 状态转换时递增,EPOLLET 语义与 Linux 一致。代码格式、clippy 和已完成的 CI 测试均通过。APPROVE。
Powered by mimo-v2.5-pro
ZR233
left a comment
There was a problem hiding this comment.
已复核最新提交 c0ceaa7,之前关于已关闭 fd 生命周期和 EPOLLET 部分读取/排空后再次写入的阻塞问题已修复。当前实现用 Weak 避免 epoll watch 延长 fd 生命周期,并把 pipe 的 read/write readiness version 拆分到实际 readiness 状态转换上,能够覆盖“部分读取不重复投递”和“排空后未经过空 wait 又重新写入仍能投递”的语义。
本地验证:
- git diff --check origin/dev...HEAD
- cargo fmt --check
- cargo xtask clippy --package ax-posix-api
- cargo xtask clippy --package ax-libc
- cargo xtask arceos test qemu --arch x86_64 --test-group c --test-case epoll
另外查看了当前 CI run 26947394133,格式、clippy、ArceOS 各架构 QEMU、Starry 各相关架构 QEMU、std 测试和可运行的板级/host job 均已通过;Starry smoke timeout 配置的影响由这些 CI job 覆盖。开放 PR 中没有发现与本 PR 相同实现面的未合并重复改动,#921 属于 Starry 网络 epoll_pwait/netlink 方向,和这里的 ArceOS epoll/pipe ready queue 不冲突。当前看可以合入。
ZR233
left a comment
There was a problem hiding this comment.
审查通过。
本 PR 显著改善了 ArceOS 的 epoll 实现,新增 EPOLLET(边缘触发)、EPOLLONESHOT、epoll_create1 支持,修复了 fd 关闭后 watch 泄漏(Weak 引用)、pipe 读写边界 hang、以及错误路径处理。通过将 pipe 的 readiness_version 拆分为独立的读端/写端版本,正确解决了 EPOLLET 部分消费假阳性问题。新增的 C 测试覆盖了参数校验、LT/ET/ONESHOT 行为以及关闭已注册 fd 的回归场景。
注意:PR 当前存在与 dev 分支的合并冲突,需要 rebase 解决。另外目前仅 pipe 实现了有意义的 readiness_version,TCP socket 等其他 fd 的 ET 行为不完整,建议后续补充。
非阻塞建议:is_epoll_file() 每次 clone Arc 仅用于类型检查,有性能开销,建议改为 as_any() 方式。
c0ceaa7 to
483ab67
Compare
There was a problem hiding this comment.
PR #1034 Review: feat(ax-posix-api): improve ArceOS epoll semantics
变更概述
本 PR 大幅改进 ArceOS POSIX epoll 实现,包括:
- WatchedEvent + Weak 引用:为每个被监听 fd 保存
Weak<dyn FileLike>、last_ready、last_readiness_version、disabled状态 - EPOLLET(边缘触发):通过 split
readiness_version(读端/写端分别维护)+ 仅在RingBufferStatus实际状态转换时递增,精确检测边沿 - EPOLLONESHOT:触发后设
disabled = true,需EPOLL_CTL_MOD重新激活 - epoll_create1:新增
EPOLL_CLOEXEC支持,含标志验证 - epoll 自引用检查:
epfd == fd→EINVAL;嵌套 epoll fd →ELOOP,符合 Linux 语义 - epoll fd 可 poll:
poll()返回PollState { readable: has_ready_events(), writable: false } - 错误码修正:read/write 返回
EINVAL(原为ENOSYS),null 指针检查返回EFAULT - pipe 修复:读/写达到请求长度或缓冲区耗尽时立即返回;split 读端/写端 readiness version
- 关闭 fd 清理:
retain()自动清理已关闭 fd 的 watch 条目 - C 测试:新增
test-suit/arceos/c/epoll/,覆盖 LT、ET、oneshot、DEL、参数校验、关闭已注册 fd、以及 drain→write 边沿回归
与前次 Review 对比
所有前次 review 阻塞问题已修复:
- ✅ EINVAL/ELOOP errno 不匹配 → 已修复
- ✅ edge-triggered
epoll_wait(timeout=0)挂起 → 已通过 pipe 读写边界修复 - ✅ 强
Arc引用导致已关闭 fd 对象继续存活 → 已通过Weak引用修复 - ✅ drain 后不经过空 wait 再写漏掉新边沿 → 已通过
readiness_version机制修复 - ✅ EPOLLET 部分消费假阳性 → 已通过 split read/write readiness version + 仅在 RingBufferStatus 状态转换时递增修复
实现分析
EPOLLET 边沿检测正确性(核心变更):
pipe.rs 中 refresh_readiness_versions(old_status) 仅在 is_readable(old_status) != is_readable(self.status) 时递增 read_readiness_version,仅在 is_writable(old_status) != is_writable(self.status) 时递增 write_readiness_version。这意味着:
- 部分 read(Normal→Normal):不递增版本,
deliverable_events中readiness_version == last_readiness_version→ready & !last_ready = 0,不会重复投递 ✅ - 全部 drain(Normal→Empty)再 write(Empty→Normal):版本递增两次,
readiness_version != last_readiness_version→ 返回ready,正确投递新边沿 ✅ - 写满 pipe(Normal→Full)再 read(Full→Normal):
write_readiness_version递增,写端 EPOLLOUT 正确恢复 ✅
其他设计要点:
Weak<dyn FileLike>+is_closed()通过strong_count() == 0检测 fd 生命周期结束epoll_ctl两层检查:epfd == fd→EINVAL(Linux 语义),is_epoll_file→ELOOP(嵌套 epoll)test_edge_triggered_epoll覆盖了 drain→write 不经过空 wait 的回归场景test_registered_fd_close覆盖了关闭已注册 fd 后 epoll 不再报告以及 pipe 读端可观察 EOF
验证结果
- ✅
cargo fmt --check通过 - ✅
git diff --check origin/dev...HEAD通过(无空白错误) - ✅
cargo clippy --features epoll -D warnings通过(ax-posix-api 及依赖全部编译通过) - CI 状态:最新 head
483ab6794的所有 check run 显示skipped。这是预期行为:CI workflow 使用路径过滤,本 PR 仅修改 ArceOS POSIX API 和test-suit/arceos/,StarryOS/axvisor/board 等 job 不匹配路径条件。前次 mai-team-app 在提交16ec0f5f上已本地验证 QEMU 测试:cargo xtask arceos test qemu --arch x86_64 --test-group c --test-case epollPASS(34.11s),pthread-pipePASS(28.77s)。 - 无
[patch.crates-io]覆盖。
重复/冲突分析
- base 分支无等价 ArceOS epoll 增强实现
- 无其他 open PR 触及
os/arceos/api/arceos_posix_api/的 epoll 或 pipe 代码 - PR #921 为 StarryOS 侧 epoll_pwait(内核路径),与本 PR 的 ArceOS POSIX 层 epoll 无重叠
- 无合并冲突(
mergeable_state: clean)
非阻塞建议(未来改进)
is_epoll_fileArc clone:file.clone().into_any().downcast()多了一次 Arc clone。如果FileLiketrait 提供as_any()方法可直接借用,但功能正确。EPOLLHUP未在正常 poll 路径上报:current_ready()仅在poll()返回Err时设置EPOLLERR,成功路径不会设置EPOLLHUP。不影响当前测试,但依赖EPOLLHUP的应用行为会偏离 Linux 语义。epoll_create(0)返回 EINVAL:Linux 允许epoll_create(0)成功(hint 被忽略),本 PR 返回 EINVAL。epoll_create1为推荐 API,可接受。
结论
所有前次 review 阻塞问题已修复,EPOLLET 边沿检测语义正确(split readiness version + 仅在状态转换时递增),测试覆盖完整(含 drain→write 边沿回归),无新增阻塞问题。APPROVE。
Powered by mimo-v2.5-pro
问题
ArceOS POSIX epoll 的行为需要更接近 Linux 语义,尤其是
EPOLLET、EPOLLONESHOT、epoll 自引用、已关闭 fd 清理和 pipe 读写边界这些路径。最近的 review 还指出:pipe 在部分读取后仍保持 readable 时,EPOLLETwatch 不能因为一次普通 read 造成的内部版本变化而重复投递EPOLLIN。远端 CI 还暴露出 Starry
riscv64host smoke 用例在启动到 shell 附近后被 5 秒超时截断的问题;该 smoke 用例本身只执行pwd && echo 'All tests passed!',失败点是超时预算过紧,而不是业务命令失败。修改
epoll_create1(EPOLL_CLOEXEC)的参数校验,并补齐epoll_ctl的 null event、自引用和嵌套 epoll 错误路径。EPOLLET投递逻辑,结合 ready bits 与 readiness version 处理没有经过空 wait 的 drain -> write 场景。Empty/Normal/Full导致可读或可写状态真正变化时递增;部分 read 后仍 readable 的路径不会再制造新的读端 edge。epoll_wait(timeout=0)不重复返回的回归场景。qemu-smp1/smoke的riscv64、aarch64、loongarch64超时从 5 秒调整到 15 秒,与已有的x86_64smoke 配置保持一致。方案逻辑
epoll_wait之间经历了 not-ready -> ready 转换,则 readiness version 变化会使新的 edge 被投递。Normal -> Full这类只影响可写性的变化误唤醒只监听EPOLLIN的读端 watch。EPOLLONESHOT在投递后禁用 watch,只有EPOLL_CTL_MOD会重新激活。验证
cargo fmtcargo fmt --checkgit diff --check upstream/dev...HEADcargo xtask clippy --package ax-posix-api:15/15 checks passedcargo xtask clippy --package ax-libc:16/16 checks passed本地曾在 LF-only 临时副本中尝试
STARRY_APK_REGION=us cargo xtask starry test qemu --arch riscv64 --test-case smoke;该环境的debugfs 1.45.5写入 rootfs 后缺失/etc/apk/repositories与/etc/resolv.conf,和 CI 的 rootfs 流程不等价,因此 Starry smoke 最终以远端 CI 结果为准。