Skip to content

fix(bridge): poll http2 server/session retain instead of monopolizing sync-RPC wait#193

Merged
NathanFlurry merged 3 commits into
mainfrom
stack/fix-bridge-poll-http2-server-session-retain-instead-of-monopolizing-sync-rpc-wait-ulysquzo
Jul 2, 2026
Merged

fix(bridge): poll http2 server/session retain instead of monopolizing sync-RPC wait#193
NathanFlurry merged 3 commits into
mainfrom
stack/fix-bridge-poll-http2-server-session-retain-instead-of-monopolizing-sync-rpc-wait-ulysquzo

Conversation

@NathanFlurry

Copy link
Copy Markdown
Member

Any guest http2.createServer().listen() wedged the whole guest: Http2Server/
Http2Session.retain() issued a long-lived net.http2*_wait sync RPC, and guest
sync-RPC servicing is serialized, so the in-flight wait starved every later RPC
(including the listen response the isolate was blocked on).

  • retain() now drives bounded net.http2*_poll calls (wait_ms=0) on a timer
    loop and dispatches polled events through http2Dispatch; the loop stops on
    release/close/terminal events.
  • New regression test: guest h2c listen -> request -> response round-trip.
  • New net/http2_loopback_get matrix op (node + guest lanes, explicit
    unsupported native/wasm reasons) with body verification.

Guest https.get to an in-guest TLS server failed with ERR_AGENTOS_NODE_SYNC_RPC:
the client upgrade leaves the rustls handshake incomplete (looping would deadlock
the guest thread that must service the server-side upgrade), so the first write
hit WouldBlock from the loopback transport and surfaced as a fatal sync-RPC error.

- Buffer plaintext writes on loopback TLS sockets while the handshake is pending
  (bounded 4 MiB, warn at 80%, typed error on overflow); the TLS reader thread
  drives the handshake with a short poll timeout, flushes the buffer on
  completion, enforces TLS_HANDSHAKE_TIMEOUT, and honors deferred shutdown.
- Interrupt flags on the loopback transport let a direct write reclaim the
  stream mutex from the reader without waiting out its poll.
- Bridge: https.createServer now actually upgrades accepted sockets to TLS
  (options were previously discarded); client secureConnect defers to a
  macrotask without double-starting the read pump; first-read wake scheduling
  no longer requires bench metrics to be enabled; localAddress/localPort flow
  through connect.
- New net/tls_loopback_get bench op (guest+node lanes, explicit unsupported
  native/wasm reasons) and engine support for unsupported-lane rendering.
- Regression tests: loopback https round-trip; pending-write buffer cap.
… sync-RPC wait

Any guest http2.createServer().listen() wedged the whole guest: Http2Server/
Http2Session._retain() issued a long-lived net.http2_*_wait sync RPC, and guest
sync-RPC servicing is serialized, so the in-flight wait starved every later RPC
(including the listen response the isolate was blocked on).

- _retain() now drives bounded net.http2_*_poll calls (wait_ms=0) on a timer
  loop and dispatches polled events through http2Dispatch; the loop stops on
  release/close/terminal events.
- New regression test: guest h2c listen -> request -> response round-trip.
- New net/http2_loopback_get matrix op (node + guest lanes, explicit
  unsupported native/wasm reasons) with body verification.
@NathanFlurry

NathanFlurry commented Jul 2, 2026

Copy link
Copy Markdown
Member Author

Stack for rivet-dev/secure-exec

Get stack: forklift get 193
Push local edits: forklift submit
Merge when ready: forklift merge 193

@NathanFlurry
NathanFlurry changed the base branch from stack/style-cargo-fmt-kzmqrmsz to main July 2, 2026 08:48
@NathanFlurry
NathanFlurry merged commit 4a1b5f0 into main Jul 2, 2026
1 check passed
@NathanFlurry
NathanFlurry deleted the stack/fix-bridge-poll-http2-server-session-retain-instead-of-monopolizing-sync-rpc-wait-ulysquzo branch July 2, 2026 08:48
NathanFlurry added a commit that referenced this pull request Jul 2, 2026
The http2 poll-retain loop (PR #193's 1ms bounded-poll workaround) now gets
event pushes: the sidecar's http2 server/session event queues fire an
edge-triggered net_socket {event:'http2'} push on empty->nonempty (computed
before push_back, sent via the retained V8 session captured at listen/
connect), routed to the guest retain dispatcher which wake-latches and polls
immediately; the idle fallback stretches 1ms -> 10ms since events carry
latency now. net/http2_loopback_get 95 -> ~53-57ms.

Residual (documented in the backlog): ~5 turns per h2c GET still wake by
timer. A round-2 attempt reached ZERO timer-origin wakes but broke the
stream-API server path (server.on('stream') — the bench op) while the compat
API kept working; it was reverted per the revert rule. Retry needs a
regression test on the stream-API path first; artifacts in
~/progress/secure-exec/2026-07-02-http2-retain-push/. Adds an h2c
request-handler round-trip service regression (runs twice in one VM).
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.

1 participant