Skip to content

msnodesqlv8: resuming a stream paused before its first result set can fail with a spurious 'Function sequence error' (HY010) #1924

Open

Description

@dhensby

Summary

With the msnodesqlv8 driver, a stream that is paused before its query reaches a result set can fail with a spurious [Microsoft][ODBC Driver Manager] Function sequence error (HY010) if it is resumed while the query is still executing. toReadableStream() pauses the request this way before it runs it, and so does calling request.pause() before query().

Observed

Seen in a live probe on Windows CI against SQL Server 2022 with msnodesqlv8 5.6.0. The request was a stream paused before it ran, for select * from dbo.probe_no_such_table, resumed 8 ms after it started. These are the driver events:

call:pauseQuery
drv:submitted  select * from dbo.probe_no_such_table
call:resumeQuery   (query still running, no result yet)
drv:error HY010  [Microsoft][ODBC Driver Manager] Function sequence error  more=false
drv:error 42S02  Invalid object name 'dbo.probe_no_such_table'.            more=false
drv:free
drv:free

The stream emitted two EREQUEST errors instead of one, and the statement was freed twice. The next query on the connection still ran. In other runs at other timings the resume landed before submitted or after the error, and only the real error was emitted. So this depends on timing, and that one run is the only time it has been seen live so far.

Cause

msnodesqlv8's Query.resume() does not check whether a native call is still under way. With no batch fetched yet, it ends in dispatch(), which starts a fetch at once. If the query's begin() (its SQLExecute) or a nextResult() is still running, ODBC rejects that fetch with HY010. When begin() resolves it calls dispatch() again, so a successful query can also have its first fetch issued twice.

In 5.6.0 (lib/reader.js), resume() falls through to this.dispatch() when batchData is null, and dispatch() calls nativeGetRows() unless the query is paused or no longer running. 2.7 to 5.2 behave the same way: resume() there is dispatchRows(batchData) followed by dispatch().

Since #1919, the adapter pauses the driver at only two points. One is the end of a full 50-row batch, where the driver has nothing of its own under way. The other is before the query's first result set, in _pauseStream(), which is called just after queryRaw() and so while begin() is usually in flight. _resumeStream() resumes the driver whenever the stream is resumed. A stream paused before its first result set and resumed before the driver reaches one can therefore resume it in the middle of begin() or of a nextResult() chain.

#1215 reported the same error from mid-stream pause and resume on msnodesqlv8 2.0. #1919 removed most of that window by only pausing the driver at batch ends. This is the window that is left.

Proposed fix

Keep the early pause, because a cancel needs it to reach a stream paused before it runs. Hold back the matching resume until the driver has stopped:

The underlying gap could also be raised upstream: msnodesqlv8's Query.resume() could wait for an in-flight native call. The adapter fix is needed either way, for the versions already released.

I'll work on a fix, with a deterministic reproduction across msnodesqlv8 2.7 to 5.6. A slow first statement such as waitfor delay should widen the window enough to hit it every time.

Related: #1215, #1919, #1921.

Activity

  1. dhensby commented on Oct 15, 2026

    @dhensby
    CollaboratorAuthor

    Reproduced on every msnodesqlv8 line, and the effects are wider than the duplicate error described above.

    These runs used SQL Server 2022 on Windows CI. The setup was the one proposed above: a stream paused before it runs a query that starts with waitfor delay '00:00:01', then resumed 300 ms in. With master's adapter, each msnodesqlv8 version ran the test six times. Each run streams a query that succeeds and one that fails.

    msnodesqlv8 runs that failed what happened
    ^2 3 of 6 node crashed twice, and a query that succeeds lost its row once
    ^3 2 of 6 the failing query's 208 error was lost both times
    ^4 3 of 6 the 208 error was lost twice, and replaced by Function sequence error once
    5.2.1 6 of 6 an extra Function sequence error four times, node crashed twice
    ^5 (5.6.0) 6 of 6 an extra Function sequence error four times, node crashed twice

    On 5.2.1 and 5.6.0, in one run each, a query that should succeed also failed, with Statement handle was freed before results could be returned.

    So the widened window hits the bug in most runs but not all of them. The outcome isn't only an extra error. A failing query can complete as if it succeeded, a query that succeeds can lose its rows, and node can crash.

    One correction to the description above: 5.2's resume() already has the 5.6 form, not the 2.7 to 4.x form. With no batch fetched yet it also ends in dispatch(), so the conclusion is the same.

    The fix is in #1928. With it, every run passes, and so does the full msnodesqlv8 suite on all five versions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions