Skip to content

fix(net): persistent epoll registration without reintroducing #22 - #23

Merged
caixuf merged 1 commit into
mainfrom
cursor/persistent-epoll-registration-9f14
Sep 15, 2026
Merged

caixuf merged 1 commit into
mainfrom
cursor/persistent-epoll-registration-9f14

Conversation

@caixuf

@caixuf caixuf commented Sep 15, 2026

Copy link
Copy Markdown
Owner

Root cause

After #22, persistent localhost echo is healthy (WSL ~43.7k req/s, fail=0). The remaining cost called out there still stood: every Socket::read / write / accept / connect wait did one-shot add_fd + remove_fd (EPOLL_CTL_ADD/DEL, replace handlers_[fd]).

That is extra syscalls on the steady-state path. It is also the shape of the #22 race: if the EventLoop thread set_values (worker may add_fd the next wait) and then remove_fd, the new handler is erased and the waiter hangs.

Short-connection connect+echo p99 under wave=32 is a different bottleneck: one accept() per epoll event + listen-queue physics. ADD/DEL on the listen fd made that worse, but draining the backlog cannot make the tail of a 32-way handshake wave look like p50.

Design

Keep the fd in the poll set for the life of the Socket. Close still remove_fd (DEL + erase).

  1. add_fd: first wait EPOLL_CTL_ADD (store handler before ctl so EPOLLONESHOT cannot fire with no waiter). Later waits update the existing IoEventHandler in place and EPOLL_CTL_MOD. No DEL.
  2. Linux EPOLLONESHOT: kernel disables the fd after one event, so a registered fd with no waiter does not LT-spin. Re-arm is the next add_fd/MOD.
  3. complete_io before set_value / set_exception (the fix(net): persistent localhost echo no longer times out #22 ordering): clear waiter callbacks and interest bits, do not DEL/erase. A CoroutinePool worker that resumes before the callback returns can add_fd a new waiter on the same handler slot.
  4. Windows WSAPoll: same protocol. complete_io sets events=0 and skips those fds when rebuilding the poll set (ONESHOT equivalent). Still compiles; no epoll.
  5. Accept drain: on listen readability, accept4 until EAGAIN into pending_accepts_. Later co_await accept() pops without another epoll wait. Default listen(SOMAXCONN).

Not done (on purpose): no CoroutinePool/Task rewrite; no timeout bumps; no fake p99 by shrinking wave.

Tests

How to verify (Linux; WSL is the performance source of truth)

cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --target real_net_benchmark test_real_net_echo test_core -j$(nproc)
./build/tests/test_real_net_echo
./build/tests/test_core
./build/benchmarks/real_net_benchmark
# or:
./build/benchmarks/real_net_benchmark echo --duration-ms 1500 --clients 8 --payload 64
./build/benchmarks/real_net_benchmark connect --connections 200 --payload 64

Success:

  • Persistent echo: wall ≈ requested duration, fail=0, no Task::get timed out. Throughput on WSL is the number to quote — not a cloud VM.
  • Connect+echo: fail=0. p99 under wave=32 will still show accept-queue delay; that is expected.

Do not treat cloud QPS as success. Loopback only; not NIC/wire; not the CPU-sim Echo rows in professional_flowcoro_benchmark.

Open in Web Open in Cursor 

One-shot add_fd/remove_fd on every await was leftover churn after #22.
Register once, re-arm with EPOLLONESHOT+MOD, and complete_io (clear
waiters, no DEL) before set_value so a worker cannot lose its new handler.

Drain the listen backlog on accept, default listen(SOMAXCONN), and add
an accept-burst regression. Short-connection p99 under wave=32 is still
mostly accept-queue physics.

Co-authored-by: 李龙飞 <caixuf@users.noreply.github.com>
@caixuf
caixuf marked this pull request as ready for review September 15, 2026 02:14
@caixuf
caixuf merged commit ab2fe57 into main Sep 15, 2026
5 checks passed
@caixuf
caixuf deleted the cursor/persistent-epoll-registration-9f14 branch September 15, 2026 02:15
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.

2 participants