@@ -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