1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Diagnostics;
6 using System.Threading;
7 
8 namespace System.Transactions
9 {
10     public enum TransactionScopeOption
11     {
12         Required,
13         RequiresNew,
14         Suppress,
15     }
16 
17     //
18     //  The legacy TransactionScope uses TLS to store the ambient transaction. TLS data doesn't flow across thread continuations and hence legacy TransactionScope does not compose well with
19     //  new .Net async programming model constructs like Tasks and async/await. To enable TransactionScope to work with Task and async/await, a new TransactionScopeAsyncFlowOption
20     //  is introduced. When users opt-in the async flow option, ambient transaction will automatically flow across thread continuations and user can compose TransactionScope with Task and/or
21     //  async/await constructs.
22     //
23     public enum TransactionScopeAsyncFlowOption
24     {
25         Suppress, // Ambient transaction will be stored in TLS and will not flow across thread continuations.
26         Enabled,  // Ambient transaction will be stored in CallContext and will flow across thread continuations. This option will enable TransactionScope to compose well with Task and async/await.
27     }
28 
29     public enum EnterpriseServicesInteropOption
30     {
31         None = 0,
32         Automatic = 1,
33         Full = 2
34     }
35 
36     public sealed class TransactionScope : IDisposable
37     {
TransactionScope()38         public TransactionScope() : this(TransactionScopeOption.Required)
39         {
40         }
41 
TransactionScope(TransactionScopeOption scopeOption)42         public TransactionScope(TransactionScopeOption scopeOption)
43             : this(scopeOption, TransactionScopeAsyncFlowOption.Suppress)
44         {
45         }
46 
TransactionScope(TransactionScopeAsyncFlowOption asyncFlowOption)47         public TransactionScope(TransactionScopeAsyncFlowOption asyncFlowOption)
48             : this(TransactionScopeOption.Required, asyncFlowOption)
49         {
50         }
51 
TransactionScope( TransactionScopeOption scopeOption, TransactionScopeAsyncFlowOption asyncFlowOption )52         public TransactionScope(
53             TransactionScopeOption scopeOption,
54             TransactionScopeAsyncFlowOption asyncFlowOption
55             )
56         {
57             TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
58             if (etwLog.IsEnabled())
59             {
60                 etwLog.MethodEnter(TraceSourceType.TraceSourceBase, this);
61             }
62 
63             ValidateAndSetAsyncFlowOption(asyncFlowOption);
64 
65             if (NeedToCreateTransaction(scopeOption))
66             {
67                 _committableTransaction = new CommittableTransaction();
68                 _expectedCurrent = _committableTransaction.Clone();
69             }
70 
71             if (null == _expectedCurrent)
72             {
73                 if (etwLog.IsEnabled())
74                 {
75                     etwLog.TransactionScopeCreated(TransactionTraceIdentifier.Empty, TransactionScopeResult.NoTransaction);
76                 }
77             }
78             else
79             {
80                 TransactionScopeResult scopeResult;
81 
82                 if (null == _committableTransaction)
83                 {
84                     scopeResult = TransactionScopeResult.UsingExistingCurrent;
85                 }
86                 else
87                 {
88                     scopeResult = TransactionScopeResult.CreatedTransaction;
89                 }
90 
91                 if (etwLog.IsEnabled())
92                 {
93                     etwLog.TransactionScopeCreated(_expectedCurrent.TransactionTraceId, scopeResult);
94                 }
95             }
96 
97             PushScope();
98 
99             if (etwLog.IsEnabled())
100             {
101                 etwLog.MethodExit(TraceSourceType.TraceSourceBase, this);
102             }
103         }
104 
TransactionScope(TransactionScopeOption scopeOption, TimeSpan scopeTimeout)105         public TransactionScope(TransactionScopeOption scopeOption, TimeSpan scopeTimeout)
106             : this(scopeOption, scopeTimeout, TransactionScopeAsyncFlowOption.Suppress)
107         {
108         }
109 
TransactionScope( TransactionScopeOption scopeOption, TimeSpan scopeTimeout, TransactionScopeAsyncFlowOption asyncFlowOption )110         public TransactionScope(
111             TransactionScopeOption scopeOption,
112             TimeSpan scopeTimeout,
113             TransactionScopeAsyncFlowOption asyncFlowOption
114             )
115         {
116             TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
117             if (etwLog.IsEnabled())
118             {
119                 etwLog.MethodEnter(TraceSourceType.TraceSourceBase, this);
120             }
121 
122             ValidateScopeTimeout(nameof(scopeTimeout), scopeTimeout);
123             TimeSpan txTimeout = TransactionManager.ValidateTimeout(scopeTimeout);
124 
125             ValidateAndSetAsyncFlowOption(asyncFlowOption);
126 
127             if (NeedToCreateTransaction(scopeOption))
128             {
129                 _committableTransaction = new CommittableTransaction(txTimeout);
130                 _expectedCurrent = _committableTransaction.Clone();
131             }
132 
133             if ((null != _expectedCurrent) && (null == _committableTransaction) && (TimeSpan.Zero != scopeTimeout))
134             {
135                 // BUGBUG: Scopes should not use individual timers
136                 _scopeTimer = new Timer(
137                     TimerCallback,
138                     this,
139                     scopeTimeout,
140                     TimeSpan.Zero);
141             }
142 
143             if (null == _expectedCurrent)
144             {
145                 if (etwLog.IsEnabled())
146                 {
147                     etwLog.TransactionScopeCreated(TransactionTraceIdentifier.Empty, TransactionScopeResult.NoTransaction);
148                 }
149             }
150             else
151             {
152                 TransactionScopeResult scopeResult;
153 
154                 if (null == _committableTransaction)
155                 {
156                     scopeResult = TransactionScopeResult.UsingExistingCurrent;
157                 }
158                 else
159                 {
160                     scopeResult = TransactionScopeResult.CreatedTransaction;
161                 }
162 
163                 if (etwLog.IsEnabled())
164                 {
165                     etwLog.TransactionScopeCreated(_expectedCurrent.TransactionTraceId, scopeResult);
166                 }
167             }
168 
169             PushScope();
170 
171             if (etwLog.IsEnabled())
172             {
173                 etwLog.MethodExit(TraceSourceType.TraceSourceBase, this);
174             }
175         }
176 
TransactionScope(TransactionScopeOption scopeOption, TransactionOptions transactionOptions)177         public TransactionScope(TransactionScopeOption scopeOption, TransactionOptions transactionOptions)
178             : this(scopeOption, transactionOptions, TransactionScopeAsyncFlowOption.Suppress)
179         {
180         }
181 
TransactionScope( TransactionScopeOption scopeOption, TransactionOptions transactionOptions, TransactionScopeAsyncFlowOption asyncFlowOption )182         public TransactionScope(
183             TransactionScopeOption scopeOption,
184             TransactionOptions transactionOptions,
185             TransactionScopeAsyncFlowOption asyncFlowOption
186             )
187         {
188             TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
189             if (etwLog.IsEnabled())
190             {
191                 etwLog.MethodEnter(TraceSourceType.TraceSourceBase, this);
192             }
193 
194             ValidateScopeTimeout("transactionOptions.Timeout", transactionOptions.Timeout);
195             TimeSpan scopeTimeout = transactionOptions.Timeout;
196 
197             transactionOptions.Timeout = TransactionManager.ValidateTimeout(transactionOptions.Timeout);
198             TransactionManager.ValidateIsolationLevel(transactionOptions.IsolationLevel);
199 
200             ValidateAndSetAsyncFlowOption(asyncFlowOption);
201 
202             if (NeedToCreateTransaction(scopeOption))
203             {
204                 _committableTransaction = new CommittableTransaction(transactionOptions);
205                 _expectedCurrent = _committableTransaction.Clone();
206             }
207             else
208             {
209                 if (null != _expectedCurrent)
210                 {
211                     // If the requested IsolationLevel is stronger than that of the specified transaction, throw.
212                     if ((IsolationLevel.Unspecified != transactionOptions.IsolationLevel) && (_expectedCurrent.IsolationLevel != transactionOptions.IsolationLevel))
213                     {
214                         throw new ArgumentException(SR.TransactionScopeIsolationLevelDifferentFromTransaction, "transactionOptions.IsolationLevel");
215                     }
216                 }
217             }
218 
219             if ((null != _expectedCurrent) && (null == _committableTransaction) && (TimeSpan.Zero != scopeTimeout))
220             {
221                 // BUGBUG: Scopes should use a shared timer
222                 _scopeTimer = new Timer(
223                     TimerCallback,
224                     this,
225                     scopeTimeout,
226                     TimeSpan.Zero);
227             }
228 
229             if (null == _expectedCurrent)
230             {
231                 if (etwLog.IsEnabled())
232                 {
233                     etwLog.TransactionScopeCreated(TransactionTraceIdentifier.Empty, TransactionScopeResult.NoTransaction);
234                 }
235             }
236             else
237             {
238                 TransactionScopeResult scopeResult;
239 
240                 if (null == _committableTransaction)
241                 {
242                     scopeResult = TransactionScopeResult.UsingExistingCurrent;
243                 }
244                 else
245                 {
246                     scopeResult = TransactionScopeResult.CreatedTransaction;
247                 }
248 
249                 if (etwLog.IsEnabled())
250                 {
251                     etwLog.TransactionScopeCreated(_expectedCurrent.TransactionTraceId, scopeResult);
252                 }
253             }
254 
255             PushScope();
256 
257             if (etwLog.IsEnabled())
258             {
259                 etwLog.MethodExit(TraceSourceType.TraceSourceBase, this);
260             }
261         }
262 
TransactionScope( TransactionScopeOption scopeOption, TransactionOptions transactionOptions, EnterpriseServicesInteropOption interopOption)263         public TransactionScope(
264             TransactionScopeOption scopeOption,
265             TransactionOptions transactionOptions,
266             EnterpriseServicesInteropOption interopOption)
267         {
268             TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
269             if (etwLog.IsEnabled())
270             {
271                 etwLog.MethodEnter(TraceSourceType.TraceSourceBase, this);
272             }
273 
274             ValidateScopeTimeout("transactionOptions.Timeout", transactionOptions.Timeout);
275             TimeSpan scopeTimeout = transactionOptions.Timeout;
276 
277             transactionOptions.Timeout = TransactionManager.ValidateTimeout(transactionOptions.Timeout);
278             TransactionManager.ValidateIsolationLevel(transactionOptions.IsolationLevel);
279 
280             ValidateInteropOption(interopOption);
281             _interopModeSpecified = true;
282             _interopOption = interopOption;
283 
284             if (NeedToCreateTransaction(scopeOption))
285             {
286                 _committableTransaction = new CommittableTransaction(transactionOptions);
287                 _expectedCurrent = _committableTransaction.Clone();
288             }
289             else
290             {
291                 if (null != _expectedCurrent)
292                 {
293                     // If the requested IsolationLevel is stronger than that of the specified transaction, throw.
294                     if ((IsolationLevel.Unspecified != transactionOptions.IsolationLevel) && (_expectedCurrent.IsolationLevel != transactionOptions.IsolationLevel))
295                     {
296                         throw new ArgumentException(SR.TransactionScopeIsolationLevelDifferentFromTransaction, "transactionOptions.IsolationLevel");
297                     }
298                 }
299             }
300 
301             if ((null != _expectedCurrent) && (null == _committableTransaction) && (TimeSpan.Zero != scopeTimeout))
302             {
303                 // BUGBUG: Scopes should use a shared timer
304                 _scopeTimer = new Timer(
305                     TimerCallback,
306                     this,
307                     scopeTimeout,
308                     TimeSpan.Zero);
309             }
310 
311             if (null == _expectedCurrent)
312             {
313                 if (etwLog.IsEnabled())
314                 {
315                     etwLog.TransactionScopeCreated(TransactionTraceIdentifier.Empty, TransactionScopeResult.NoTransaction);
316                 }
317             }
318             else
319             {
320                 TransactionScopeResult scopeResult;
321 
322                 if (null == _committableTransaction)
323                 {
324                     scopeResult = TransactionScopeResult.UsingExistingCurrent;
325                 }
326                 else
327                 {
328                     scopeResult = TransactionScopeResult.CreatedTransaction;
329                 }
330 
331                 if (etwLog.IsEnabled())
332                 {
333                     etwLog.TransactionScopeCreated(_expectedCurrent.TransactionTraceId, scopeResult);
334                 }
335             }
336 
337             PushScope();
338 
339             if (etwLog.IsEnabled())
340             {
341                 etwLog.MethodExit(TraceSourceType.TraceSourceBase, this);
342             }
343         }
344 
TransactionScope(Transaction transactionToUse)345         public TransactionScope(Transaction transactionToUse)
346             : this(transactionToUse, TransactionScopeAsyncFlowOption.Suppress)
347         {
348         }
349 
TransactionScope( Transaction transactionToUse, TransactionScopeAsyncFlowOption asyncFlowOption )350         public TransactionScope(
351             Transaction transactionToUse,
352             TransactionScopeAsyncFlowOption asyncFlowOption
353             )
354         {
355             TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
356             if (etwLog.IsEnabled())
357             {
358                 etwLog.MethodEnter(TraceSourceType.TraceSourceBase, this);
359             }
360 
361             ValidateAndSetAsyncFlowOption(asyncFlowOption);
362 
363             Initialize(
364                 transactionToUse,
365                 TimeSpan.Zero,
366                 false);
367 
368             if (etwLog.IsEnabled())
369             {
370                 etwLog.MethodExit(TraceSourceType.TraceSourceBase, this);
371             }
372         }
373 
TransactionScope(Transaction transactionToUse, TimeSpan scopeTimeout)374         public TransactionScope(Transaction transactionToUse, TimeSpan scopeTimeout)
375             : this(transactionToUse, scopeTimeout, TransactionScopeAsyncFlowOption.Suppress)
376         {
377         }
378 
TransactionScope( Transaction transactionToUse, TimeSpan scopeTimeout, TransactionScopeAsyncFlowOption asyncFlowOption)379         public TransactionScope(
380             Transaction transactionToUse,
381             TimeSpan scopeTimeout,
382             TransactionScopeAsyncFlowOption asyncFlowOption)
383         {
384             TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
385             if (etwLog.IsEnabled())
386             {
387                 etwLog.MethodEnter(TraceSourceType.TraceSourceBase, this);
388             }
389 
390             ValidateAndSetAsyncFlowOption(asyncFlowOption);
391 
392             Initialize(
393                 transactionToUse,
394                 scopeTimeout,
395                 false);
396 
397             if (etwLog.IsEnabled())
398             {
399                 etwLog.MethodExit(TraceSourceType.TraceSourceBase, this);
400             }
401         }
402 
TransactionScope( Transaction transactionToUse, TimeSpan scopeTimeout, EnterpriseServicesInteropOption interopOption )403         public TransactionScope(
404             Transaction transactionToUse,
405             TimeSpan scopeTimeout,
406             EnterpriseServicesInteropOption interopOption
407         )
408         {
409             TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
410             if (etwLog.IsEnabled())
411             {
412                 etwLog.MethodEnter(TraceSourceType.TraceSourceBase, this);
413             }
414 
415             ValidateInteropOption(interopOption);
416             _interopOption = interopOption;
417 
418             Initialize(
419                 transactionToUse,
420                 scopeTimeout,
421                 true);
422 
423             if (etwLog.IsEnabled())
424             {
425                 etwLog.MethodExit(TraceSourceType.TraceSourceBase, this);
426             }
427         }
428 
NeedToCreateTransaction(TransactionScopeOption scopeOption)429         private bool NeedToCreateTransaction(TransactionScopeOption scopeOption)
430         {
431             bool retVal = false;
432 
433             CommonInitialize();
434 
435             // If the options specify NoTransactionNeeded, that trumps everything else.
436             switch (scopeOption)
437             {
438                 case TransactionScopeOption.Suppress:
439                     _expectedCurrent = null;
440                     retVal = false;
441                     break;
442 
443                 case TransactionScopeOption.Required:
444                     _expectedCurrent = _savedCurrent;
445                     // If current is null, we need to create one.
446                     if (null == _expectedCurrent)
447                     {
448                         retVal = true;
449                     }
450                     break;
451 
452                 case TransactionScopeOption.RequiresNew:
453                     retVal = true;
454                     break;
455 
456                 default:
457                     throw new ArgumentOutOfRangeException(nameof(scopeOption));
458             }
459 
460             return retVal;
461         }
462 
Initialize( Transaction transactionToUse, TimeSpan scopeTimeout, bool interopModeSpecified)463         private void Initialize(
464             Transaction transactionToUse,
465             TimeSpan scopeTimeout,
466             bool interopModeSpecified)
467         {
468             if (null == transactionToUse)
469             {
470                 throw new ArgumentNullException(nameof(transactionToUse));
471             }
472 
473             ValidateScopeTimeout(nameof(scopeTimeout), scopeTimeout);
474 
475             CommonInitialize();
476 
477             if (TimeSpan.Zero != scopeTimeout)
478             {
479                 _scopeTimer = new Timer(
480                     TimerCallback,
481                     this,
482                     scopeTimeout,
483                     TimeSpan.Zero
484                     );
485             }
486 
487             _expectedCurrent = transactionToUse;
488             _interopModeSpecified = interopModeSpecified;
489 
490             TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
491             if (etwLog.IsEnabled())
492             {
493                 etwLog.TransactionScopeCreated(_expectedCurrent.TransactionTraceId, TransactionScopeResult.TransactionPassed);
494             }
495 
496             PushScope();
497         }
498 
499 
500         // We don't have a finalizer (~TransactionScope) because all it would be able to do is try to
501         // operate on other managed objects (the transaction), which is not safe to do because they may
502         // already have been finalized.
503 
Dispose()504         public void Dispose()
505         {
506             bool successful = false;
507 
508             TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
509             if (etwLog.IsEnabled())
510             {
511                 etwLog.MethodEnter(TraceSourceType.TraceSourceBase, this);
512             }
513             if (_disposed)
514             {
515                 if (etwLog.IsEnabled())
516                 {
517                     etwLog.MethodExit(TraceSourceType.TraceSourceBase, this);
518                 }
519                 return;
520             }
521 
522             // Dispose for a scope can only be called on the thread where the scope was created.
523             if ((_scopeThread != Thread.CurrentThread) && !AsyncFlowEnabled)
524             {
525                 if (etwLog.IsEnabled())
526                 {
527                     etwLog.InvalidOperation("TransactionScope", "InvalidScopeThread");
528                 }
529 
530                 throw new InvalidOperationException(SR.InvalidScopeThread);
531             }
532 
533             Exception exToThrow = null;
534 
535             try
536             {
537                 // Single threaded from this point
538                 _disposed = true;
539 
540                 // First, lets pop the "stack" of TransactionScopes and dispose each one that is above us in
541                 // the stack, making sure they are NOT consistent before disposing them.
542 
543                 // Optimize the first lookup by getting both the actual current scope and actual current
544                 // transaction at the same time.
545                 TransactionScope actualCurrentScope = _threadContextData.CurrentScope;
546                 Transaction contextTransaction = null;
547                 Transaction current = Transaction.FastGetTransaction(actualCurrentScope, _threadContextData, out contextTransaction);
548 
549                 if (!Equals(actualCurrentScope))
550                 {
551                     // Ok this is bad.  But just how bad is it.  The worst case scenario is that someone is
552                     // poping scopes out of order and has placed a new transaction in the top level scope.
553                     // Check for that now.
554                     if (actualCurrentScope == null)
555                     {
556                         // Something must have gone wrong trying to clean up a bad scope
557                         // stack previously.
558                         // Make a best effort to abort the active transaction.
559                         Transaction rollbackTransaction = _committableTransaction;
560                         if (rollbackTransaction == null)
561                         {
562                             rollbackTransaction = _dependentTransaction;
563                         }
564                         Debug.Assert(rollbackTransaction != null);
565                         rollbackTransaction.Rollback();
566 
567                         successful = true;
568                         throw TransactionException.CreateInvalidOperationException(
569                             TraceSourceType.TraceSourceBase, SR.TransactionScopeInvalidNesting, null, rollbackTransaction.DistributedTxId);
570                     }
571                     // Verify that expectedCurrent is the same as the "current" current if we the interopOption value is None.
572                     else if (EnterpriseServicesInteropOption.None == actualCurrentScope._interopOption)
573                     {
574                         if (((null != actualCurrentScope._expectedCurrent) && (!actualCurrentScope._expectedCurrent.Equals(current)))
575                             ||
576                             ((null != current) && (null == actualCurrentScope._expectedCurrent))
577                             )
578                         {
579                             TransactionTraceIdentifier myId;
580                             TransactionTraceIdentifier currentId;
581 
582                             if (null == current)
583                             {
584                                 currentId = TransactionTraceIdentifier.Empty;
585                             }
586                             else
587                             {
588                                 currentId = current.TransactionTraceId;
589                             }
590 
591                             if (null == _expectedCurrent)
592                             {
593                                 myId = TransactionTraceIdentifier.Empty;
594                             }
595                             else
596                             {
597                                 myId = _expectedCurrent.TransactionTraceId;
598                             }
599 
600                             if (etwLog.IsEnabled())
601                             {
602                                 etwLog.TransactionScopeCurrentChanged(currentId, myId);
603                             }
604 
605                             exToThrow = TransactionException.CreateInvalidOperationException(TraceSourceType.TraceSourceBase, SR.TransactionScopeIncorrectCurrent, null,
606                                 current == null ? Guid.Empty : current.DistributedTxId);
607 
608                             // If there is a current transaction, abort it.
609                             if (null != current)
610                             {
611                                 try
612                                 {
613                                     current.Rollback();
614                                 }
615                                 catch (TransactionException)
616                                 {
617                                     // we are already going to throw and exception, so just ignore this one.
618                                 }
619                                 catch (ObjectDisposedException)
620                                 {
621                                     // Dito
622                                 }
623                             }
624                         }
625                     }
626 
627                     // Now fix up the scopes
628                     while (!Equals(actualCurrentScope))
629                     {
630                         if (null == exToThrow)
631                         {
632                             exToThrow = TransactionException.CreateInvalidOperationException(TraceSourceType.TraceSourceBase, SR.TransactionScopeInvalidNesting, null,
633                                 current == null ? Guid.Empty : current.DistributedTxId);
634                         }
635 
636                         if (null == actualCurrentScope._expectedCurrent)
637                         {
638                             if (etwLog.IsEnabled())
639                             {
640                                 etwLog.TransactionScopeNestedIncorrectly(TransactionTraceIdentifier.Empty);
641                             }
642                         }
643                         else
644                         {
645                             if (etwLog.IsEnabled())
646                             {
647                                 etwLog.TransactionScopeNestedIncorrectly(actualCurrentScope._expectedCurrent.TransactionTraceId);
648                             }
649                         }
650 
651                         actualCurrentScope._complete = false;
652                         try
653                         {
654                             actualCurrentScope.InternalDispose();
655                         }
656                         catch (TransactionException)
657                         {
658                             // we are already going to throw an exception, so just ignore this one.
659                         }
660 
661                         actualCurrentScope = _threadContextData.CurrentScope;
662 
663                         // We want to fail this scope, too, because work may have been done in one of these other
664                         // nested scopes that really should have been done in my scope.
665                         _complete = false;
666                     }
667                 }
668                 else
669                 {
670                     // Verify that expectedCurrent is the same as the "current" current if we the interopOption value is None.
671                     // If we got here, actualCurrentScope is the same as "this".
672                     if (EnterpriseServicesInteropOption.None == _interopOption)
673                     {
674                         if (((null != _expectedCurrent) && (!_expectedCurrent.Equals(current)))
675                             || ((null != current) && (null == _expectedCurrent))
676                             )
677                         {
678                             TransactionTraceIdentifier myId;
679                             TransactionTraceIdentifier currentId;
680 
681                             if (null == current)
682                             {
683                                 currentId = TransactionTraceIdentifier.Empty;
684                             }
685                             else
686                             {
687                                 currentId = current.TransactionTraceId;
688                             }
689 
690                             if (null == _expectedCurrent)
691                             {
692                                 myId = TransactionTraceIdentifier.Empty;
693                             }
694                             else
695                             {
696                                 myId = _expectedCurrent.TransactionTraceId;
697                             }
698 
699                             if (etwLog.IsEnabled())
700                             {
701                                 etwLog.TransactionScopeCurrentChanged(currentId, myId);
702                             }
703 
704                             if (null == exToThrow)
705                             {
706                                 exToThrow = TransactionException.CreateInvalidOperationException(TraceSourceType.TraceSourceBase, SR.TransactionScopeIncorrectCurrent, null,
707                                     current == null ? Guid.Empty : current.DistributedTxId);
708                             }
709 
710                             // If there is a current transaction, abort it.
711                             if (null != current)
712                             {
713                                 try
714                                 {
715                                     current.Rollback();
716                                 }
717                                 catch (TransactionException)
718                                 {
719                                     // we are already going to throw and exception, so just ignore this one.
720                                 }
721                                 catch (ObjectDisposedException)
722                                 {
723                                     // Dito
724                                 }
725                             }
726                             // Set consistent to false so that the subsequent call to
727                             // InternalDispose below will rollback this.expectedCurrent.
728                             _complete = false;
729                         }
730                     }
731                 }
732                 successful = true;
733             }
734             finally
735             {
736                 if (!successful)
737                 {
738                     PopScope();
739                 }
740             }
741 
742             // No try..catch here.  Just let any exception thrown by InternalDispose go out.
743             InternalDispose();
744 
745             if (null != exToThrow)
746             {
747                 throw exToThrow;
748             }
749 
750             if (etwLog.IsEnabled())
751             {
752                 etwLog.MethodExit(TraceSourceType.TraceSourceBase, this);
753             }
754         }
755 
InternalDispose()756         private void InternalDispose()
757         {
758             // Set this if it is called internally.
759             _disposed = true;
760 
761             try
762             {
763                 PopScope();
764 
765                 TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
766                 if (null == _expectedCurrent)
767                 {
768                     if (etwLog.IsEnabled())
769                     {
770                         etwLog.TransactionScopeDisposed(TransactionTraceIdentifier.Empty);
771                     }
772                 }
773                 else
774                 {
775                     if (etwLog.IsEnabled())
776                     {
777                         etwLog.TransactionScopeDisposed(_expectedCurrent.TransactionTraceId);
778                     }
779                 }
780 
781                 // If Transaction.Current is not null, we have work to do.  Otherwise, we don't, except to replace
782                 // the previous value.
783                 if (null != _expectedCurrent)
784                 {
785                     if (!_complete)
786                     {
787                         if (etwLog.IsEnabled())
788                         {
789                             etwLog.TransactionScopeIncomplete(_expectedCurrent.TransactionTraceId);
790                         }
791 
792                         //
793                         // Note: Rollback is not called on expected current because someone could conceiveably
794                         //       dispose expectedCurrent out from under the transaction scope.
795                         //
796                         Transaction rollbackTransaction = _committableTransaction;
797                         if (rollbackTransaction == null)
798                         {
799                             rollbackTransaction = _dependentTransaction;
800                         }
801                         Debug.Assert(rollbackTransaction != null);
802                         rollbackTransaction.Rollback();
803                     }
804                     else
805                     {
806                         // If we are supposed to commit on dispose, cast to CommittableTransaction and commit it.
807                         if (null != _committableTransaction)
808                         {
809                             _committableTransaction.Commit();
810                         }
811                         else
812                         {
813                             Debug.Assert(null != _dependentTransaction, "null != this.dependentTransaction");
814                             _dependentTransaction.Complete();
815                         }
816                     }
817                 }
818             }
819             finally
820             {
821                 if (null != _scopeTimer)
822                 {
823                     _scopeTimer.Dispose();
824                 }
825 
826                 if (null != _committableTransaction)
827                 {
828                     _committableTransaction.Dispose();
829 
830                     // If we created the committable transaction then we placed a clone in expectedCurrent
831                     // and it needs to be disposed as well.
832                     _expectedCurrent.Dispose();
833                 }
834 
835                 if (null != _dependentTransaction)
836                 {
837                     _dependentTransaction.Dispose();
838                 }
839             }
840         }
841 
Complete()842         public void Complete()
843         {
844             TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
845             if (etwLog.IsEnabled())
846             {
847                 etwLog.MethodEnter(TraceSourceType.TraceSourceBase, this);
848             }
849             if (_disposed)
850             {
851                 throw new ObjectDisposedException(nameof(TransactionScope));
852             }
853 
854             if (_complete)
855             {
856                 throw TransactionException.CreateInvalidOperationException(TraceSourceType.TraceSourceBase, SR.DisposeScope, null);
857             }
858 
859             _complete = true;
860             if (etwLog.IsEnabled())
861             {
862                 etwLog.MethodExit(TraceSourceType.TraceSourceBase, this);
863             }
864         }
865 
TimerCallback(object state)866         private static void TimerCallback(object state)
867         {
868             TransactionScope scope = state as TransactionScope;
869             if (null == scope)
870             {
871                 TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
872                 if (etwLog.IsEnabled())
873                 {
874                     etwLog.TransactionScopeInternalError("TransactionScopeTimerObjectInvalid");
875                 }
876 
877                 throw TransactionException.Create(TraceSourceType.TraceSourceBase, SR.InternalError + SR.TransactionScopeTimerObjectInvalid, null);
878             }
879 
880             scope.Timeout();
881         }
882 
Timeout()883         private void Timeout()
884         {
885             if ((!_complete) && (null != _expectedCurrent))
886             {
887                 TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
888                 if (etwLog.IsEnabled())
889                 {
890                     etwLog.TransactionScopeTimeout(_expectedCurrent.TransactionTraceId);
891                 }
892                 try
893                 {
894                     _expectedCurrent.Rollback();
895                 }
896                 catch (ObjectDisposedException ex)
897                 {
898                     // Tolerate the fact that the transaction has already been disposed.
899                     if (etwLog.IsEnabled())
900                     {
901                         etwLog.ExceptionConsumed(TraceSourceType.TraceSourceBase, ex);
902                     }
903                 }
904                 catch (TransactionException txEx)
905                 {
906                     // Tolerate transaction exceptions
907                     if (etwLog.IsEnabled())
908                     {
909                         etwLog.ExceptionConsumed(TraceSourceType.TraceSourceBase, txEx);
910                     }
911                 }
912             }
913         }
914 
CommonInitialize()915         private void CommonInitialize()
916         {
917             ContextKey = new ContextKey();
918             _complete = false;
919             _dependentTransaction = null;
920             _disposed = false;
921             _committableTransaction = null;
922             _expectedCurrent = null;
923             _scopeTimer = null;
924             _scopeThread = Thread.CurrentThread;
925 
926             Transaction.GetCurrentTransactionAndScope(
927                             AsyncFlowEnabled ? TxLookup.DefaultCallContext : TxLookup.DefaultTLS,
928                             out _savedCurrent,
929                             out _savedCurrentScope,
930                             out _contextTransaction
931                             );
932 
933             // Calling validate here as we need to make sure the existing parent ambient transaction scope is already looked up to see if we have ES interop enabled.
934             ValidateAsyncFlowOptionAndESInteropOption();
935         }
936 
937         // PushScope
938         //
939         // Push a transaction scope onto the stack.
PushScope()940         private void PushScope()
941         {
942             // Fixup the interop mode before we set current.
943             if (!_interopModeSpecified)
944             {
945                 // Transaction.InteropMode will take the interop mode on
946                 // for the scope in currentScope into account.
947                 _interopOption = Transaction.InteropMode(_savedCurrentScope);
948             }
949 
950             // async function yield at await points and main thread can continue execution. We need to make sure the TLS data are restored appropriately.
951             SaveTLSContextData();
952 
953             if (AsyncFlowEnabled)
954             {
955                 // Async Flow is enabled and CallContext will be used for ambient transaction.
956                 _threadContextData = CallContextCurrentData.CreateOrGetCurrentData(ContextKey);
957 
958                 if (_savedCurrentScope == null && _savedCurrent == null)
959                 {
960                     // Clear TLS data so that transaction doesn't leak from current thread.
961                     ContextData.TLSCurrentData = null;
962                 }
963             }
964             else
965             {
966                 // Legacy TransactionScope. Use TLS to track ambient transaction context.
967                 _threadContextData = ContextData.TLSCurrentData;
968                 CallContextCurrentData.ClearCurrentData(ContextKey, false);
969             }
970 
971             // This call needs to be done first
972             SetCurrent(_expectedCurrent);
973             _threadContextData.CurrentScope = this;
974         }
975 
976         // PopScope
977         //
978         // Pop the current transaction scope off the top of the stack
PopScope()979         private void PopScope()
980         {
981             bool shouldRestoreContextData = true;
982 
983             // Clear the current TransactionScope CallContext data
984             if (AsyncFlowEnabled)
985             {
986                 CallContextCurrentData.ClearCurrentData(ContextKey, true);
987             }
988 
989             if (_scopeThread == Thread.CurrentThread)
990             {
991                 // async function yield at await points and main thread can continue execution. We need to make sure the TLS data are restored appropriately.
992                 // Restore the TLS only if the thread Ids match.
993                 RestoreSavedTLSContextData();
994             }
995 
996             // Restore threadContextData to parent CallContext or TLS data
997             if (_savedCurrentScope != null)
998             {
999                 if (_savedCurrentScope.AsyncFlowEnabled)
1000                 {
1001                     _threadContextData = CallContextCurrentData.CreateOrGetCurrentData(_savedCurrentScope.ContextKey);
1002                 }
1003                 else
1004                 {
1005                     if (_savedCurrentScope._scopeThread != Thread.CurrentThread)
1006                     {
1007                         // Clear TLS data so that transaction doesn't leak from current thread.
1008                         shouldRestoreContextData = false;
1009                         ContextData.TLSCurrentData = null;
1010                     }
1011                     else
1012                     {
1013                         _threadContextData = ContextData.TLSCurrentData;
1014                     }
1015 
1016                     CallContextCurrentData.ClearCurrentData(_savedCurrentScope.ContextKey, false);
1017                 }
1018             }
1019             else
1020             {
1021                 // No parent TransactionScope present
1022 
1023                 // Clear any CallContext data
1024                 CallContextCurrentData.ClearCurrentData(null, false);
1025 
1026                 if (_scopeThread != Thread.CurrentThread)
1027                 {
1028                     // Clear TLS data so that transaction doesn't leak from current thread.
1029                     shouldRestoreContextData = false;
1030                     ContextData.TLSCurrentData = null;
1031                 }
1032                 else
1033                 {
1034                     // Restore the current data to TLS.
1035                     ContextData.TLSCurrentData = _threadContextData;
1036                 }
1037             }
1038 
1039             // prevent restoring the context in an unexpected thread due to thread switch during TransactionScope's Dispose
1040             if (shouldRestoreContextData)
1041             {
1042                 _threadContextData.CurrentScope = _savedCurrentScope;
1043                 RestoreCurrent();
1044             }
1045         }
1046 
1047         // SetCurrent
1048         //
1049         // Place the given value in current by whatever means necessary for interop mode.
SetCurrent(Transaction newCurrent)1050         private void SetCurrent(Transaction newCurrent)
1051         {
1052             // Keep a dependent clone of current if we don't have one and we are not committable
1053             if (_dependentTransaction == null && _committableTransaction == null)
1054             {
1055                 if (newCurrent != null)
1056                 {
1057                     _dependentTransaction = newCurrent.DependentClone(DependentCloneOption.RollbackIfNotComplete);
1058                 }
1059             }
1060 
1061             switch (_interopOption)
1062             {
1063                 case EnterpriseServicesInteropOption.None:
1064                     _threadContextData.CurrentTransaction = newCurrent;
1065                     break;
1066 
1067                 case EnterpriseServicesInteropOption.Automatic:
1068                     EnterpriseServices.VerifyEnterpriseServicesOk();
1069                     if (EnterpriseServices.UseServiceDomainForCurrent())
1070                     {
1071                         EnterpriseServices.PushServiceDomain(newCurrent);
1072                     }
1073                     else
1074                     {
1075                         _threadContextData.CurrentTransaction = newCurrent;
1076                     }
1077                     break;
1078 
1079                 case EnterpriseServicesInteropOption.Full:
1080                     EnterpriseServices.VerifyEnterpriseServicesOk();
1081                     EnterpriseServices.PushServiceDomain(newCurrent);
1082                     break;
1083             }
1084         }
1085 
SaveTLSContextData()1086         private void SaveTLSContextData()
1087         {
1088             if (_savedTLSContextData == null)
1089             {
1090                 _savedTLSContextData = new ContextData(false);
1091             }
1092 
1093             _savedTLSContextData.CurrentScope = ContextData.TLSCurrentData.CurrentScope;
1094             _savedTLSContextData.CurrentTransaction = ContextData.TLSCurrentData.CurrentTransaction;
1095             _savedTLSContextData.DefaultComContextState = ContextData.TLSCurrentData.DefaultComContextState;
1096             _savedTLSContextData.WeakDefaultComContext = ContextData.TLSCurrentData.WeakDefaultComContext;
1097         }
1098 
RestoreSavedTLSContextData()1099         private void RestoreSavedTLSContextData()
1100         {
1101             if (_savedTLSContextData != null)
1102             {
1103                 ContextData.TLSCurrentData.CurrentScope = _savedTLSContextData.CurrentScope;
1104                 ContextData.TLSCurrentData.CurrentTransaction = _savedTLSContextData.CurrentTransaction;
1105                 ContextData.TLSCurrentData.DefaultComContextState = _savedTLSContextData.DefaultComContextState;
1106                 ContextData.TLSCurrentData.WeakDefaultComContext = _savedTLSContextData.WeakDefaultComContext;
1107             }
1108         }
1109 
1110         // RestoreCurrent
1111         //
1112         // Restore current to it's previous value depending on how it was changed for this scope.
RestoreCurrent()1113         private void RestoreCurrent()
1114         {
1115             if (EnterpriseServices.CreatedServiceDomain)
1116             {
1117                 EnterpriseServices.LeaveServiceDomain();
1118             }
1119 
1120             // Only restore the value that was actually in the context.
1121             _threadContextData.CurrentTransaction = _contextTransaction;
1122         }
1123 
1124 
1125         // ValidateInteropOption
1126         //
1127         // Validate a given interop Option
ValidateInteropOption(EnterpriseServicesInteropOption interopOption)1128         private void ValidateInteropOption(EnterpriseServicesInteropOption interopOption)
1129         {
1130             if (interopOption < EnterpriseServicesInteropOption.None || interopOption > EnterpriseServicesInteropOption.Full)
1131             {
1132                 throw new ArgumentOutOfRangeException(nameof(interopOption));
1133             }
1134         }
1135 
1136 
1137         // ValidateScopeTimeout
1138         //
1139         // Scope timeouts are not governed by MaxTimeout and therefore need a special validate function
ValidateScopeTimeout(string paramName, TimeSpan scopeTimeout)1140         private void ValidateScopeTimeout(string paramName, TimeSpan scopeTimeout)
1141         {
1142             if (scopeTimeout < TimeSpan.Zero)
1143             {
1144                 throw new ArgumentOutOfRangeException(paramName);
1145             }
1146         }
1147 
ValidateAndSetAsyncFlowOption(TransactionScopeAsyncFlowOption asyncFlowOption)1148         private void ValidateAndSetAsyncFlowOption(TransactionScopeAsyncFlowOption asyncFlowOption)
1149         {
1150             if (asyncFlowOption < TransactionScopeAsyncFlowOption.Suppress || asyncFlowOption > TransactionScopeAsyncFlowOption.Enabled)
1151             {
1152                 throw new ArgumentOutOfRangeException(nameof(asyncFlowOption));
1153             }
1154 
1155             if (asyncFlowOption == TransactionScopeAsyncFlowOption.Enabled)
1156             {
1157                 AsyncFlowEnabled = true;
1158             }
1159         }
1160 
1161         // The validate method assumes that the existing parent ambient transaction scope is already looked up.
ValidateAsyncFlowOptionAndESInteropOption()1162         private void ValidateAsyncFlowOptionAndESInteropOption()
1163         {
1164             if (AsyncFlowEnabled)
1165             {
1166                 EnterpriseServicesInteropOption currentInteropOption = _interopOption;
1167                 if (!_interopModeSpecified)
1168                 {
1169                     // Transaction.InteropMode will take the interop mode on
1170                     // for the scope in currentScope into account.
1171                     currentInteropOption = Transaction.InteropMode(_savedCurrentScope);
1172                 }
1173 
1174                 if (currentInteropOption != EnterpriseServicesInteropOption.None)
1175                 {
1176                     throw new NotSupportedException(SR.AsyncFlowAndESInteropNotSupported);
1177                 }
1178             }
1179         }
1180 
1181         // Denotes the action to take when the scope is disposed.
1182         private bool _complete;
1183         internal bool ScopeComplete
1184         {
1185             get
1186             {
1187                 return _complete;
1188             }
1189         }
1190 
1191         // Storage location for the previous current transaction.
1192         private Transaction _savedCurrent;
1193 
1194         // To ensure that we don't restore a value for current that was
1195         // returned to us by an external entity keep the value that was actually
1196         // in TLS when the scope was created.
1197         private Transaction _contextTransaction;
1198 
1199         // Storage for the value to restore to current
1200         private TransactionScope _savedCurrentScope;
1201 
1202         // Store a reference to the context data object for this scope.
1203         private ContextData _threadContextData;
1204 
1205         private ContextData _savedTLSContextData;
1206 
1207         // Store a reference to the value that this scope expects for current
1208         private Transaction _expectedCurrent;
1209 
1210         // Store a reference to the committable form of this transaction if
1211         // the scope made one.
1212         private CommittableTransaction _committableTransaction;
1213 
1214         // Store a reference to the scopes transaction guard.
1215         private DependentTransaction _dependentTransaction;
1216 
1217         // Note when the scope is disposed.
1218         private bool _disposed;
1219 
1220         // BUGBUG: A shared timer should be used.
1221         // Individual timer for this scope.
1222         private Timer _scopeTimer;
1223 
1224         // Store a reference to the thread on which the scope was created so that we can
1225         // check to make sure that the dispose pattern for scope is being used correctly.
1226         private Thread _scopeThread;
1227 
1228         // Store the interop mode for this transaction scope.
1229         private bool _interopModeSpecified = false;
1230         private EnterpriseServicesInteropOption _interopOption;
1231         internal EnterpriseServicesInteropOption InteropMode
1232         {
1233             get
1234             {
1235                 return _interopOption;
1236             }
1237         }
1238 
1239         internal ContextKey ContextKey
1240         {
1241             get;
1242             private set;
1243         }
1244 
1245         internal bool AsyncFlowEnabled
1246         {
1247             get;
1248             private set;
1249         }
1250     }
1251 }
1252