Repository navigation
Conversation
…executes msnodesqlv8 fetches as soon as a query is resumed, whatever the query is doing. A stream that is paused before its query reaches a result set, as toReadableStream() leaves one until it is read, pauses the query straight away. Resumed while the query was still executing, or moving on through statements with no result, it had msnodesqlv8 fetch from a statement with nothing to fetch yet. Depending on timing, the stream reported a spurious 'Function sequence error' (HY010), lost the error the query raised, or crashed the process. A query held back once msnodesqlv8 has started it is now resumed only when it has stopped at a result set, a tick after its 'meta', and not at all if it ends without one. One resumed before msnodesqlv8 starts it is resumed at once, as before, so that it starts unpaused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
Cancellation after a deferred resume can free a still-executing statement on older msnodesqlv8 versions.
1 open finding
What changed in this PR
Fixes premature msnodesqlv8 stream resumes by waiting until the driver reaches its first result set.
Changes:
- Defers resumes after query submission while preserving pre-submission behavior.
- Adds unit coverage for resume timing and queries without results.
- Adds shared success and failure integration tests for both drivers.
| File | Description |
|---|---|
| test/tedious/tedious.js | Registers shared resume regression tests. |
| test/msnodesqlv8/msnodesqlv8.js | Registers shared resume regression tests. |
| test/common/unit.js | Tests deferred and immediate resume paths. |
| test/common/tests.js | Adds delayed-query streaming regression tests. |
| lib/msnodesqlv8/request.js | Guards early resumes using driver lifecycle events. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if (!pending.length && !this.paused && driverPaused && !finished && !cancelRequested && reachable() && | ||
| (!heldBeforeResults || !submitted || atResultSet)) { |
There was a problem hiding this comment.
The window is real, and it is wider than this PR. I ran live probes on Windows CI (SQL Server 2022) with msnodesqlv8 2.7, 3.1, 4.5, 5.2.1 and 5.6.0:
- The cancel lands while the query is still in
SQLExecDirect: safe on every version, whether the stream was resumed or not. The stream ends withECANCELwithin 10 ms, and the next query on the connection runs. - The cancel lands after the server has flushed an earlier statement's row count: here msnodesqlv8 is in
SQLMoreResults, and cancelling the paused query segfaults the process on every version. The query wasset nocount off; insert ...; raiserror(..., 0, 1) with nowait; waitfor ...; select .... It crashes on master too, for a stream that was never resumed.
So this is the existing paused-cancel path, which this PR also sends the resume-then-cancel sequence down. #1938 fixes it for both cases. It defers the cancel of a query held before its first result set until the query reaches that result set, and reports the query as cancelled if it ends first. Across the five versions, an earlier form of that deferral, with this PR's change on top, ran 120 times without a crash. #1938 merges first, and then this PR is rebased onto it, with its tests changed to expect the deferred cancel.

Fixes #1924.
What goes wrong
With msnodesqlv8, a stream paused before its query reaches a result set has the driver paused at once.
toReadableStream()leaves a stream like this until it is read, and so doesrequest.pause()beforequery(). When the stream is resumed, the adapter resumes the driver straight away. msnodesqlv8'sQuery.resume()then fetches at once, whatever the query is doing. Ifbegin()(itsSQLExecute) or anextResult()is still running, that fetch lands on a statement with nothing to fetch yet. Depending on timing, one of these happens:Function sequence error(HY010), which the stream reports as an extra error.The fix
The early pause stays, because a cancel needs it to reach a stream paused before it runs. Only the matching resume is held back, and only once msnodesqlv8 has started the query, which it signals with
'submitted':'meta'. By thenbegin()ornextResult()has handed over, anddispatch()has returned because the query is paused.Pausing at the end of a 50-row batch is unchanged, because the driver has nothing of its own under way there.
Tests
Unit tests (
test/common/unit.js):'meta', even when a'recordset'listener resumes the stream. It is resumed a tick later.'rowsaffected'listener, is not resumed while it moves on through its statements. It is resumed a tick after its first'meta'.Each of nine mutants of the fix fails at least one of these tests.
Integration test (
resuming a stream held back while its query executes), run against both drivers in two variants, one passing and one failing:waitfor delay '00:00:01', and resumed 300 ms in.208error with no rows.Live check on Windows
These runs used SQL Server 2022 on Windows CI, on a throwaway fork branch that has since been deleted. For each msnodesqlv8 version, the two new integration tests ran with master's adapter and with this one. The full msnodesqlv8 suite then ran with the fix. All of this was done twice: once on an earlier revision of this fix, and once on the commit in this PR.
208error was lost both times208error was lost twice, and replaced byFunction sequence erroronceFunction sequence errorfour times, node crashed twiceFunction sequence errorfour times, node crashed twiceWhat the failures looked like:
[Microsoft][ODBC Driver Manager] Function sequence error(number0) as well as the query's own208.Statement handle was freed before results could be returned.Master's adapter failed 20 of the 30 runs. The rest passed because the result depends on timing. The fix passed all 30 runs across both revisions, and the full suite on every version both times.
Local checks
npm run lint: clean.npm run test-unit: 234 passing.test-tediousagainst Azure SQL Edge: 179 passing. The 3 failures are the CLR-dependent tests that always fail on SQL Edge:user defined types, and the spatial and hierarchyid bulk loads.msnodesqlv8 v6
msnodesqlv8's unreleased upstream master fixes this HY010 itself. It also holds back a paused query's
metauntil the query is resumed, so on master this held resume never fires and the stream hangs. The gate has to be switched off for v6 before v6 is released. #1940 tracks that.🤖 Generated with Claude Code