From 33de9350fcaeb388bf4aeb8d2ff316c4887991f3 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra Date: Thu, 9 Jul 2026 20:02:45 -0700 Subject: [PATCH 1/2] 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); } From 4b8d781696bed2cca23bfa3ad533551daaeb5817 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra Date: Tue, 14 Jul 2026 13:45:25 -0700 Subject: [PATCH 2/2] Address feedback --- .../src/Microsoft/Data/SqlClient/SqlBulkCopy.cs | 15 ++------------- 1 file changed, 2 insertions(+), 13 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 f5ef2eee6c..44c74fc3f0 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs @@ -1356,20 +1356,9 @@ private Task ReadFromRowSourceAsync(CancellationToken cts) { _hasMoreRowToCopy = ReadFromRowSource(); // Synchronous calls for DataRows and DataTable won't block. For IDataReader, it may block. } - catch (Exception ex) + catch (Exception ex) when (_isAsyncBulkCopy && ADP.IsCatchableExceptionType(ex)) { - if (_isAsyncBulkCopy) - { - if (!ADP.IsCatchableExceptionType(ex)) - { - throw; - } - return Task.FromException(ex); - } - else - { - throw; - } + return Task.FromException(ex); } finally {