Skip to content

fix(tls): separate transport destruction and graceful shutdown - #144

Merged
steipete merged 3 commits into
mainfrom
claude/tls-duplex-destroy-parity
Oct 8, 2026
Merged

steipete merged 3 commits into
mainfrom
claude/tls-duplex-destroy-parity

Conversation

@steipete

@steipete steipete commented Oct 8, 2026 •

Copy link
Copy Markdown

READY for coordinator merge. Final head: 5da4013bc4c8fd0a5a802776fb667833059adbf8.

Destroying a TLS socket over a supplied Duplex must destroy its transport without reading or calling end(). Graceful shutdown remains separate. This change preserves adopted transport ownership after native-handle detachment, distinguishes peer close-notify from full native close, and retains the writable half of half-open connections.

HTTP/2 session destruction now releases its accepted transport and emits terminal session events after socket closure. The duplicate _http2_upgrade.ts adapter is removed: Http2SecureServer uses the inherited tls.Server connection listener. A full tracked-tree search finds no remaining references to the removed module or helper. The internal module registry and build tables are generated from source discovery. node-http2-upgrade.test.mts directly covers injected TLS sockets, ALPN, requests, TLS options, disconnects and transport release; h2-conformance.test.ts separately covers shared session and wire behavior over h2c.

The unrelated resolver options refactor was extracted into #149, merged separately, and is absent from this PR's diff. This head is rebased onto main 9e640c399bb42c4b102be1e89a95ce659d112c43; the existing VM, resolver and TLS changelog entries are all preserved.

Independent P1 review caught an additional half-open write bug: the TLS engine accepted data after peer close-notify but did not flush it until end(). The fix drains pending output while the writable half remains open. Its new regression defers write("tail") out of the receive callback and calls end() only after peer receipt; Node 24 passes and the prior candidate fails. A proposed change to HTTP/2's writable-finish gating was rejected after Node 24 source inspection and a held-final/held-peer probe produced identical behavior. The final-head P1 review is scoped-clean.

Head: 5da4013bc4c8fd0a5a802776fb667833059adbf8.

Final-source proof Result
Darwin native, 87 paired files Candidate 2,580 pass / 105 existing skips / 0 fail; main 2,567 pass / 105 skips / 13 fail
Linux native, 87 paired files Candidate 2,591 pass / 94 existing skips / 0 fail; main 2,579 pass / 94 skips / 12 fail
Broader Node compatibility Darwin 1,195 files per arm, five identical inherited failures; Linux 1,194 files per arm, four identical inherited failures; no introduced or missing files
Node 24.21.0, Darwin and Linux Six-case explicit destruction, 16 HTTP/2 upgrade cases and the new write-before-end control pass
Rust target matrix 12 checked / 0 failed / 0 skipped
Native CI Green: Linux 65/65 and Darwin 66/66 result rows, plus direct net probes
Windows dry-run CI x64 and ARM64 builds, packaging and smoke pass; compatibility 31/31 on each architecture, both direct net probes pass; publish=false
Rust CI Green: Mordant, Clippy, Miri and lol-html

Both native comparisons use identical final-head test inputs and verified executable hashes, with zero retries or relaxed limits. The CI merge-ref tree equals the PR head tree. The baseline executable tree equals merged main's tree. Inherited broader-suite diagnostics match after normalizing only the runtime revision. Darwin main's extra failure was ERR_SSL_WRONG_VERSION_NUMBER in the unchanged PFX-only context replacement case; it is retained as a baseline observation, not claimed as a separate fix.

This builds on oven-sh#42350, ports relevant session lifecycle behavior from oven-sh#38195 and the destroyed-EOF guard from oven-sh#43392, and retains regression credit for oven-sh#38154. Thanks @robobun. Receiving server-initiated renegotiation is covered; Bun-initiated renegotiate() remains the existing documented unsupported API. No event-loop changes from #142 are included.

The Windows fd-offset deadline tracked in #148 did not recur in this final run. Its timeout and test inputs are unchanged; this result does not claim to fix the intermittent. No merge, tag, release or repository settings change was performed as part of this preparation.

@steipete steipete changed the title fix(tls): destroy wrapped transports without ending them fix(tls): separate transport destruction and graceful shutdown Oct 8, 2026
@steipete
steipete marked this pull request as ready for review October 8, 2026 11:43
@steipete
steipete force-pushed the claude/tls-duplex-destroy-parity branch from a1c3a45 to 06726b4 Compare October 8, 2026 11:57
steipete and others added 3 commits October 8, 2026 05:15
Match Node 24 destruction for Duplex-backed TLS while keeping graceful
shutdown separate. Retain adopted-fd close ownership and release HTTP/2
injected transports when their TLS proxy is destroyed.

Adapt HTTP/2 lifecycle coverage from oven-sh#38154.

Co-authored-by: robobun <117481402+robobun@users.noreply.github.com>
Keep adopted-fd ownership on the TLS socket after its raw handle detaches.
Separate peer EOF, graceful writable shutdown, and full TLS destruction;
wait for transport completion without forwarding its error a second time.
Use the inherited tls.Server connection path for injected HTTP/2 sockets
instead of maintaining a second TLS transport adapter.

Port HTTP/2 destroy-versus-close and final event ordering from
oven-sh#38195, and the destroyed-socket EOF guard from oven-sh#43392.
Retain the six-case destruction regression and add Node 24 controls for
half-open replies, raw EOF, renegotiation shutdown, and transport ownership.
Synchronize conformance assertions with the sessionError event itself.

Co-authored-by: robobun <117481402+robobun@users.noreply.github.com>
Continue draining encrypted output after close_notify while the transport remains open. Add a Node 24 parity case that writes outside the receive callback and waits for peer receipt before ending, so shutdown cannot mask a missing flush.
@steipete
steipete force-pushed the claude/tls-duplex-destroy-parity branch from 5589417 to 5da4013 Compare October 8, 2026 12:16
@steipete
steipete merged commit 9141cca into main Oct 8, 2026
22 checks passed
@steipete
steipete deleted the claude/tls-duplex-destroy-parity branch October 8, 2026 12:55
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