Skip to content

feat(ax-posix-api): improve ArceOS epoll semantics#1034

Merged
ZR233 merged 8 commits into
rcore-os:devfrom
cqwhfhh:feature/arceos-epoll-ready-queue
Jun 5, 2026
Merged

feat(ax-posix-api): improve ArceOS epoll semantics#1034
ZR233 merged 8 commits into
rcore-os:devfrom
cqwhfhh:feature/arceos-epoll-ready-queue

Conversation

@cqwhfhh

@cqwhfhh cqwhfhh commented May 29, 2026

Copy link
Copy Markdown
Contributor

问题

ArceOS POSIX epoll 的行为需要更接近 Linux 语义,尤其是 EPOLLETEPOLLONESHOT、epoll 自引用、已关闭 fd 清理和 pipe 读写边界这些路径。最近的 review 还指出:pipe 在部分读取后仍保持 readable 时,EPOLLET watch 不能因为一次普通 read 造成的内部版本变化而重复投递 EPOLLIN

远端 CI 还暴露出 Starry riscv64 host smoke 用例在启动到 shell 附近后被 5 秒超时截断的问题;该 smoke 用例本身只执行 pwd && echo 'All tests passed!',失败点是超时预算过紧,而不是业务命令失败。

修改

  1. 为 epoll watch 保存被监听 fd 的弱引用、上次 ready bits、上次 readiness version 和 oneshot disabled 状态,避免 epoll 持有 fd 强引用导致 close 后 watch 仍存活。
  2. 支持 epoll_create1(EPOLL_CLOEXEC) 的参数校验,并补齐 epoll_ctl 的 null event、自引用和嵌套 epoll 错误路径。
  3. 调整 EPOLLET 投递逻辑,结合 ready bits 与 readiness version 处理没有经过空 wait 的 drain -> write 场景。
  4. 修正 pipe 读写边界,避免读写达到请求长度或缓冲区边界时继续等待。
  5. 将 pipe readiness version 拆成读端和写端分别维护,只在 Empty/Normal/Full 导致可读或可写状态真正变化时递增;部分 read 后仍 readable 的路径不会再制造新的读端 edge。
  6. 新增 ArceOS C epoll 测试,覆盖参数校验、LT、ET、ONESHOT、删除 watch、关闭已注册 fd,以及部分消费后 epoll_wait(timeout=0) 不重复返回的回归场景。
  7. 将 Starry qemu-smp1/smokeriscv64aarch64loongarch64 超时从 5 秒调整到 15 秒,与已有的 x86_64 smoke 配置保持一致。

方案逻辑

  • LT watch 直接返回当前 ready bits,保持 level-triggered 语义。
  • ET watch 在 ready bits 新增时返回事件;如果 fd 在两次 epoll_wait 之间经历了 not-ready -> ready 转换,则 readiness version 变化会使新的 edge 被投递。
  • pipe 的读端和写端 readiness version 分开维护,避免写端 Normal -> Full 这类只影响可写性的变化误唤醒只监听 EPOLLIN 的读端 watch。
  • EPOLLONESHOT 在投递后禁用 watch,只有 EPOLL_CTL_MOD 会重新激活。
  • Starry smoke 已经能启动到 shell 并等待执行初始化命令,放宽超时只给慢 host runner 留出启动和 shell 交互余量,不改变用例断言。

验证

  • cargo fmt
  • cargo fmt --check
  • git diff --check upstream/dev...HEAD
  • cargo xtask clippy --package ax-posix-api:15/15 checks passed
  • cargo 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 结果为准。

@mai-team-app mai-team-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #1034 Review: feat(ax-posix-api): improve ArceOS epoll semantics

变更概述

本 PR 重构了 ArceOS POSIX epoll 实现,主要包括:

  1. WatchedEvent 结构体:引入独立的 watch 状态,替代原来仅存储 epoll_event 的方式,支持 per-fd 跟踪 last_readydisabled 等状态
  2. EPOLLET(边缘触发)支持deliverable_events 通过 ready & !last_ready 计算新事件,语义正确
  3. EPOLLONESHOT 支持:事件触发后设置 disabled = true,需 EPOLL_CTL_MOD 重新激活
  4. epoll_create1:新增 EPOLL_CLOEXEC 标志支持,包含标志验证
  5. epoll 自引用检查epoll_ctl(ADD, epfd) 返回 ELOOP,符合 Linux 语义
  6. epoll fd 的 poll 支持:使 epoll fd 可被 poll/select 监控
  7. 错误码修正:read/write 返回 EINVAL(原来为 ENOSYS),null 指针检查等
  8. ArceOS C 测试:新增 test-suit/arceos/c/epoll/ 测试用例,覆盖 level-triggered、edge-triggered、oneshot 和参数校验

实现分析

  • Edge-triggered 语义正确:通过 last_ready 跟踪上一次状态,ready & !last_ready 检测新边沿
  • Oneshot 语义正确:触发后设 disabled=trueupdate() 重置 disabledlast_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

Comment thread test-suit/arceos/c/epoll/c/main.c

@mai-team-app mai-team-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 语义不完全一致)。

非阻塞建议

  1. is_epoll_file 可优化:当前 file.clone().into_any().downcast() 多了一次 Arc::clone。如果 FileLike trait 有 as_any() 方法,可以直接借用而无需 clone。

  2. EPOLLHUP 未上报:当 pipe 的写端关闭时,Linux 会自动返回 EPOLLHUP | EPOLLIN,但当前 current_ready() 仅在 poll() 返回 Err 时设置 EPOLLERR,成功路径不会设置 EPOLLHUP。这不影响当前测试,但会导致依赖 EPOLLHUP 的应用行为不符合预期。

Powered by mimo-v2.5-pro

@mai-team-app mai-team-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_allcurrent_ready() 返回 ready=EPOLLINdeliverable_events 因 edge-triggered 计算 EPOLLIN & !EPOLLIN = 0,应返回 0 事件。随后 timeout=0 的 deadline 检查应立即返回 Ok(0)。但 epoll_wait 未返回,120s 后 QEMU 超时。

可能原因(需进一步排查):

  1. poll_all 内部 Mutex 锁获取可能死锁(但代码分析未发现锁序反转)
  2. wall_time() 或 deadline 比较在特定条件下不满足退出条件
  3. sys_sched_yield() 导致任务未被重新调度(不应影响 timeout=0 的情况)

建议排查方向:在 epoll_wait 的循环中添加迭代计数或 debug 输出,确认 poll_all 的返回值和 deadline 检查结果。

非阻塞建议

  1. is_epoll_file 可优化file.clone().into_any().downcast() 多了一次 Arc clone。如果 FileLike trait 提供 as_any() 方法可直接借用。

  2. 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

@mai-team-app mai-team-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #1034 Review: feat(ax-posix-api): improve ArceOS epoll semantics

变更概述

本 PR 大幅改进了 ArceOS POSIX epoll 实现,包括:

  1. WatchedEvent 结构体:为每个被监听 fd 保存 FileLike 引用、last_readydisabled 状态,替代原来仅存 epoll_event 的方式
  2. EPOLLET(边缘触发):通过 ready & !last_ready 计算新边沿事件,语义正确
  3. EPOLLONESHOT:触发后设 disabled = true,需 EPOLL_CTL_MOD 重新激活
  4. epoll_create1:新增 EPOLL_CLOEXEC 标志支持,包含标志验证
  5. epoll 自引用检查epfd == fdEINVAL;嵌套 epoll fd → ELOOP,符合 Linux 语义
  6. epoll fd 的 poll 支持:使 epoll fd 可被 poll/select 监控为 readable
  7. 错误码修正:read/write 返回 EINVAL(原为 ENOSYS),null 指针检查等
  8. pipe 修复:读/写达到请求长度或缓冲区耗尽时立即返回,避免 ET epoll drain pipe 时卡死
  9. 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=trueupdate() 重置 disabledlast_ready
  • epoll_ctl 两层检查设计合理:epfd == fdEINVAL(Linux 语义),is_epoll_fileELOOP(嵌套 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 epoll PASS(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-pipe PASS(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,均不相关)
  • 无合并冲突

非阻塞建议

  1. is_epoll_file 可优化:当前 file.clone().into_any().downcast() 多了一次 Arc::clone。如果 FileLike trait 提供 as_any() 方法可直接借用。
  2. EPOLLHUP 未在正常 poll 路径上报current_ready() 仅在 poll() 返回 Err 时设置 EPOLLERR,成功路径不会设置 EPOLLHUP。不影响当前测试,但依赖 EPOLLHUP 的应用行为会偏离 Linux 语义。
  3. 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 ZR233 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

本次重点按死锁/永久等待风险检查了 epoll 与 pipe 的生命周期路径。现有实现中 WatchedEvent 对被监听 fd 持有强 Arc<dyn FileLike>,这会让已经 close() 的 fd 对象继续存活;对 ArceOS 的 pipe 来说,读端 EOF 依赖写端对象真正释放,因此会出现关闭写端后读端仍认为写端存在、继续等待的情况。

我在当前 head 上做了验证:正式用例 cargo fmt --checkcargo xtask clippy --package ax-posix-apicargo xtask clippy --package ax-libccargo 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。

Comment thread os/arceos/api/arceos_posix_api/src/imp/io_mpx/epoll.rs Outdated
@ZR233
ZR233 requested a review from shilei-massclouds June 1, 2026 09:45
@shilei-massclouds

Copy link
Copy Markdown
Contributor

一个建议: 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。

@mai-team-app mai-team-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #1034 Review: feat(ax-posix-api): improve ArceOS epoll semantics

变更概述

本 PR 大幅改进了 ArceOS POSIX epoll 实现,包括:

  1. WatchedEvent + Weak 引用:为每个被监听 fd 保存 Weak<dyn FileLike>last_readydisabled 状态,替代原来仅存 epoll_event 的方式
  2. EPOLLET(边缘触发):通过 ready & !last_ready 计算新边沿事件,语义正确
  3. EPOLLONESHOT:触发后设 disabled = true,需 EPOLL_CTL_MOD 重新激活
  4. epoll_create1:新增 EPOLL_CLOEXEC 标志支持,包含标志验证
  5. epoll 自引用检查epfd == fdEINVAL;嵌套 epoll fd → ELOOP,符合 Linux 语义
  6. epoll fd 可 pollpoll() 返回 PollState { readable: has_ready_events(), writable: false }
  7. 错误码修正:read/write 返回 EINVAL(原为 ENOSYS),null 指针检查返回 EFAULT
  8. pipe 修复:读/写达到请求长度或缓冲区耗尽时立即返回,避免 ET epoll drain pipe 时卡死
  9. 关闭 fd 清理poll_all()/has_ready_events()/control()retain(|_, watch| !watch.is_closed()) 自动清理已关闭 fd 的 watch 条目
  10. 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() == 0current_ready()upgrade() 失败返回 0,retain() 清理已关闭条目。这与 ZR233 建议的修复方向一致。
  • Edge-triggered 语义正确last_ready 跟踪上一次状态,ready & !last_ready 检测新边沿
  • Oneshot 语义正确:触发后设 disabled=trueupdate() 重置 disabledlast_ready
  • epoll_ctl 两层检查合理epfd == fdEINVAL(Linux 语义),is_epoll_fileELOOP(嵌套 epoll)
  • 事件常量分层清晰:READ/WRITE/ERROR/BEHAVIOR/RETURN
  • pipe 修复符合 POSIXread() 已读到数据后应立即返回,不应继续阻塞等待更多数据

验证

  • 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 epoll PASS(34.11s),cargo xtask arceos test qemu --arch x86_64 --test-group c --test-case pthread-pipe PASS(28.77s)
  • 本地环境无法运行 QEMU 测试(缺少交叉编译器和 QEMU),依赖前次验证结果

重复/冲突分析

  • base 分支无等价 epoll 增强实现
  • 无其他 open PR 涉及 epoll、pipe 或 ArceOS POSIX API 改动
  • 无合并冲突

已知限制(非阻塞)

  1. is_epoll_file Arc clonefile.clone().into_any().downcast() 多了一次 Arc clone。如果 FileLike trait 提供 as_any() 方法可直接借用,但功能正确
  2. EPOLLHUP 未在正常 poll 路径上报current_ready() 仅在 poll() 返回 Err 时设置 EPOLLERR,成功路径不会设置 EPOLLHUP。不影响当前测试,但依赖 EPOLLHUP 的应用行为会偏离 Linux 语义
  3. epoll_create(0) 返回 EINVAL:Linux 允许 epoll_create(0) 成功(hint 被忽略),本 PR 返回 EINVAL。epoll_create1 为推荐 API,可接受

结论

所有前次 review 阻塞问题已修复(包括 ZR233 指出的强引用生命周期问题),实现语义正确,测试覆盖完整,无新增阻塞问题。APPROVE

Powered by mimo-v2.5-pro

Comment thread os/arceos/api/arceos_posix_api/src/imp/io_mpx/epoll.rs
@mai-team-app
mai-team-app Bot requested a review from ZR233 June 2, 2026 07:21

@ZR233 ZR233 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

本轮重新审查了最新提交 698af2a37ba3998d90dfc12b8e50541716c549cc

此前提到的 epoll watch 持有强 Arc<dyn FileLike> 导致 fd 关闭后对象生命周期被 epoll 延长的问题,这版已经改成 Weak,并新增了 test_registered_fd_close() 覆盖;我已把旧的对应 review thread 标记为 resolved。

当前还剩一个阻塞问题:EPOLLETlast_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 passed
  • cargo 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 逻辑。

Comment thread os/arceos/api/arceos_posix_api/src/imp/io_mpx/epoll.rs

@ZR233 ZR233 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

复审 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 --check
  • cargo 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 日志存储连接失败,未能进一步分类。

Comment thread os/arceos/api/arceos_posix_api/src/imp/io_mpx/epoll.rs

@ZR233 ZR233 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

复审 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...HEADcargo fmt --check 通过。由于本次提交无代码差异,未重复跑上次已经通过的 clippy/QEMU;远端 CI run 26936437920 评审时仍有多项 job 在运行中。

@mai-team-app mai-team-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #1034 Review: feat(ax-posix-api): improve ArceOS epoll semantics

变更概述

本 PR 大幅改进了 ArceOS POSIX epoll 实现,包括:

  1. WatchedEvent + Weak 引用:为每个被监听 fd 保存 Weak<dyn FileLike>last_readylast_readiness_versiondisabled 状态
  2. EPOLLET(边缘触发):通过 readiness_version + last_ready 组合检测边沿事件
  3. EPOLLONESHOT:触发后设 disabled = true,需 EPOLL_CTL_MOD 重新激活
  4. epoll_create1:新增 EPOLL_CLOEXEC 标志支持,包含标志验证
  5. epoll 自引用检查epfd == fdEINVAL;嵌套 epoll fd → ELOOP,符合 Linux 语义
  6. epoll fd 可 poll:使 epoll fd 可被 poll/select 监控
  7. 错误码修正:read/write 返回 EINVAL,null 指针检查返回 EFAULT
  8. pipe 修复:读/写达到请求长度或缓冲区耗尽时立即返回
  9. 关闭 fd 清理retain() 自动清理已关闭 fd 的 watch 条目
  10. 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)也当作新边沿重复投递。

复现场景

  1. pipe 写入 "abc",EPOLLET watch EPOLLIN
  2. epoll_wait(timeout=0) → 返回 EPOLLIN(正确),last_ready=EPOLLIN, last_readiness_version=V1
  3. read(buf, 1) 只读 1 字节(pipe 仍有数据)→ readiness_version 递增到 V2
  4. epoll_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 增强实现
  • 无合并冲突

非阻塞建议

  1. is_epoll_file 可优化file.clone().into_any().downcast() 多了一次 Arc clone,如果 FileLike trait 提供 as_any() 方法可直接借用
  2. EPOLLHUP 未在正常 poll 路径上报current_ready() 仅在 poll() 返回 Err 时设置 EPOLLERR,成功路径不会设置 EPOLLHUP
  3. epoll_create(0) 返回 EINVAL:Linux 允许 epoll_create(0) 成功(hint 被忽略),epoll_create1 为推荐 API,可接受

Powered by glm-5.1

Comment thread os/arceos/api/arceos_posix_api/src/imp/io_mpx/epoll.rs
Comment thread os/arceos/api/arceos_posix_api/src/imp/pipe.rs Outdated

@mai-team-app mai-team-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #1034 复审:feat(ax-posix-api): improve ArceOS epoll semantics

变更概述

本 PR 大幅改进 ArceOS POSIX epoll 实现,包括:

  1. WatchedEvent + Weak 引用:保存 Weak<dyn FileLike>last_readylast_readiness_versiondisabled 状态
  2. EPOLLET(边缘触发):通过 readiness_version + last_ready 组合检测边沿事件
  3. EPOLLONESHOT:触发后设 disabled=true,需 EPOLL_CTL_MOD 重新激活
  4. epoll_create1:新增 EPOLL_CLOEXEC 标志支持
  5. epoll 自引用检查epfd == fdEINVAL;嵌套 epoll fd → ELOOP
  6. epoll fd 可 pollpoll() 返回 PollState { readable: has_ready_events() }
  7. pipe 修复:读/写达到请求长度或缓冲区耗尽时立即返回
  8. 关闭 fd 清理retain() 自动清理已关闭 fd 的 watch 条目
  9. pipe readiness_version:读端和写端分开维护,仅在 RingBufferStatus 状态转换时递增
  10. 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↔NormalFull↔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=trueready != 0 时视为新边沿
  • 方案 B:将 readiness_versionu64 改为 (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 epoll PASS
  • cargo xtask arceos test qemu --arch x86_64 --test-group c --test-case pthread-pipe PASS

本次本地验证:

  • 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)均不相关
  • 无合并冲突

非阻塞建议

  1. is_epoll_file Arc clone 优化file.clone().into_any().downcast() 多了一次 Arc clone。如果 FileLike trait 提供 as_any() 方法可直接借用
  2. EPOLLHUP 未在正常 poll 路径上报current_ready() 仅在 poll() 返回 Err 时设置 EPOLLERR,成功路径不会设置 EPOLLHUP。不影响当前测试,但依赖 EPOLLHUP 的应用行为会偏离 Linux 语义
  3. epoll_create(0) 返回 EINVAL:Linux 允许 epoll_create(0) 成功(hint 被忽略),本 PR 返回 EINVAL。epoll_create1 为推荐 API,可接受
  4. TCP/UDP socket 的 readiness_version:目前 TcpSocketUdpSocket 始终返回 readiness_version: 0。如果 epoll 需要对 socket 也支持 ET 语义,后续需要为 socket 实现类似的版本追踪

结论

整体设计合理,Weak 引用修复了 fd 生命周期问题,readiness_version 机制的方向正确。但 EPOLLET 在部分消费场景下会产生假阳性重复投递事件,需要修复 deliverable_events 的边沿判定逻辑。此外,当前测试未覆盖部分消费场景,建议补充回归测试。

Powered by mimo-v2.5-pro

Comment thread os/arceos/api/arceos_posix_api/src/imp/io_mpx/epoll.rs

@mai-team-app mai-team-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #1034 Review: feat(ax-posix-api): improve ArceOS epoll semantics

变更概述

本 PR 大幅改进 ArceOS POSIX epoll 实现,包括:

  1. WatchedEvent + Weak 引用:保存 Weak<dyn FileLike>last_readylast_readiness_versiondisabled 状态
  2. EPOLLET(边缘触发):通过 readiness_version + last_ready 组合检测边沿
  3. EPOLLONESHOT:触发后设 disabled=true,需 EPOLL_CTL_MOD 重新激活
  4. epoll_create1:新增 EPOLL_CLOEXEC 支持,含标志校验
  5. epoll 自引用epfd == fdEINVAL;嵌套 epoll fd → ELOOP
  6. epoll fd 可 poll:支持 poll()/select() 监控 epoll fd
  7. 错误码修正:read/write 返回 EINVAL,null 指针返回 EFAULT
  8. pipe 修复:读/写达到请求长度或缓冲区耗尽时立即返回
  9. pipe readiness version 拆分:读端和写端分别维护版本号,仅在 Empty↔Normal↔Full 状态转换时递增
  10. 关闭 fd 清理retain() 自动清理已关闭 fd 的 watch
  11. C 测试:覆盖 LT、ET、oneshot、DEL、参数校验、fd 关闭、部分消费后不重复投递
  12. 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 彻底解决了此问题:

  1. 版本拆分readiness_versionread_readiness_version + write_readiness_version
  2. 仅在状态转换时递增refresh_readiness_versions(old_status) 只在 is_readable(old) != is_readable(new)is_writable(old) != is_writable(new) 时递增对应版本
  3. 移除了 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-api 15/15 checks passed
  • cargo xtask clippy --package ax-libc 16/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

非阻塞建议

  1. is_epoll_file Arc clonefile.clone().into_any().downcast() 多了一次 Arc clone,FileLike trait 若提供 as_any() 可优化
  2. EPOLLHUP 未上报:写端关闭后当前不会报告 EPOLLHUP,依赖此事件的应用行为会偏离 Linux
  3. epoll_create(0) 语义差异:Linux 忽略 hint 参数允许成功,本 PR 返回 EINVAL(epoll_create1 为推荐 API,可接受)
  4. pipe 单一 readiness_versionPollState 只有一个 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 ZR233 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已复核最新提交 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 ZR233 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

审查通过。

本 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() 方式。

@ZR233
ZR233 force-pushed the feature/arceos-epoll-ready-queue branch from c0ceaa7 to 483ab67 Compare June 5, 2026 01:58

@mai-team-app mai-team-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #1034 Review: feat(ax-posix-api): improve ArceOS epoll semantics

变更概述

本 PR 大幅改进 ArceOS POSIX epoll 实现,包括:

  1. WatchedEvent + Weak 引用:为每个被监听 fd 保存 Weak<dyn FileLike>last_readylast_readiness_versiondisabled 状态
  2. EPOLLET(边缘触发):通过 split readiness_version(读端/写端分别维护)+ 仅在 RingBufferStatus 实际状态转换时递增,精确检测边沿
  3. EPOLLONESHOT:触发后设 disabled = true,需 EPOLL_CTL_MOD 重新激活
  4. epoll_create1:新增 EPOLL_CLOEXEC 支持,含标志验证
  5. epoll 自引用检查epfd == fdEINVAL;嵌套 epoll fd → ELOOP,符合 Linux 语义
  6. epoll fd 可 pollpoll() 返回 PollState { readable: has_ready_events(), writable: false }
  7. 错误码修正:read/write 返回 EINVAL(原为 ENOSYS),null 指针检查返回 EFAULT
  8. pipe 修复:读/写达到请求长度或缓冲区耗尽时立即返回;split 读端/写端 readiness version
  9. 关闭 fd 清理retain() 自动清理已关闭 fd 的 watch 条目
  10. 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.rsrefresh_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_eventsreadiness_version == last_readiness_versionready & !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 == fdEINVAL(Linux 语义),is_epoll_fileELOOP(嵌套 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 epoll PASS(34.11s),pthread-pipe PASS(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

非阻塞建议(未来改进)

  1. is_epoll_file Arc clonefile.clone().into_any().downcast() 多了一次 Arc clone。如果 FileLike trait 提供 as_any() 方法可直接借用,但功能正确。
  2. EPOLLHUP 未在正常 poll 路径上报current_ready() 仅在 poll() 返回 Err 时设置 EPOLLERR,成功路径不会设置 EPOLLHUP。不影响当前测试,但依赖 EPOLLHUP 的应用行为会偏离 Linux 语义。
  3. 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

@ZR233
ZR233 merged commit c835b6e into rcore-os:dev Jun 5, 2026
49 checks passed
This was referenced Jun 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants