Filter fatal exceptions from Task.FromException in async paths#4437
Filter fatal exceptions from Task.FromException in async paths#4437cheenamalhotra wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR hardens async error-handling paths in Microsoft.Data.SqlClient by ensuring “fatal” exceptions (e.g., OutOfMemoryException, ThreadAbortException, etc.) are not silently converted into faulted Tasks via Task.FromException, aligning behavior with existing ADP.IsCatchableExceptionType patterns in the codebase.
Changes:
- Add an exception filter to
SqlDataReader.InvokeAsyncCallto avoid wrapping non-catchable exceptions viaTask.FromException<T>. - Add an
ADP.IsCatchableExceptionTypeguard inSqlBulkCopybefore returningTask.FromException<bool>(ex)for async bulk copy.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs | Adds an exception filter around Task.FromException<T>(ex) in InvokeAsyncCall. |
| src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs | Adds a catchability guard before returning Task.FromException<bool>(ex) in the async bulk copy path. |
Fatal exceptions (OOM, StackOverflow, ThreadAbort, NullReference, AccessViolation) must not be wrapped in Task.FromException or TrySetException — they should propagate synchronously. Add ADP.IsCatchableExceptionType guards matching the existing pattern used elsewhere in SqlDataReader. Sites fixed: - SqlBulkCopy.cs: 8 catch blocks guarded with exception filter - SqlDataReader.cs (InvokeAsyncCall): exception filter added - SqlCommand.Reader.cs: exception filter added - SqlCommand.Xml.cs: exception filter added Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1b34de8 to
33de935
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #4437 +/- ##
==========================================
- Coverage 65.83% 64.06% -1.78%
==========================================
Files 287 282 -5
Lines 43763 66661 +22898
==========================================
+ Hits 28812 42704 +13892
- Misses 14951 23957 +9006
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Summary
Multiple sites in
SqlBulkCopy,SqlDataReader.InvokeAsyncCall,SqlCommand.Reader, andSqlCommand.XmlcatchExceptionbroadly and wrap it inTask.FromException<T>()orTrySetException()without filtering fatal exceptions. This meansOutOfMemoryException,StackOverflowException,ThreadAbortException, etc. get silently captured into faulted Tasks instead of propagating synchronously.Changes
SqlBulkCopy.cswhen (ADP.IsCatchableExceptionType(ex))filter to 8 catch blocks that wrap exceptions intoTask.FromExceptionorTrySetExceptionSqlDataReader.cs(InvokeAsyncCall)when (ADP.IsCatchableExceptionType(ex))exception filterSqlCommand.Reader.cswhen (ADP.IsCatchableExceptionType(e))exception filterSqlCommand.Xml.cswhen (ADP.IsCatchableExceptionType(e))exception filterPrecedent
This matches the existing pattern already used in
SqlDataReaderat multiple other sites (e.g.,ReadNextAsync,IsDBNullAsync,GetFieldValueAsync):What was NOT changed
Sites in task continuations / callbacks (
TdsParserStateObjectSNI callbacks,AsyncHelpercontinuations,SqlCommandretry continuations) were intentionally left unchanged. In those contexts, rethrowing would crash the unobserved-task handler or the thread-pool thread; theTrySetExceptioncall is the correct behavior since the exception originated asynchronously.Similarly, sites in
SqlSequentialTextReaderandSqlSequentialStreamthat callTrySetExceptionand then rethrow were left unchanged — the fatal exception already propagates.Testing