Skip to content

Improve the timeout error when closing an incompletely consumed SqlDataReader #4639

Description

@mdaigle

Describe the bug

Closing a SqlDataReader before consuming the full result set drains the remaining rows while the command timeout is still armed. If the drain exceeds the timeout on Linux, the caller receives an opaque timeout ending in Win32Exception (258): Unknown error 258.

The behavior is expected, but the error does not explain that the timeout occurred while draining an incompletely consumed result set or suggest canceling the command before closing the reader.

This is Cause B from #1530. See the deterministic analysis and version matrix and the request to track an improved error separately.

Exception message: Execution Timeout Expired. The timeout period elapsed prior to completion of the operation or the server is not responding.
Inner exception: System.ComponentModel.Win32Exception (258): Unknown error 258
Stack trace: The exception is raised from SqlDataReader.Close/CloseAsync while draining the remaining rows.

To reproduce

  1. Execute a query that returns enough rows that draining them exceeds a short CommandTimeout.
  2. Read only the first row.
  3. Call SqlDataReader.Close() or CloseAsync() without first canceling the command.
  4. Observe the timeout with Unknown error 258.

The linked analysis includes results across Microsoft.Data.SqlClient 5.1.5 through 7.1.0-preview2.

Expected behavior

Surface an error that identifies the timeout as occurring while closing and draining an incompletely consumed reader, with guidance to consume the result set or cancel the command before closing it.

Further technical details

Microsoft.Data.SqlClient version: Reproduced from 5.1.5 through 7.1.0-preview2
.NET target: net8.0 and net9.0 assets, tested on .NET 8, .NET 9, and .NET 10
SQL Server version: Reproduced with SQL Server in Docker
Operating system: Linux

Additional context

Explicit Close() and CloseAsync() surface the exception. Dispose() currently swallows the SqlException from Close(). EF Core calls the throwing close paths, while Dapper cancels the command during teardown and avoids this result.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    🐛 Bug!Area\Managed SNIIssues that are targeted to the Managed SNI codebase.

    Type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions