deno
3eb1657d - perf(libs/core): drop libuv-style partial-read break to fix node:http p99 (#33860)

Commit
73 days ago
perf(libs/core): drop libuv-style partial-read break to fix node:http p99 (#33860) ## Summary `poll_tcp_handle`'s read loop in `uv_compat` matches libuv's `uv__read` by breaking on partial reads (`n < buf.len`) to skip the predicted-EAGAIN syscall. That works for libuv's epoll loop, but it interacts badly with tokio: tokio's `TcpStream` readiness is edge-triggered and the internal "readable" bit is cleared only when `try_read` returns `WouldBlock`. Breaking on a partial read leaves the bit armed; the re-register block at the end of the function then sees `poll_read_ready == Ready` and `mark_ready()` puts the handle right back on the loop's ready queue and wakes `loop_waker`. Under sustained HTTP keep-alive load with small responses every read is partial, so every tick re-queues every active connection. Tokio's current_thread runtime sees the deno task continuously awoken and never yields to its reactor (controlled by `event_interval`, default 61). New TCP readiness from the kernel piles up undelivered, and each connection waits a full event_interval window before its next request is processed. The fix is to keep looping until `try_read` actually returns `WouldBlock`. That costs one extra `recv()` syscall when the read fully drains the socket buffer, but the syscall returns `WouldBlock`, clears tokio's readiness bit, and the handle drops out of the ready queue cleanly so the reactor is free to deliver fresh wakes. This was easy to confirm with `DENO_TOKIO_EVENT_INTERVAL=1`, which forces tokio to check the reactor every poll and made p99 drop from 33 ms to 3 ms even without the source change. ## Reproducer \`\`\`js // server.mjs import { createServer } from "node:http"; const port = Number(process.env.PORT ?? "8004"); createServer((_req, res) => { res.writeHead(200, { "content-type": "text/plain" }); res.end("Hello, World!"); }).listen(port, "127.0.0.1"); \`\`\` \`\`\` oha -c 100 -z 20s --no-tui http://127.0.0.1:8004/ \`\`\` ## Results (release-lite, 100 connections, 20 s) | | node | deno before | deno after | | ------ | -------- | ----------- | ---------- | | p50 | 1.26 ms | 0.96 ms | 1.58 ms | | p99 | 2.56 ms | 33.4 ms | 3.14 ms | | p99.9 | 4.26 ms | 60.7 ms | 6.34 ms | | p99.99 | 7.28 ms | 68.0 ms | 8.28 ms | \`Deno.serve\` p99 is unchanged at 1.31 ms (no regression on the hyper path). ## Test plan - [x] Bench `node:http` hello-world at `c=100`, confirm p99 drops from ~30 ms to ~3 ms - [x] Bench `Deno.serve` hello-world at `c=100`, confirm no regression - [ ] CI green Co-authored-by: Nathan Whitaker <nathan@deno.com>
Author
Parents
Loading