Repository navigation
Conversation
The uSockets sweep timer advances group->timestamp on an absolute LIBUS_TIMEOUT_GRANULARITY (4 s) grid that is unrelated to when a socket is armed. us_socket_timeout stored timestamp + ceil(seconds/4), so a socket armed t ms before a grid boundary got its first tick after only t ms: idleTimeout: 4 could reap a socket ~0 ms after the request arrived, aborting a handler that was still running. Arm with one extra tick (clamped to the 240-slot wheel) so a timeout of N seconds fires in (ceil(N/4)*4, ceil(N/4)*4 + 4] s and never before N. The same adjustment is applied to us_connecting_socket_timeout and the minute-granularity long-timeout path. The existing 'should allow use of custom timeout' test stalled for 10 s under an 8 s idleTimeout that now fires in (8, 12] s, so it would race the timer; bump the stall to 14 s and await the .resolves/.rejects assertions that were previously fire-and-forget.
WalkthroughThis PR changes how uSockets computes timeout wheel slots, introducing a bounded tick-rounding helper used by socket and connecting-socket timeout/long-timeout setters, updates the related documentation comment, and adjusts/extends HTTP server idle-timeout tests to match the new timing behavior. ChangesTimeout Wheel Rounding Fix
Possibly related PRs
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
|
Updated 4:40 AM PT - Jul 7th, 2026
❌ @robobun, your commit f1ba741 has 3 failures in
🧪 To try this PR locally: bunx bun-pr 33610That installs a local version of the PR into your bun-33610 --bun |
…iately The sweep compares long_ticks == s->long_timeout on every 4 s tick but long_ticks = (global_tick / 15) % 240 only advances once per 15 ticks, so storing (long_timestamp + 240) % 240 == long_timestamp matches on the very next sweep. Capping at 239 keeps the stored slot distinct from the arm-time slot on both wheels. Also await the .resolves assertion in the adjacent per-request timeout test.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/bun-usockets/src/socket.c`:
- Around line 104-111: The timeout helper in us_internal_timeout_ticks currently
clamps the computed value to 239, which makes the documented maxLifetime value
of 240 fire one slot early. Update the logic so the public cap in App.h and the
wheel-slot handling stay consistent: either reduce the exposed maxLifetime limit
to 239 or allow the 240th slot to be represented explicitly without being
clamped away.
In `@test/js/bun/http/serve.test.ts`:
- Around line 2514-2568: This timing-sensitive idleTimeout test is flaky under
it.concurrent because sibling test contention can delay the event loop and make
a valid request exceed the 4s idle window, causing false failures in the killed
assertion. Move the test to sequential execution with it(...) (or equivalent
non-concurrent form) and set an explicit timeout aligned with the rest of the
serve tests, while keeping the existing idleTimeout/earliestAbort checks in the
same test body.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 1ab98b36-3aa5-406c-a666-1425b469a89d
📒 Files selected for processing (3)
packages/bun-usockets/src/libusockets.hpackages/bun-usockets/src/socket.ctest/js/bun/http/serve.test.ts
|
The diff is green on every lane that ran tests; CI is red only on infra and unrelated flakes across two consecutive builds (69673, 69692):
Ready for review; needs a maintainer to merge past the darwin artifact-download infra timeout. |
|
Closing this PR because it has been inactive for more than 90 days. |
Reproduction
On 1.4.0 one of those requests is closed with zero response bytes and
req.signalaborts ~80 ms after the request arrived, even though nothing was idle for anywhere near 4 s:Cause
us_socket_timeoutstoress->timeout = (group->timestamp + ceil(seconds/4)) % 240and the sweep inloop.cfires whentimestamp == s->timeout. Buttimestampadvances on an absoluteLIBUS_TIMEOUT_GRANULARITY(4 s) grid that is unrelated to when the socket was armed, so a socket armed t ms before a grid boundary gets its first tick after only t ms. ForidleTimeout: Nthe actual fire window was(4*(k-1), 4*k]withk = ceil(N/4), i.e.idleTimeout: 4could fire after ~0 ms and every value N could fire up to 4 s early.server.timeout(req, N),Bun.connectsocket timeouts and the WebSocketmaxLifetime(minute wheel) arm through the same helpers.Fix
Arm with one extra tick, clamped to the 240-slot wheel, so a timeout of N seconds fires in
(ceil(N/4)*4, ceil(N/4)*4 + 4]s and never before N (packages/bun-usockets/src/socket.c). The same adjustment is applied tous_connecting_socket_timeoutand the minute-granularityus_socket_long_timeout/us_connecting_socket_long_timeout.The existing
should allow use of custom timeouttest stalled for 10 s under an 8 sidleTimeout; that now fires in (8, 12] s so the stall is bumped to 14 s, and the.resolves/.rejectsassertions in that helper are now awaited.Verification
test/js/bun/http/serve.test.ts: 239 pass / 4 pre-existing container-env failures (localhost/IPv6/proxy) that reproduce on main.