Skip to content

Commit 0c17d90

Browse files
Additional checks to handle external connection abort.
1 parent f34cdf8 commit 0c17d90

File tree

2 files changed

+353
-319
lines changed

2 files changed

+353
-319
lines changed

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs

Lines changed: 165 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -137,93 +137,105 @@ public byte[] Promote()
137137
// from an operational standpoint (i.e. logging connection's ObjectID should be fine,
138138
// but the PromotedDTCToken can change over calls. so that must be protected).
139139
SqlInternalConnection connection = GetValidConnection();
140-
141140
Exception promoteException;
142141
byte[] returnValue = null;
143-
SqlConnection usersConnection = connection.Connection;
144-
SqlClientEventSource.Log.TraceEvent("<sc.SqlDelegatedTransaction.Promote|RES|CPOOL> {0}, Connection {1}, promoting transaction.", ObjectID, connection.ObjectID);
145-
RuntimeHelpers.PrepareConstrainedRegions();
146-
try
142+
143+
if (null != connection)
147144
{
148-
lock (connection)
145+
SqlConnection usersConnection = connection.Connection;
146+
SqlClientEventSource.Log.TraceEvent("<sc.SqlDelegatedTransaction.Promote|RES|CPOOL> {0}, Connection {1}, promoting transaction.", ObjectID, connection.ObjectID);
147+
RuntimeHelpers.PrepareConstrainedRegions();
148+
try
149149
{
150-
try
150+
lock (connection)
151151
{
152-
// Now that we've acquired the lock, make sure we still have valid state for this operation.
153-
ValidateActiveOnConnection(connection);
152+
try
153+
{
154+
// Now that we've acquired the lock, make sure we still have valid state for this operation.
155+
ValidateActiveOnConnection(connection);
154156

155-
connection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Promote, null, System.Data.IsolationLevel.Unspecified, _internalTransaction, true);
156-
returnValue = _connection.PromotedDTCToken;
157+
connection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Promote, null, System.Data.IsolationLevel.Unspecified, _internalTransaction, true);
158+
returnValue = _connection.PromotedDTCToken;
157159

158-
// For Global Transactions, we need to set the Transaction Id since we use a Non-MSDTC Promoter type.
159-
if (_connection.IsGlobalTransaction)
160-
{
161-
if (SysTxForGlobalTransactions.SetDistributedTransactionIdentifier == null)
160+
// For Global Transactions, we need to set the Transaction Id since we use a Non-MSDTC Promoter type.
161+
if (_connection.IsGlobalTransaction)
162162
{
163-
throw SQL.UnsupportedSysTxForGlobalTransactions();
164-
}
163+
if (SysTxForGlobalTransactions.SetDistributedTransactionIdentifier == null)
164+
{
165+
throw SQL.UnsupportedSysTxForGlobalTransactions();
166+
}
165167

166-
if (!_connection.IsGlobalTransactionsEnabledForServer)
167-
{
168-
throw SQL.GlobalTransactionsNotEnabled();
168+
if (!_connection.IsGlobalTransactionsEnabledForServer)
169+
{
170+
throw SQL.GlobalTransactionsNotEnabled();
171+
}
172+
173+
SysTxForGlobalTransactions.SetDistributedTransactionIdentifier.Invoke(_atomicTransaction, new object[] { this, GetGlobalTxnIdentifierFromToken() });
169174
}
170175

171-
SysTxForGlobalTransactions.SetDistributedTransactionIdentifier.Invoke(_atomicTransaction, new object[] { this, GetGlobalTxnIdentifierFromToken() });
176+
promoteException = null;
172177
}
178+
catch (SqlException e)
179+
{
180+
promoteException = e;
173181

174-
promoteException = null;
182+
// Doom the connection, to make sure that the transaction is
183+
// eventually rolled back.
184+
// VSTS 144562: doom the connection while having the lock on it to prevent race condition with "Transaction Ended" Event
185+
connection.DoomThisConnection();
186+
}
187+
catch (InvalidOperationException e)
188+
{
189+
promoteException = e;
190+
connection.DoomThisConnection();
191+
}
175192
}
176-
catch (SqlException e)
177-
{
178-
promoteException = e;
193+
}
194+
catch (System.OutOfMemoryException e)
195+
{
196+
usersConnection.Abort(e);
197+
throw;
198+
}
199+
catch (System.StackOverflowException e)
200+
{
201+
usersConnection.Abort(e);
202+
throw;
203+
}
204+
catch (System.Threading.ThreadAbortException e)
205+
{
206+
usersConnection.Abort(e);
207+
throw;
208+
}
179209

180-
// Doom the connection, to make sure that the transaction is
181-
// eventually rolled back.
182-
// VSTS 144562: doom the connection while having the lock on it to prevent race condition with "Transaction Ended" Event
183-
connection.DoomThisConnection();
184-
}
185-
catch (InvalidOperationException e)
186-
{
187-
promoteException = e;
188-
connection.DoomThisConnection();
189-
}
210+
//Throw exception only if Transaction is till active and not yet aborted.
211+
if (promoteException != null && Transaction.TransactionInformation.Status != TransactionStatus.Aborted)
212+
{
213+
throw SQL.PromotionFailed(promoteException);
214+
}
215+
else
216+
{
217+
// The transaction was aborted externally, since it's already doomed above, we only log the same.
218+
SqlClientEventSource.Log.TraceEvent("<sc.SqlDelegatedTransaction.Promote|RES|CPOOL> {0}, Connection {1}, aborted during promotion.", ObjectID, connection.ObjectID);
190219
}
191220
}
192-
catch (System.OutOfMemoryException e)
193-
{
194-
usersConnection.Abort(e);
195-
throw;
196-
}
197-
catch (System.StackOverflowException e)
198-
{
199-
usersConnection.Abort(e);
200-
throw;
201-
}
202-
catch (System.Threading.ThreadAbortException e)
203-
{
204-
usersConnection.Abort(e);
205-
throw;
206-
}
207-
208-
if (promoteException != null)
221+
else
209222
{
210-
throw SQL.PromotionFailed(promoteException);
223+
// The transaction was aborted externally, doom the connection to make sure it's eventually rolled back and log the same.
224+
SqlClientEventSource.Log.TraceEvent("<sc.SqlDelegatedTransaction.Promote|RES|CPOOL> {0}, Connection {1}, aborted before promoting.", ObjectID, connection.ObjectID);
211225
}
212-
213226
return returnValue;
214227
}
215228

216229
// Called by transaction to initiate abort sequence
217230
public void Rollback(SinglePhaseEnlistment enlistment)
218231
{
219232
Debug.Assert(null != enlistment, "null enlistment?");
220-
SqlInternalConnection connection = null;
233+
SqlInternalConnection connection = GetValidConnection();
221234

222-
try
235+
if (null != connection)
223236
{
224-
connection = GetValidConnection();
225237
SqlConnection usersConnection = connection.Connection;
226-
SqlClientEventSource.Log.TraceEvent("<sc.SqlDelegatedTransaction.Rollback|RES|CPOOL> {0}, Connection {1}, aborting transaction.", ObjectID, connection.ObjectID);
238+
SqlClientEventSource.Log.TraceEvent("<sc.SqlDelegatedTransaction.Rollback|RES|CPOOL> {0}, Connection {1}, rolling back transaction.", ObjectID, connection.ObjectID);
227239
RuntimeHelpers.PrepareConstrainedRegions();
228240
try
229241
{
@@ -288,122 +300,128 @@ public void Rollback(SinglePhaseEnlistment enlistment)
288300
throw;
289301
}
290302
}
291-
catch (ObjectDisposedException)
303+
else
292304
{
293-
if (_atomicTransaction.TransactionInformation.Status == TransactionStatus.Active)
294-
{
295-
throw;
296-
}
297-
// Do not throw exception if connection is already aborted/ended from another thread,
298-
// replicate as done in TransactionEnded event handler.
305+
// The transaction was aborted, report that to SysTx and log the same.
306+
enlistment.Aborted();
307+
SqlClientEventSource.Log.TraceEvent("<sc.SqlDelegatedTransaction.Rollback|RES|CPOOL> {0}, Connection {1}, aborted before rollback.", ObjectID, connection.ObjectID);
299308
}
300309
}
301310

302311
// Called by the transaction to initiate commit sequence
303312
public void SinglePhaseCommit(SinglePhaseEnlistment enlistment)
304313
{
305314
Debug.Assert(null != enlistment, "null enlistment?");
306-
307315
SqlInternalConnection connection = GetValidConnection();
308-
SqlConnection usersConnection = connection.Connection;
309-
SqlClientEventSource.Log.TraceEvent("<sc.SqlDelegatedTransaction.SinglePhaseCommit|RES|CPOOL> {0}, Connection {1}, committing transaction.", ObjectID, connection.ObjectID);
310-
RuntimeHelpers.PrepareConstrainedRegions();
311-
try
312-
{
313-
// If the connection is dooomed, we can be certain that the
314-
// transaction will eventually be rolled back, and we shouldn't
315-
// attempt to commit it.
316-
if (connection.IsConnectionDoomed)
317-
{
318-
lock (connection)
319-
{
320-
_active = false; // set to inactive first, doesn't matter how the rest completes, this transaction is done.
321-
_connection = null;
322-
}
323316

324-
enlistment.Aborted(SQL.ConnectionDoomed());
325-
}
326-
else
317+
if (null != connection)
318+
{
319+
SqlConnection usersConnection = connection.Connection;
320+
SqlClientEventSource.Log.TraceEvent("<sc.SqlDelegatedTransaction.SinglePhaseCommit|RES|CPOOL> {0}, Connection {1}, committing transaction.", ObjectID, connection.ObjectID);
321+
RuntimeHelpers.PrepareConstrainedRegions();
322+
try
327323
{
328-
Exception commitException;
329-
lock (connection)
324+
// If the connection is dooomed, we can be certain that the
325+
// transaction will eventually be rolled back, and we shouldn't
326+
// attempt to commit it.
327+
if (connection.IsConnectionDoomed)
330328
{
331-
try
329+
lock (connection)
332330
{
333-
// Now that we've acquired the lock, make sure we still have valid state for this operation.
334-
ValidateActiveOnConnection(connection);
335-
336331
_active = false; // set to inactive first, doesn't matter how the rest completes, this transaction is done.
337-
_connection = null; // Set prior to ExecuteTransaction call in case this initiates a TransactionEnd event
338-
339-
connection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Commit, null, System.Data.IsolationLevel.Unspecified, _internalTransaction, true);
340-
commitException = null;
332+
_connection = null;
341333
}
342-
catch (SqlException e)
343-
{
344-
commitException = e;
345334

346-
// Doom the connection, to make sure that the transaction is
347-
// eventually rolled back.
348-
// VSTS 144562: doom the connection while having the lock on it to prevent race condition with "Transaction Ended" Event
349-
connection.DoomThisConnection();
350-
}
351-
catch (InvalidOperationException e)
352-
{
353-
commitException = e;
354-
connection.DoomThisConnection();
355-
}
335+
enlistment.Aborted(SQL.ConnectionDoomed());
356336
}
357-
if (commitException != null)
337+
else
358338
{
359-
// connection.ExecuteTransaction failed with exception
360-
if (_internalTransaction.IsCommitted)
339+
Exception commitException;
340+
lock (connection)
361341
{
362-
// Even though we got an exception, the transaction
363-
// was committed by the server.
364-
enlistment.Committed();
342+
try
343+
{
344+
// Now that we've acquired the lock, make sure we still have valid state for this operation.
345+
ValidateActiveOnConnection(connection);
346+
347+
_active = false; // set to inactive first, doesn't matter how the rest completes, this transaction is done.
348+
_connection = null; // Set prior to ExecuteTransaction call in case this initiates a TransactionEnd event
349+
350+
connection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Commit, null, System.Data.IsolationLevel.Unspecified, _internalTransaction, true);
351+
commitException = null;
352+
}
353+
catch (SqlException e)
354+
{
355+
commitException = e;
356+
357+
// Doom the connection, to make sure that the transaction is
358+
// eventually rolled back.
359+
// VSTS 144562: doom the connection while having the lock on it to prevent race condition with "Transaction Ended" Event
360+
connection.DoomThisConnection();
361+
}
362+
catch (InvalidOperationException e)
363+
{
364+
commitException = e;
365+
connection.DoomThisConnection();
366+
}
365367
}
366-
else if (_internalTransaction.IsAborted)
368+
if (commitException != null)
367369
{
368-
// The transaction was aborted, report that to
369-
// SysTx.
370-
enlistment.Aborted(commitException);
370+
// connection.ExecuteTransaction failed with exception
371+
if (_internalTransaction.IsCommitted)
372+
{
373+
// Even though we got an exception, the transaction
374+
// was committed by the server.
375+
enlistment.Committed();
376+
}
377+
else if (_internalTransaction.IsAborted)
378+
{
379+
// The transaction was aborted, report that to
380+
// SysTx.
381+
enlistment.Aborted(commitException);
382+
}
383+
else
384+
{
385+
// The transaction is still active, we cannot
386+
// know the state of the transaction.
387+
enlistment.InDoubt(commitException);
388+
}
389+
390+
// We eat the exception. This is called on the SysTx
391+
// thread, not the applications thread. If we don't
392+
// eat the exception an UnhandledException will occur,
393+
// causing the process to FailFast.
371394
}
372-
else
395+
396+
connection.CleanupConnectionOnTransactionCompletion(_atomicTransaction);
397+
if (commitException == null)
373398
{
374-
// The transaction is still active, we cannot
375-
// know the state of the transaction.
376-
enlistment.InDoubt(commitException);
399+
// connection.ExecuteTransaction succeeded
400+
enlistment.Committed();
377401
}
378-
379-
// We eat the exception. This is called on the SysTx
380-
// thread, not the applications thread. If we don't
381-
// eat the exception an UnhandledException will occur,
382-
// causing the process to FailFast.
383-
}
384-
385-
connection.CleanupConnectionOnTransactionCompletion(_atomicTransaction);
386-
if (commitException == null)
387-
{
388-
// connection.ExecuteTransaction succeeded
389-
enlistment.Committed();
390402
}
391403
}
404+
catch (System.OutOfMemoryException e)
405+
{
406+
usersConnection.Abort(e);
407+
throw;
408+
}
409+
catch (System.StackOverflowException e)
410+
{
411+
usersConnection.Abort(e);
412+
throw;
413+
}
414+
catch (System.Threading.ThreadAbortException e)
415+
{
416+
usersConnection.Abort(e);
417+
throw;
418+
}
392419
}
393-
catch (System.OutOfMemoryException e)
394-
{
395-
usersConnection.Abort(e);
396-
throw;
397-
}
398-
catch (System.StackOverflowException e)
399-
{
400-
usersConnection.Abort(e);
401-
throw;
402-
}
403-
catch (System.Threading.ThreadAbortException e)
420+
else
404421
{
405-
usersConnection.Abort(e);
406-
throw;
422+
// The transaction was aborted before we could commit, report that to SysTx and log the same.
423+
enlistment.Aborted();
424+
SqlClientEventSource.Log.TraceEvent("<sc.SqlDelegatedTransaction.SinglePhaseCommit|RES|CPOOL> {0}, Connection {1}, aborted before commit.", ObjectID, connection.ObjectID);
407425
}
408426
}
409427

@@ -436,7 +454,7 @@ internal void TransactionEnded(Transaction transaction)
436454
private SqlInternalConnection GetValidConnection()
437455
{
438456
SqlInternalConnection connection = _connection;
439-
if (null == connection)
457+
if (null == connection && Transaction.TransactionInformation.Status != TransactionStatus.Aborted)
440458
{
441459
throw ADP.ObjectDisposed(this);
442460
}

0 commit comments

Comments
 (0)