Repository navigation
Conversation
There was a problem hiding this comment.
🔵 Needs a closer look
Version-specific native-driver cancellation ordering warrants human review, and the integration test’s timeout cleanup still needs correction.
1 open issue
What changed in this PR
This PR prevents native-driver crashes when cancelling an msnodesqlv8 stream paused before its first result set, integrating the workaround into the existing deferred-cancellation handling.
Changes:
- Defer cancellation until the first result set or query completion, preserving cancellation when the driver has already sent it.
- Add unit and cross-driver integration coverage for cancellation and connection reuse.
- Document delayed cancellation, retained statement effects, and timeout limits.
| File | Description |
|---|---|
test/tedious/tedious.js |
Registers the shared cancellation regression test. |
test/msnodesqlv8/msnodesqlv8.js |
Registers the shared cancellation regression test. |
test/common/unit.js |
Covers deferred cancellation and driver-event edge cases. |
test/common/tests.js |
Tests cancellation before results and subsequent connection reuse. |
README.md |
Explains cancellation delays and timeout behavior. |
lib/msnodesqlv8/request.js |
Defers cancellation of queries held before their first result set. |
Review effort: Balanced
dhensby
force-pushed
the
fix/msnodesqlv8-held-cancel-crash
branch
from
October 25, 2026 19:18
c9611a8 to
82a5f39
Compare
…sult set is cancelled A stream paused before its query reaches a result set, as toReadableStream() leaves one until it is read, holds msnodesqlv8's query paused. The query still runs the statements ahead of that result set, which have none, and msnodesqlv8 cancels a paused query by freeing its statement. Once the server has sent the row count of one of those statements, msnodesqlv8 is reading on to the next result when that free lands, which can crash the process. It did so in nearly every run with each msnodesqlv8 version tried, 2.7 to 5.6.0. A held query is now cancelled once it stops at its first result set, where it has nothing under way, and one that ends first completes as cancelled. Such a cancel no longer cuts short the statements ahead of that result set: it takes effect when they end, or when the request times out. With no request timeout, nothing limits the wait. msnodesqlv8 6 fixes the crash itself, and emits nothing for a paused query until it is resumed, so there a held query is cancelled at once, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
msnodesqlv8 6 requires Node 22 or later, and ships a Node-API binary for win32-x64, so the Windows jobs run its tests on Node 22 and 24 after the v5 run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dhensby
force-pushed
the
fix/msnodesqlv8-held-cancel-crash
branch
from
October 25, 2026 19:33
a17ae35 to
821c139
Compare
dhensby
marked this pull request as ready for review
October 25, 2026 19:33
This was referenced Oct 26, 2026
This branch has not been deployed
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.

What goes wrong
A stream paused before its query reaches a result set has msnodesqlv8's query held paused.
toReadableStream()leaves a stream like this until it is read, and so doesrequest.pause()beforequery(). msnodesqlv8 still runs the statements ahead of the first result set, becausebegin()andnextResult()don't checkpaused. It cancels a paused query by freeing its statement.That free can land after the server has sent the row count of one of those statements, while msnodesqlv8 is in
nextResult(SQLMoreResults). The process then crashes with a segfault. An example query:Any batch or procedure that runs DML with
NOCOUNT OFFand gets its output flushed before its first result set can do this.This bug is already on master and is independent of #1928. Copilot's review of #1928 led to it, because #1928 sends a stream resumed while its query executes down the same path.
The fix
A query held back before its first result set is now cancelled once it stops at that result set, where it has nothing under way:
cancelDeferredpath, which already does this for a paused stream part way through a result set.The cost: such a cancel no longer cuts short the statements ahead of the first result set. It takes effect when they end, or when the request times out, and
requestTimeout: 0(or under a second) means nothing limits the wait. Errors from those statements, including a timeout, are streamed ahead of theECANCEL. The README's cancel section now says so.No adapter-side signal could do better. Nothing tells the request when msnodesqlv8 moves from
SQLExecDirectintonextResult, and a cancel duringSQLExecDirectitself is safe.Live results (Windows CI, SQL Server 2022)
These ran on a throwaway fork branch, since deleted.
With this PR, the stream ends with
ECANCELat the query's first result set, about 2 s in, and the next query on the connection runs.Earlier probes of master's adapter narrowed down what triggers the crash. Each of these ran 3 times per version, across all 5 versions:
waitfor; select)ECANCELThe same row count + flush cancel, deferred to the first result set as this PR does it, gave no crashes in any of 120 runs.
The crash reproduces with msnodesqlv8 on its own, too. Over two rounds of three runs per ODBC driver, it crashed in all 12 runs on 5.2.1 and in 11 of 12 on 5.6.0, the latest release, with both ODBC Driver 17 and Native Client 11. I'll report it upstream.
Tests
Unit tests (
test/common/unit.js):stallBegintest, which cancelled a held query during its first statement, now expects the cancel when that statement ends.Master's adapter fails 7 of these. Each mutant of the fix fails at least one test, apart from two that are equivalent on master.
Integration test:
cancelling a stream held back while its query moves on to its first result setruns against both drivers on a pool of one connection. It cancels a held stream 500 ms into the repro query, expectsECANCELand no rows, and then checks that the next query runs.Local checks
npm run lint: clean.npm run test-unit: 256 passing.test-tediousagainst Azure SQL Edge: 179 passing. The 3 failures are the CLR-dependent tests that always fail on SQL Edge.Relation to #1928
#1928 (resume during execute, #1924) is rebased on this once it merges. With this in place, a stream resumed while its query executes and then cancelled takes the same deferred path. It no longer hits the crash.
msnodesqlv8 6
msnodesqlv8 6.0.0 fixes this crash itself. It also emits nothing for a paused query until it is resumed,
metaanddoneincluded, so the deferred cancel would wait forever there. The deferral is therefore switched off from msnodesqlv8 6, read from the major version in itspackage.json, and the cancel is made at once, as on master.ci:commit runs the msnodesqlv8 suite against v6 on Node 22 and 24 in the Windows jobs. msnodesqlv8 6 needs Node 22 or later.The rest of the v6 work is tracked in #1940.
🤖 Generated with Claude Code