-
Notifications
You must be signed in to change notification settings - Fork 323
Description
Description
When a ReadAsync task is pending on a SqlDataReader and the reader is disposed, the exception surfaced by Task.Wait() is inconsistent: sometimes it is an IOException, sometimes an InvalidOperationException. The outcome depends on a race condition inside the driver, and manifests differently depending on network latency (Azure SQL vs local SQL Server).
Root Cause
There are two competing disposal paths inside SqlDataReader:
-
IOException path — The
ReadAsynccontinuation entersContinueAsyncCall, reachescontext.Execute(), and the underlying stream read fails with aSqlException(due to the reader being disposed).SqlSequentialStreamwraps this in anIOExceptionbecauseStream.ReadAsyncshould not surfaceSqlException. -
InvalidOperationException path —
_cancelAsyncOnCloseTokenSource.Cancel()fires (from reader disposal) before the continuation reachescontext.Execute(). The cancellation token is checked inContinueAsyncCallorExecuteAsyncCall, and the method falls through toADP.ClosedConnectionError(), which returns anInvalidOperationException.
The race winner depends on timing:
- Low latency (local SQL Server): Data arrives faster, so the continuation is more likely to already be inside
context.Execute()→IOException. - High latency (Azure SQL): Longer round-trip means the continuation is less likely to have entered the execute path before
_cancelAsyncOnCloseTokenSource.Cancel()fires →InvalidOperationException.
Relevant Code
SqlDataReader.ContinueAsyncCall<T>()— the final fallthrough at the end returnsADP.ClosedConnectionError()(InvalidOperationException) when the reader is closed, regardless of what the caller expects.SqlDataReader.ExecuteAsyncCall<T>()— same issue with the cancellation check at the top.SqlSequentialStream/SqlSequentialTextReader— wrapsSqlExceptioninIOExceptionwhen the read faults.
Expected Behavior
The exception type thrown by a pending ReadAsync task when the reader is disposed should be deterministic and consistent regardless of network latency or server type.
Actual Behavior
- Against local SQL Server: usually
AggregateExceptionwrappingIOException - Against Azure SQL: usually
AggregateExceptionwrappingInvalidOperationException - Sometimes no exception at all (read completes before disposal)
Workaround
Tests in DataStreamTest.cs (DEBUG-only #if DEBUG blocks) currently use a try/catch pattern that accepts either exception type or no exception at all, guarded by TODO(GH-3604) comments.
Related
Metadata
Metadata
Assignees
Labels
Type
Projects
Status