Fix EPOLLONESHOT leaks in epoll bridge

Merged
JS
6mo ago
+568-16

Two bugs surfaced while auditing EPOLLET fidelity on the kqueue backend, both reproducible against Linux/qemu ground truth:

  1. sys_epoll_pwait result loop did not check oneshot_armed. With multi-filter registrations (e.g. EPOLLIN | EPOLLOUT | EPOLLONESHOT), EV_ONESHOT only removed the filter that fired; a surviving filter could fire later and was reported, violating Linux semantics where the fd stays disarmed until EPOLL_CTL_MOD re-arms it.

  2. sys_epoll_ctl MOD pre-delete batched two EV_DELETE changes in one kevent call with NULL eventlist. When the first delete fails ENOENT (the fired filter was already removed by EV_ONESHOT), kqueue stops processing and the second delete leaks the survivor. Issue each delete in its own kevent call so they are independent.

Also tighten the EPOLLET registration comment to describe what divergence actually requires (a unified drain signal across every data-consuming path, which the bridge does not maintain), and lock the contract in with tests/test-epoll-edge.c . Each new ONESHOT test was confirmed to fail on pre-fix elfuse and pass on post-fix; both pass against Linux/qemu.


Summary by cubic

Fixes EPOLLONESHOT leaks and aligns EPOLLET/ONESHOT behavior in the kqueue epoll bridge with Linux. Prevents spurious events and stale filters; adds targeted tests to lock in behavior.

  • Bug Fixes
    • epoll_wait path now guards on ONESHOT state so multi-filter registrations stay fully disarmed until EPOLL_CTL_MOD re-arms.
    • EPOLL_CTL_MOD issues EV_DELETE per filter instead of batching, avoiding ENOENT aborts that leak surviving filters.
    • Tightened EPOLLET doc comment to note the known partial-read divergence; added test-epoll-edge to validate EPOLLET and EPOLLONESHOT semantics (manifest and test matrix updated).