From 33de9350fcaeb388bf4aeb8d2ff316c4887991f3 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra Date: Thu, 9 Jul 2026 20:02:45 -0700 Subject: [PATCH] Filter fatal exceptions from Task.FromException in async paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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> --- .../Microsoft/Data/SqlClient/SqlBulkCopy.cs | 20 +++++++++++-------- .../Data/SqlClient/SqlCommand.Reader.cs | 2 +- .../Data/SqlClient/SqlCommand.Xml.cs | 2 +- .../Microsoft/Data/SqlClient/SqlDataReader.cs | 2 +- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs index 55142268e9..f5ef2eee6c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs @@ -1360,6 +1360,10 @@ private Task ReadFromRowSourceAsync(CancellationToken cts) { if (_isAsyncBulkCopy) { + if (!ADP.IsCatchableExceptionType(ex)) + { + throw; + } return Task.FromException(ex); } else @@ -2530,7 +2534,7 @@ private Task CopyColumnsAsync(int col, TaskCompletionSource source = nul source.SetResult(null); } } - catch (Exception ex) + catch (Exception ex) when (ADP.IsCatchableExceptionType(ex)) { if (source != null) { @@ -2739,7 +2743,7 @@ private Task CopyRowsAsync(int rowsSoFar, int totalRows, CancellationToken cts, source.TrySetResult(null); // This is set only on the last call of async copy. But may not be set if everything runs synchronously. } } - catch (Exception ex) + catch (Exception ex) when (ADP.IsCatchableExceptionType(ex)) { if (source != null) { @@ -2816,7 +2820,7 @@ private Task CopyBatchesAsync(BulkCopySimpleResultSet internalResults, string up } } } - catch (Exception ex) + catch (Exception ex) when (ADP.IsCatchableExceptionType(ex)) { if (source != null) { @@ -2888,7 +2892,7 @@ private Task CopyBatchesAsyncContinued(BulkCopySimpleResultSet internalResults, return CopyBatchesAsyncContinuedOnSuccess(internalResults, updateBulkCommandText, cts, source); } } - catch (Exception ex) + catch (Exception ex) when (ADP.IsCatchableExceptionType(ex)) { if (source != null) { @@ -2950,7 +2954,7 @@ private Task CopyBatchesAsyncContinuedOnSuccess(BulkCopySimpleResultSet internal return source.Task; } } - catch (Exception ex) + catch (Exception ex) when (ADP.IsCatchableExceptionType(ex)) { if (source != null) { @@ -3122,7 +3126,7 @@ private void WriteToServerInternalRestContinuedAsync(BulkCopySimpleResultSet int } } } - catch (Exception ex) + catch (Exception ex) when (ADP.IsCatchableExceptionType(ex)) { _localColumnMappings = null; @@ -3299,7 +3303,7 @@ private void WriteToServerInternalRestAsync(CancellationToken cts, TaskCompletio WriteToServerInternalRestContinuedAsync(internalResults, cts, source); // internalResults is valid here. } } - catch (Exception ex) + catch (Exception ex) when (ADP.IsCatchableExceptionType(ex)) { if (source != null) { @@ -3376,7 +3380,7 @@ private Task WriteToServerInternalAsync(CancellationToken ctoken) return resultTask; } } - catch (Exception ex) + catch (Exception ex) when (ADP.IsCatchableExceptionType(ex)) { if (source != null) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs index bffe17baf9..33af6073fb 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs @@ -381,7 +381,7 @@ private void BeginExecuteReaderInternalReadStage(TaskCompletionSource co _stateObj.ReadSni(completion); } // @TODO: CER Exception Handling was removed here (see GH#3581) - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { // Similarly, if an exception occurs put the stateObj back into the pool. // and reset async cache information to allow a second async execute diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Xml.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Xml.cs index 447627375e..39d043df12 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Xml.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Xml.cs @@ -334,7 +334,7 @@ private void BeginExecuteXmlReaderInternalReadStage(TaskCompletionSource _stateObj.ReadSni(completion); } // @TODO: CER Exception Handling was removed here (see GH#3581) - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { // Similarly, if an exception occurs put the stateObj back into the pool. // and reset async cache information to allow a second async execute diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs index 14c8a51b4f..bc74940c4d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs @@ -5476,7 +5476,7 @@ private Task InvokeAsyncCall(SqlDataReaderBaseAsyncCallContext context) { task = context.Execute(null, context); } - catch (Exception ex) + catch (Exception ex) when (ADP.IsCatchableExceptionType(ex)) { task = Task.FromException(ex); }