fix(ext/node): drive TLSWrap cycle on JSStream writes to fix deadlock#33914
Merged
Conversation
For JS-backed streams (TLSSocket wrapping a non-net.Socket Duplex, the mssql/tedious pattern), enc_write_cb (the libuv write completion path) never fires, so cycle() — the only path that fires InvokeQueued for queued cleartext WriteWraps — was only driven indirectly via peer reads. If the writer awaited each cb before the next chunk and the peer waited for all chunks before responding, the second cb never fired and the connection deadlocked. Mirror Node`s TLSWrap::EncOut + OnStreamAfterWrite loop: pump encrypted bytes to the underlying Duplex, wait for its _write callback, then drive cycle() (the ClearIn + EncOut analog) to push more cleartext, write more encrypted bytes if produced, and fire InvokeQueued. Gating cycle() on the underlying _write callback also propagates backpressure. Fixes denoland#33907.
nathanwhit
enabled auto-merge (squash)
May 7, 2026 20:26
littledivy
pushed a commit
to crowlKats/deno
that referenced
this pull request
Jun 10, 2026
…denoland#33914) For JS-backed streams (TLSSocket wrapping a non-net.Socket Duplex, the mssql/tedious pattern), enc_write_cb (the libuv write completion path) never fires, so cycle() — the only path that fires InvokeQueued for queued cleartext WriteWraps — was only driven indirectly via peer reads. If the writer awaited each cb before the next chunk and the peer waited for all chunks before responding, the second cb never fired and the connection deadlocked. Mirror Node`s TLSWrap::EncOut + OnStreamAfterWrite loop: pump encrypted bytes to the underlying Duplex, wait for its _write callback, then drive cycle() (the ClearIn + EncOut analog) to push more cleartext, write more encrypted bytes if produced, and fire InvokeQueued. Gating cycle() on the underlying _write callback also propagates backpressure. Fixes denoland#33907. <!-- IMPORTANT: If you used AI tools (e.g. Copilot, ChatGPT, Claude, Cursor, etc.) to help write this PR, you MUST disclose it in the PR description. PRs will be rejected if there is suspicion of undisclosed AI usage. Before submitting a PR, please read https://docs.deno.com/runtime/manual/references/contributing 1. Give the PR a descriptive title. Examples of good title: - fix(ext/net): fix race condition in TCP listener - docs(runtime): update permissions API docstrings - feat(cli): add --env-file flag to `deno run` - refactor(ext/node): simplify Buffer.from() implementation - test(ext/fs): add spec tests for symlink resolution Examples of bad title: - fix denoland#7123 - update docs - fix bugs 2. Ensure there is a related issue and it is referenced in the PR text. 3. Ensure there are tests that cover the changes. 4. Ensure `./x fmt` passes without changing files. 5. Ensure `./x lint` passes. 6. If you used AI tools to help write this PR, you MUST disclose it below. PRs will be rejected if there is suspicion of undisclosed AI usage. 7. Open as a draft PR if your work is still in progress. The CI won't run all steps, but you can add '[ci]' to a commit message to force it to. 8. If you would like to run the benchmarks on the CI, add the 'ci-bench' label. -->
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
For JS-backed streams (TLSSocket wrapping a non-net.Socket Duplex, the mssql/tedious pattern), enc_write_cb (the libuv write completion path) never fires, so cycle() — the only path that fires InvokeQueued for queued cleartext WriteWraps — was only driven indirectly via peer reads. If the writer awaited each cb before the next chunk and the peer waited for all chunks before responding, the second cb never fired and the connection deadlocked.
Mirror Node`s TLSWrap::EncOut + OnStreamAfterWrite loop: pump encrypted bytes to the underlying Duplex, wait for its _write callback, then drive cycle() (the ClearIn + EncOut analog) to push more cleartext, write more encrypted bytes if produced, and fire InvokeQueued. Gating cycle() on the underlying _write callback also propagates backpressure.
Fixes #33907.