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.Runtime.CompilerServices;
7 using System.Runtime.Serialization;
8 using System.Threading;
9 using System.Transactions.Distributed;
10 
11 namespace System.Transactions
12 {
13     public class TransactionEventArgs : EventArgs
14     {
15         internal Transaction _transaction;
16         public Transaction Transaction => _transaction;
17     }
18 
TransactionCompletedEventHandler(object sender, TransactionEventArgs e)19     public delegate void TransactionCompletedEventHandler(object sender, TransactionEventArgs e);
20 
21     public enum IsolationLevel
22     {
23         Serializable = 0,
24         RepeatableRead = 1,
25         ReadCommitted = 2,
26         ReadUncommitted = 3,
27         Snapshot = 4,
28         Chaos = 5,
29         Unspecified = 6,
30     }
31 
32     public enum TransactionStatus
33     {
34         Active = 0,
35         Committed = 1,
36         Aborted = 2,
37         InDoubt = 3
38     }
39 
40     public enum DependentCloneOption
41     {
42         BlockCommitUntilComplete = 0,
43         RollbackIfNotComplete = 1,
44     }
45 
46     [Flags]
47     public enum EnlistmentOptions
48     {
49         None = 0x0,
50         EnlistDuringPrepareRequired = 0x1,
51     }
52 
53     // When we serialize a Transaction, we specify the type DistributedTransaction, so a Transaction never
54     // actually gets deserialized.
55     [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2229", Justification = "Serialization not yet supported and will be done using DistributedTransaction")]
56     [Serializable]
57     public class Transaction : IDisposable, ISerializable
58     {
59         // UseServiceDomain
60         //
61         // Property tells parts of system.transactions if it should use a
62         // service domain for current.
63         [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
UseServiceDomainForCurrent()64         internal static bool UseServiceDomainForCurrent() => false;
65 
66         // InteropMode
67         //
68         // This property figures out the current interop mode based on the
69         // top of the transaction scope stack as well as the default mode
70         // from config.
InteropMode(TransactionScope currentScope)71         internal static EnterpriseServicesInteropOption InteropMode(TransactionScope currentScope)
72         {
73             if (currentScope != null)
74             {
75                 return currentScope.InteropMode;
76             }
77 
78             return EnterpriseServicesInteropOption.None;
79         }
80 
FastGetTransaction(TransactionScope currentScope, ContextData contextData, out Transaction contextTransaction)81         internal static Transaction FastGetTransaction(TransactionScope currentScope, ContextData contextData, out Transaction contextTransaction)
82         {
83             Transaction current = null;
84             contextTransaction = null;
85 
86             contextTransaction = contextData.CurrentTransaction;
87 
88             switch (InteropMode(currentScope))
89             {
90                 case EnterpriseServicesInteropOption.None:
91 
92                     current = contextTransaction;
93 
94                     // If there is a transaction in the execution context or if there is a current transaction scope
95                     // then honer the transaction context.
96                     if (current == null && currentScope == null)
97                     {
98                         // Otherwise check for an external current.
99                         if (TransactionManager.s_currentDelegateSet)
100                         {
101                             current = TransactionManager.s_currentDelegate();
102                         }
103                         else
104                         {
105                             current = EnterpriseServices.GetContextTransaction(contextData);
106                         }
107                     }
108                     break;
109 
110                 case EnterpriseServicesInteropOption.Full:
111                     current = EnterpriseServices.GetContextTransaction(contextData);
112                     break;
113 
114                 case EnterpriseServicesInteropOption.Automatic:
115                     if (EnterpriseServices.UseServiceDomainForCurrent())
116                     {
117                         current = EnterpriseServices.GetContextTransaction(contextData);
118                     }
119                     else
120                     {
121                         current = contextData.CurrentTransaction;
122                     }
123                     break;
124             }
125 
126             return current;
127         }
128 
129 
130         // GetCurrentTransactionAndScope
131         //
132         // Returns both the current transaction and scope.  This is implemented for optimizations
133         // in TransactionScope because it is required to get both of them in several cases.
GetCurrentTransactionAndScope( TxLookup defaultLookup, out Transaction current, out TransactionScope currentScope, out Transaction contextTransaction)134         internal static void GetCurrentTransactionAndScope(
135             TxLookup defaultLookup,
136             out Transaction current,
137             out TransactionScope currentScope,
138             out Transaction contextTransaction)
139         {
140             current = null;
141             currentScope = null;
142             contextTransaction = null;
143 
144             ContextData contextData = ContextData.LookupContextData(defaultLookup);
145             if (contextData != null)
146             {
147                 currentScope = contextData.CurrentScope;
148                 current = FastGetTransaction(currentScope, contextData, out contextTransaction);
149             }
150         }
151 
152         public static Transaction Current
153         {
154             get
155             {
156                 TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
157                 if (etwLog.IsEnabled())
158                 {
159                     etwLog.MethodEnter(TraceSourceType.TraceSourceBase, "Transaction.get_Current");
160                 }
161 
162                 Transaction current = null;
163                 TransactionScope currentScope = null;
164                 Transaction contextValue = null;
165                 GetCurrentTransactionAndScope(TxLookup.Default, out current, out currentScope, out contextValue);
166 
167                 if (currentScope != null)
168                 {
169                     if (currentScope.ScopeComplete)
170                     {
171                         throw new InvalidOperationException(SR.TransactionScopeComplete);
172                     }
173                 }
174 
175                 if (etwLog.IsEnabled())
176                 {
177                     etwLog.MethodExit(TraceSourceType.TraceSourceBase, "Transaction.get_Current");
178                 }
179 
180                 return current;
181             }
182 
183             set
184             {
185                 TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
186                 if (etwLog.IsEnabled())
187                 {
188                     etwLog.MethodEnter(TraceSourceType.TraceSourceBase, "Transaction.set_Current");
189                 }
190 
191                 // Bring your own Transaction(BYOT) is supported only for legacy scenarios.
192                 // This transaction won't be flown across thread continuations.
193                 if (InteropMode(ContextData.TLSCurrentData.CurrentScope) != EnterpriseServicesInteropOption.None)
194                 {
195                     if (etwLog.IsEnabled())
196                     {
197                         etwLog.InvalidOperation("Transaction", "Transaction.set_Current");
198                     }
199 
200                     throw new InvalidOperationException(SR.CannotSetCurrent);
201                 }
202 
203                 // Support only legacy scenarios using TLS.
204                 ContextData.TLSCurrentData.CurrentTransaction = value;
205                 // Clear CallContext data.
206                 CallContextCurrentData.ClearCurrentData(null, false);
207 
208                 if (etwLog.IsEnabled())
209                 {
210                     etwLog.MethodExit(TraceSourceType.TraceSourceBase, "Transaction.set_Current");
211                 }
212             }
213         }
214 
215         // Storage for the transaction isolation level
216         internal IsolationLevel _isoLevel;
217 
218         // Storage for the consistent flag
219         internal bool _complete = false;
220 
221         // Record an identifier for this clone
222         internal int _cloneId;
223 
224         // Storage for a disposed flag
225         internal const int _disposedTrueValue = 1;
226         internal int _disposed = 0;
227         internal bool Disposed { get { return _disposed == Transaction._disposedTrueValue; } }
228 
229         internal Guid DistributedTxId
230         {
231             get
232             {
233                 Guid returnValue = Guid.Empty;
234 
235                 if (_internalTransaction != null)
236                 {
237                     returnValue = _internalTransaction.DistributedTxId;
238                 }
239                 return returnValue;
240             }
241         }
242 
243         // Internal synchronization object for transactions.  It is not safe to lock on the
244         // transaction object because it is public and users of the object may lock it for
245         // other purposes.
246         internal InternalTransaction _internalTransaction;
247 
248         // The TransactionTraceIdentifier for the transaction instance.
249         internal TransactionTraceIdentifier _traceIdentifier;
250 
251         // Not used by anyone
Transaction()252         private Transaction() { }
253 
254         // Create a transaction with the given settings
255         //
Transaction(IsolationLevel isoLevel, InternalTransaction internalTransaction)256         internal Transaction(IsolationLevel isoLevel, InternalTransaction internalTransaction)
257         {
258             TransactionManager.ValidateIsolationLevel(isoLevel);
259 
260             _isoLevel = isoLevel;
261 
262             // Never create a transaction with an IsolationLevel of Unspecified.
263             if (IsolationLevel.Unspecified == _isoLevel)
264             {
265                 _isoLevel = TransactionManager.DefaultIsolationLevel;
266             }
267 
268             if (internalTransaction != null)
269             {
270                 _internalTransaction = internalTransaction;
271                 _cloneId = Interlocked.Increment(ref _internalTransaction._cloneCount);
272             }
273             else
274             {
275                 // Null is passed from the constructor of a CommittableTransaction.  That
276                 // constructor will fill in the traceIdentifier because it has allocated the
277                 // internal transaction.
278             }
279         }
280 
Transaction(DistributedTransaction distributedTransaction)281         internal Transaction(DistributedTransaction distributedTransaction)
282         {
283             _isoLevel = distributedTransaction.IsolationLevel;
284             _internalTransaction = new InternalTransaction(this, distributedTransaction);
285             _cloneId = Interlocked.Increment(ref _internalTransaction._cloneCount);
286         }
287 
Transaction(IsolationLevel isoLevel, ISimpleTransactionSuperior superior)288         internal Transaction(IsolationLevel isoLevel, ISimpleTransactionSuperior superior)
289         {
290             TransactionManager.ValidateIsolationLevel(isoLevel);
291 
292             if (superior == null)
293             {
294                 throw new ArgumentNullException(nameof(superior));
295             }
296 
297             _isoLevel = isoLevel;
298 
299             // Never create a transaction with an IsolationLevel of Unspecified.
300             if (IsolationLevel.Unspecified == _isoLevel)
301             {
302                 _isoLevel = TransactionManager.DefaultIsolationLevel;
303             }
304 
305             _internalTransaction = new InternalTransaction(this, superior);
306             // ISimpleTransactionSuperior is defined to also promote to MSDTC.
307             _internalTransaction.SetPromoterTypeToMSDTC();
308             _cloneId = 1;
309         }
310 
311         #region System.Object Overrides
312 
313         // Don't use the identifier for the hash code.
314         //
GetHashCode()315         public override int GetHashCode()
316         {
317             return _internalTransaction.TransactionHash;
318         }
319 
320 
321         // Don't allow equals to get the identifier
322         //
Equals(object obj)323         public override bool Equals(object obj)
324         {
325             Transaction transaction = obj as Transaction;
326 
327             // If we can't cast the object as a Transaction, it must not be equal
328             // to this, which is a Transaction.
329             if (null == transaction)
330             {
331                 return false;
332             }
333 
334             // Check the internal transaction object for equality.
335             return _internalTransaction.TransactionHash == transaction._internalTransaction.TransactionHash;
336         }
337 
operator ==(Transaction x, Transaction y)338         public static bool operator ==(Transaction x, Transaction y)
339         {
340             if (((object)x) != null)
341             {
342                 return x.Equals(y);
343             }
344             return ((object)y) == null;
345         }
346 
operator !=(Transaction x, Transaction y)347         public static bool operator !=(Transaction x, Transaction y)
348         {
349             if (((object)x) != null)
350             {
351                 return !x.Equals(y);
352             }
353             return ((object)y) != null;
354         }
355 
356 
357         #endregion
358 
359         public TransactionInformation TransactionInformation
360         {
361             get
362             {
363                 TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
364                 if (etwLog.IsEnabled())
365                 {
366                     etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
367                 }
368 
369                 if (Disposed)
370                 {
371                     throw new ObjectDisposedException(nameof(Transaction));
372                 }
373 
374                 TransactionInformation txInfo = _internalTransaction._transactionInformation;
375                 if (txInfo == null)
376                 {
377                     // A race would only result in an extra allocation
378                     txInfo = new TransactionInformation(_internalTransaction);
379                     _internalTransaction._transactionInformation = txInfo;
380                 }
381 
382                 if (etwLog.IsEnabled())
383                 {
384                     etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
385                 }
386 
387                 return txInfo;
388             }
389         }
390 
391 
392         // Return the Isolation Level for the given transaction
393         //
394         public IsolationLevel IsolationLevel
395         {
396             get
397             {
398                 TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
399                 if (etwLog.IsEnabled())
400                 {
401                     etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
402                 }
403 
404                 if (Disposed)
405                 {
406                     throw new ObjectDisposedException(nameof(Transaction));
407                 }
408 
409                 if (etwLog.IsEnabled())
410                 {
411                     etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
412                 }
413                 return _isoLevel;
414             }
415         }
416 
417         /// <summary>
418         /// Gets the PromoterType value for the transaction.
419         /// </summary>
420         /// <value>
421         /// If the transaction has not yet been promoted and does not yet have a promotable single phase enlistment,
422         /// this property value will be Guid.Empty.
423         ///
424         /// If the transaction has been promoted or has a promotable single phase enlistment, this will return the
425         /// promoter type specified by the transaction promoter.
426         ///
427         /// If the transaction is, or will be, promoted to MSDTC, the value will be TransactionInterop.PromoterTypeDtc.
428         /// </value>
429         public Guid PromoterType
430         {
431             get
432             {
433                 TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
434                 if (etwLog.IsEnabled())
435                 {
436                     etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
437                 }
438 
439                 if (Disposed)
440                 {
441                     throw new ObjectDisposedException(nameof(Transaction));
442                 }
443 
444                 lock (_internalTransaction)
445                 {
446                     return _internalTransaction._promoterType;
447                 }
448             }
449         }
450 
451         /// <summary>
452         /// Gets the PromotedToken for the transaction.
453         ///
454         /// If the transaction has not already been promoted, retrieving this value will cause promotion. Before retrieving the
455         /// PromotedToken, the Transaction.PromoterType value should be checked to see if it is a promoter type (Guid) that the
456         /// caller understands. If the caller does not recognize the PromoterType value, retrieving the PromotedToken doesn't
457         /// have much value because the caller doesn't know how to utilize it. But if the PromoterType is recognized, the
458         /// caller should know how to utilize the PromotedToken to communicate with the promoting distributed transaction
459         /// coordinator to enlist on the distributed transaction.
460         ///
461         /// If the value of a transaction's PromoterType is TransactionInterop.PromoterTypeDtc, then that transaction's
462         /// PromotedToken will be an MSDTC-based TransmitterPropagationToken.
463         /// </summary>
464         /// <returns>
465         /// The byte[] that can be used to enlist with the distributed transaction coordinator used to promote the transaction.
466         /// The format of the byte[] depends upon the value of Transaction.PromoterType.
467         /// </returns>
GetPromotedToken()468         public byte[] GetPromotedToken()
469         {
470             // We need to ask the current transaction state for the PromotedToken because depending on the state
471             // we may need to induce a promotion.
472             TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
473             if (etwLog.IsEnabled())
474             {
475                 etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
476             }
477 
478             if (Disposed)
479             {
480                 throw new ObjectDisposedException(nameof(Transaction));
481             }
482 
483             // We always make a copy of the promotedToken stored in the internal transaction.
484             byte[] internalPromotedToken;
485             lock (_internalTransaction)
486             {
487                 internalPromotedToken = _internalTransaction.State.PromotedToken(_internalTransaction);
488             }
489 
490             byte[] toReturn = new byte[internalPromotedToken.Length];
491             Array.Copy(internalPromotedToken, toReturn, toReturn.Length);
492             return toReturn;
493         }
494 
EnlistDurable( Guid resourceManagerIdentifier, IEnlistmentNotification enlistmentNotification, EnlistmentOptions enlistmentOptions)495         public Enlistment EnlistDurable(
496             Guid resourceManagerIdentifier,
497             IEnlistmentNotification enlistmentNotification,
498             EnlistmentOptions enlistmentOptions)
499         {
500             TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
501             if (etwLog.IsEnabled())
502             {
503                 etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
504             }
505 
506             if (Disposed)
507             {
508                 throw new ObjectDisposedException(nameof(Transaction));
509             }
510 
511             if (resourceManagerIdentifier == Guid.Empty)
512             {
513                 throw new ArgumentException(SR.BadResourceManagerId, nameof(resourceManagerIdentifier));
514             }
515 
516             if (enlistmentNotification == null)
517             {
518                 throw new ArgumentNullException(nameof(enlistmentNotification));
519             }
520 
521             if (enlistmentOptions != EnlistmentOptions.None && enlistmentOptions != EnlistmentOptions.EnlistDuringPrepareRequired)
522             {
523                 throw new ArgumentOutOfRangeException(nameof(enlistmentOptions));
524             }
525 
526             if (_complete)
527             {
528                 throw TransactionException.CreateTransactionCompletedException(DistributedTxId);
529             }
530 
531             lock (_internalTransaction)
532             {
533                 Enlistment enlistment = _internalTransaction.State.EnlistDurable(_internalTransaction,
534                     resourceManagerIdentifier, enlistmentNotification, enlistmentOptions, this);
535 
536                 if (etwLog.IsEnabled())
537                 {
538                     etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
539                 }
540 
541                 return enlistment;
542             }
543         }
544 
545 
546         // Forward request to the state machine to take the appropriate action.
547         //
EnlistDurable( Guid resourceManagerIdentifier, ISinglePhaseNotification singlePhaseNotification, EnlistmentOptions enlistmentOptions)548         public Enlistment EnlistDurable(
549             Guid resourceManagerIdentifier,
550             ISinglePhaseNotification singlePhaseNotification,
551             EnlistmentOptions enlistmentOptions)
552         {
553             TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
554             if (etwLog.IsEnabled())
555             {
556                 etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
557             }
558 
559             if (Disposed)
560             {
561                 throw new ObjectDisposedException(nameof(Transaction));
562             }
563 
564             if (resourceManagerIdentifier == Guid.Empty)
565             {
566                 throw new ArgumentException(SR.BadResourceManagerId, nameof(resourceManagerIdentifier));
567             }
568 
569             if (singlePhaseNotification == null)
570             {
571                 throw new ArgumentNullException(nameof(singlePhaseNotification));
572             }
573 
574             if (enlistmentOptions != EnlistmentOptions.None && enlistmentOptions != EnlistmentOptions.EnlistDuringPrepareRequired)
575             {
576                 throw new ArgumentOutOfRangeException(nameof(enlistmentOptions));
577             }
578 
579             if (_complete)
580             {
581                 throw TransactionException.CreateTransactionCompletedException(DistributedTxId);
582             }
583 
584             lock (_internalTransaction)
585             {
586                 Enlistment enlistment = _internalTransaction.State.EnlistDurable(_internalTransaction,
587                     resourceManagerIdentifier, singlePhaseNotification, enlistmentOptions, this);
588 
589                 if (etwLog.IsEnabled())
590                 {
591                     etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
592                 }
593                 return enlistment;
594             }
595         }
596 
597 
Rollback()598         public void Rollback()
599         {
600             TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
601             if (etwLog.IsEnabled())
602             {
603                 etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
604                 etwLog.TransactionRollback(this, "Transaction");
605             }
606 
607             if (Disposed)
608             {
609                 throw new ObjectDisposedException(nameof(Transaction));
610             }
611 
612             lock (_internalTransaction)
613             {
614                 Debug.Assert(_internalTransaction.State != null);
615                 _internalTransaction.State.Rollback(_internalTransaction, null);
616             }
617 
618             if (etwLog.IsEnabled())
619             {
620                 etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
621             }
622         }
623 
624 
Rollback(Exception e)625         public void Rollback(Exception e)
626         {
627             TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
628             if (etwLog.IsEnabled())
629             {
630                 etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
631                 etwLog.TransactionRollback(this, "Transaction");
632             }
633 
634             if (Disposed)
635             {
636                 throw new ObjectDisposedException(nameof(Transaction));
637             }
638 
639             lock (_internalTransaction)
640             {
641                 Debug.Assert(_internalTransaction.State != null);
642                 _internalTransaction.State.Rollback(_internalTransaction, e);
643             }
644 
645             if (etwLog.IsEnabled())
646             {
647                 etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
648             }
649         }
650 
651 
652         // Forward request to the state machine to take the appropriate action.
653         //
EnlistVolatile(IEnlistmentNotification enlistmentNotification, EnlistmentOptions enlistmentOptions)654         public Enlistment EnlistVolatile(IEnlistmentNotification enlistmentNotification, EnlistmentOptions enlistmentOptions)
655         {
656             TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
657             if (etwLog.IsEnabled())
658             {
659                 etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
660             }
661 
662             if (Disposed)
663             {
664                 throw new ObjectDisposedException(nameof(Transaction));
665             }
666 
667             if (enlistmentNotification == null)
668             {
669                 throw new ArgumentNullException(nameof(enlistmentNotification));
670             }
671 
672             if (enlistmentOptions != EnlistmentOptions.None && enlistmentOptions != EnlistmentOptions.EnlistDuringPrepareRequired)
673             {
674                 throw new ArgumentOutOfRangeException(nameof(enlistmentOptions));
675             }
676 
677             if (_complete)
678             {
679                 throw TransactionException.CreateTransactionCompletedException(DistributedTxId);
680             }
681 
682             lock (_internalTransaction)
683             {
684                 Enlistment enlistment = _internalTransaction.State.EnlistVolatile(_internalTransaction,
685                     enlistmentNotification, enlistmentOptions, this);
686 
687                 if (etwLog.IsEnabled())
688                 {
689                     etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
690                 }
691                 return enlistment;
692             }
693         }
694 
695 
696         // Forward request to the state machine to take the appropriate action.
697         //
EnlistVolatile(ISinglePhaseNotification singlePhaseNotification, EnlistmentOptions enlistmentOptions)698         public Enlistment EnlistVolatile(ISinglePhaseNotification singlePhaseNotification, EnlistmentOptions enlistmentOptions)
699         {
700             TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
701             if (etwLog.IsEnabled())
702             {
703                 etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
704             }
705 
706             if (Disposed)
707             {
708                 throw new ObjectDisposedException(nameof(Transaction));
709             }
710 
711             if (singlePhaseNotification == null)
712             {
713                 throw new ArgumentNullException(nameof(singlePhaseNotification));
714             }
715 
716             if (enlistmentOptions != EnlistmentOptions.None && enlistmentOptions != EnlistmentOptions.EnlistDuringPrepareRequired)
717             {
718                 throw new ArgumentOutOfRangeException(nameof(enlistmentOptions));
719             }
720 
721             if (_complete)
722             {
723                 throw TransactionException.CreateTransactionCompletedException(DistributedTxId);
724             }
725 
726             lock (_internalTransaction)
727             {
728                 Enlistment enlistment = _internalTransaction.State.EnlistVolatile(_internalTransaction,
729                     singlePhaseNotification, enlistmentOptions, this);
730 
731                 if (etwLog.IsEnabled())
732                 {
733                     etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
734                 }
735                 return enlistment;
736             }
737         }
738 
739         // Create a clone of the transaction that forwards requests to this object.
740         //
Clone()741         public Transaction Clone()
742         {
743             TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
744             if (etwLog.IsEnabled())
745             {
746                 etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
747             }
748 
749             if (Disposed)
750             {
751                 throw new ObjectDisposedException(nameof(Transaction));
752             }
753 
754             if (_complete)
755             {
756                 throw TransactionException.CreateTransactionCompletedException(DistributedTxId);
757             }
758 
759             Transaction clone = InternalClone();
760 
761             if (etwLog.IsEnabled())
762             {
763                 etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
764             }
765             return clone;
766         }
767 
InternalClone()768         internal Transaction InternalClone()
769         {
770             Transaction clone = new Transaction(_isoLevel, _internalTransaction);
771 
772             TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
773             if (etwLog.IsEnabled())
774             {
775                 etwLog.TransactionCloneCreate(clone, "Transaction");
776             }
777 
778             return clone;
779         }
780 
781 
782         // Create a dependent clone of the transaction that forwards requests to this object.
783         //
DependentClone( DependentCloneOption cloneOption )784         public DependentTransaction DependentClone(
785             DependentCloneOption cloneOption
786             )
787         {
788             TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
789             if (etwLog.IsEnabled())
790             {
791                 etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
792             }
793 
794             if (cloneOption != DependentCloneOption.BlockCommitUntilComplete
795                 && cloneOption != DependentCloneOption.RollbackIfNotComplete)
796             {
797                 throw new ArgumentOutOfRangeException(nameof(cloneOption));
798             }
799 
800             if (Disposed)
801             {
802                 throw new ObjectDisposedException(nameof(Transaction));
803             }
804 
805             if (_complete)
806             {
807                 throw TransactionException.CreateTransactionCompletedException(DistributedTxId);
808             }
809 
810             DependentTransaction clone = new DependentTransaction(
811                 _isoLevel, _internalTransaction, cloneOption == DependentCloneOption.BlockCommitUntilComplete);
812 
813             if (etwLog.IsEnabled())
814             {
815                 etwLog.TransactionCloneCreate(clone, "DependentTransaction");
816                 etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
817             }
818             return clone;
819         }
820 
821 
822         internal TransactionTraceIdentifier TransactionTraceId
823         {
824             get
825             {
826                 if (_traceIdentifier == TransactionTraceIdentifier.Empty)
827                 {
828                     lock (_internalTransaction)
829                     {
830                         if (_traceIdentifier == TransactionTraceIdentifier.Empty)
831                         {
832                             TransactionTraceIdentifier temp = new TransactionTraceIdentifier(
833                                 _internalTransaction.TransactionTraceId.TransactionIdentifier,
834                                 _cloneId);
835                             Interlocked.MemoryBarrier();
836                             _traceIdentifier = temp;
837                         }
838                     }
839                 }
840                 return _traceIdentifier;
841             }
842         }
843 
844         // Forward request to the state machine to take the appropriate action.
845         //
846         public event TransactionCompletedEventHandler TransactionCompleted
847         {
848             add
849             {
850                 if (Disposed)
851                 {
852                     throw new ObjectDisposedException(nameof(Transaction));
853                 }
854 
855                 lock (_internalTransaction)
856                 {
857                     // Register for completion with the inner transaction
858                     _internalTransaction.State.AddOutcomeRegistrant(_internalTransaction, value);
859                 }
860             }
861 
862             remove
863             {
864                 lock (_internalTransaction)
865                 {
866                     _internalTransaction._transactionCompletedDelegate = (TransactionCompletedEventHandler)
867                         System.Delegate.Remove(_internalTransaction._transactionCompletedDelegate, value);
868                 }
869             }
870         }
871 
Dispose()872         public void Dispose()
873         {
874             InternalDispose();
875         }
876 
877         // Handle Transaction Disposal.
878         //
InternalDispose()879         internal virtual void InternalDispose()
880         {
881             TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
882             if (etwLog.IsEnabled())
883             {
884                 etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
885             }
886 
887             if (Interlocked.Exchange(ref _disposed, Transaction._disposedTrueValue) == Transaction._disposedTrueValue)
888             {
889                 return;
890             }
891 
892             // Attempt to clean up the internal transaction
893             long remainingITx = Interlocked.Decrement(ref _internalTransaction._cloneCount);
894             if (remainingITx == 0)
895             {
896                 _internalTransaction.Dispose();
897             }
898 
899             if (etwLog.IsEnabled())
900             {
901                 etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
902             }
903         }
904 
905         // Ask the state machine for serialization info.
906         //
ISerializable.GetObjectData( SerializationInfo serializationInfo, StreamingContext context)907         void ISerializable.GetObjectData(
908             SerializationInfo serializationInfo,
909             StreamingContext context)
910         {
911             //TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
912             //if (etwLog.IsEnabled())
913             //{
914             //    etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
915             //}
916 
917             //if (Disposed)
918             //{
919             //    throw new ObjectDisposedException(nameof(Transaction));
920             //}
921 
922             //if (serializationInfo == null)
923             //{
924             //    throw new ArgumentNullException(nameof(serializationInfo));
925             //}
926 
927             //if (_complete)
928             //{
929             //    throw TransactionException.CreateTransactionCompletedException(DistributedTxId);
930             //}
931 
932             //lock (_internalTransaction)
933             //{
934             //    _internalTransaction.State.GetObjectData(_internalTransaction, serializationInfo, context);
935             //}
936 
937             //if (etwLog.IsEnabled())
938             //{
939             //    etwLog.TransactionSerialized(this, "Transaction");
940             //    etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
941             //}
942 
943             throw new PlatformNotSupportedException();
944         }
945 
946         /// <summary>
947         /// Create a promotable single phase enlistment that promotes to MSDTC.
948         /// </summary>
949         /// <param name="promotableSinglePhaseNotification">The object that implements the IPromotableSinglePhaseNotification interface.</param>
950         /// <returns>
951         /// True if the enlistment is successful.
952         ///
953         /// False if the transaction already has a durable enlistment or promotable single phase enlistment or
954         /// if the transaction has already promoted. In this case, the caller will need to enlist in the transaction through other
955         /// means, such as Transaction.EnlistDurable or retrieve the MSDTC export cookie or propagation token to enlist with MSDTC.
956         /// </returns>
957         // We apparently didn't spell Promotable like FXCop thinks it should be spelled.
EnlistPromotableSinglePhase(IPromotableSinglePhaseNotification promotableSinglePhaseNotification)958         public bool EnlistPromotableSinglePhase(IPromotableSinglePhaseNotification promotableSinglePhaseNotification)
959         {
960             return EnlistPromotableSinglePhase(promotableSinglePhaseNotification, TransactionInterop.PromoterTypeDtc);
961         }
962 
963         /// <summary>
964         /// Create a promotable single phase enlistment that promotes to a distributed transaction manager other than MSDTC.
965         /// </summary>
966         /// <param name="promotableSinglePhaseNotification">The object that implements the IPromotableSinglePhaseNotification interface.</param>
967         /// <param name="promoterType">
968         /// The promoter type Guid that identifies the format of the byte[] that is returned by the ITransactionPromoter.Promote
969         /// call that is implemented by the IPromotableSinglePhaseNotificationObject, and thus the promoter of the transaction.
970         /// </param>
971         /// <returns>
972         /// True if the enlistment is successful.
973         ///
974         /// False if the transaction already has a durable enlistment or promotable single phase enlistment or
975         /// if the transaction has already promoted. In this case, the caller will need to enlist in the transaction through other
976         /// means.
977         ///
978         /// If the Transaction.PromoterType matches the promoter type supported by the caller, then the
979         /// Transaction.PromotedToken can be retrieved and used to enlist directly with the identified distributed transaction manager.
980         ///
981         /// How the enlistment is created with the distributed transaction manager identified by the Transaction.PromoterType
982         /// is defined by that distributed transaction manager.
983         /// </returns>
EnlistPromotableSinglePhase(IPromotableSinglePhaseNotification promotableSinglePhaseNotification, Guid promoterType)984         public bool EnlistPromotableSinglePhase(IPromotableSinglePhaseNotification promotableSinglePhaseNotification, Guid promoterType)
985         {
986             TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
987             if (etwLog.IsEnabled())
988             {
989                 etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
990             }
991 
992             if (Disposed)
993             {
994                 throw new ObjectDisposedException(nameof(Transaction));
995             }
996 
997             if (promotableSinglePhaseNotification == null)
998             {
999                 throw new ArgumentNullException(nameof(promotableSinglePhaseNotification));
1000             }
1001 
1002             if (promoterType == Guid.Empty)
1003             {
1004                 throw new ArgumentException(SR.PromoterTypeInvalid, nameof(promoterType));
1005             }
1006 
1007             if (_complete)
1008             {
1009                 throw TransactionException.CreateTransactionCompletedException(DistributedTxId);
1010             }
1011 
1012             bool succeeded = false;
1013 
1014             lock (_internalTransaction)
1015             {
1016                 succeeded = _internalTransaction.State.EnlistPromotableSinglePhase(_internalTransaction, promotableSinglePhaseNotification, this, promoterType);
1017             }
1018 
1019             if (etwLog.IsEnabled())
1020             {
1021                 etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
1022             }
1023 
1024             return succeeded;
1025         }
1026 
PromoteAndEnlistDurable(Guid resourceManagerIdentifier, IPromotableSinglePhaseNotification promotableNotification, ISinglePhaseNotification enlistmentNotification, EnlistmentOptions enlistmentOptions)1027         public Enlistment PromoteAndEnlistDurable(Guid resourceManagerIdentifier,
1028                                                   IPromotableSinglePhaseNotification promotableNotification,
1029                                                   ISinglePhaseNotification enlistmentNotification,
1030                                                   EnlistmentOptions enlistmentOptions)
1031         {
1032             TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
1033             if (etwLog.IsEnabled())
1034             {
1035                 etwLog.MethodEnter(TraceSourceType.TraceSourceDistributed, this);
1036             }
1037 
1038             if (Disposed)
1039             {
1040                 throw new ObjectDisposedException(nameof(Transaction));
1041             }
1042 
1043             if (resourceManagerIdentifier == Guid.Empty)
1044             {
1045                 throw new ArgumentException(SR.BadResourceManagerId, nameof(resourceManagerIdentifier));
1046             }
1047 
1048             if (promotableNotification == null)
1049             {
1050                 throw new ArgumentNullException(nameof(promotableNotification));
1051             }
1052 
1053             if (enlistmentNotification == null)
1054             {
1055                 throw new ArgumentNullException(nameof(enlistmentNotification));
1056             }
1057 
1058             if (enlistmentOptions != EnlistmentOptions.None && enlistmentOptions != EnlistmentOptions.EnlistDuringPrepareRequired)
1059             {
1060                 throw new ArgumentOutOfRangeException(nameof(enlistmentOptions));
1061             }
1062 
1063             if (_complete)
1064             {
1065                 throw TransactionException.CreateTransactionCompletedException(DistributedTxId);
1066             }
1067 
1068             lock (_internalTransaction)
1069             {
1070                 Enlistment enlistment = _internalTransaction.State.PromoteAndEnlistDurable(_internalTransaction,
1071                     resourceManagerIdentifier, promotableNotification, enlistmentNotification, enlistmentOptions, this);
1072 
1073                 if (etwLog.IsEnabled())
1074                 {
1075                     etwLog.MethodExit(TraceSourceType.TraceSourceDistributed, this);
1076                 }
1077 
1078                 return enlistment;
1079             }
1080         }
1081 
SetDistributedTransactionIdentifier(IPromotableSinglePhaseNotification promotableNotification, Guid distributedTransactionIdentifier)1082         public void SetDistributedTransactionIdentifier(IPromotableSinglePhaseNotification promotableNotification,
1083                                                         Guid distributedTransactionIdentifier)
1084         {
1085             TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
1086             if (etwLog.IsEnabled())
1087             {
1088                 etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
1089             }
1090 
1091             if (Disposed)
1092             {
1093                 throw new ObjectDisposedException(nameof(Transaction));
1094             }
1095 
1096             if (promotableNotification == null)
1097             {
1098                 throw new ArgumentNullException(nameof(promotableNotification));
1099             }
1100 
1101             if (distributedTransactionIdentifier == Guid.Empty)
1102             {
1103                 throw new ArgumentException(null, nameof(distributedTransactionIdentifier));
1104             }
1105 
1106             if (_complete)
1107             {
1108                 throw TransactionException.CreateTransactionCompletedException(DistributedTxId);
1109             }
1110 
1111             lock (_internalTransaction)
1112             {
1113                 _internalTransaction.State.SetDistributedTransactionId(_internalTransaction,
1114                     promotableNotification,
1115                     distributedTransactionIdentifier);
1116 
1117                 if (etwLog.IsEnabled())
1118                 {
1119                     etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
1120                 }
1121                 return;
1122             }
1123         }
1124 
Promote()1125         internal DistributedTransaction Promote()
1126         {
1127             lock (_internalTransaction)
1128             {
1129                 // This method is only called when we expect to be promoting to MSDTC.
1130                 _internalTransaction.ThrowIfPromoterTypeIsNotMSDTC();
1131                 _internalTransaction.State.Promote(_internalTransaction);
1132                 return _internalTransaction.PromotedTransaction;
1133             }
1134         }
1135     }
1136 
1137     //
1138     // The following code & data is related to management of Transaction.Current
1139     //
1140 
1141     internal enum DefaultComContextState
1142     {
1143         Unknown = 0,
1144         Unavailable = -1,
1145         Available = 1
1146     }
1147 
1148     //
1149     //  The TxLookup enum is used internally to detect where the ambient context needs to be stored or looked up.
1150     //  Default                  - Used internally when looking up Transaction.Current.
1151     //  DefaultCallContext - Used when TransactionScope with async flow option is enabled. Internally we will use CallContext to store the ambient transaction.
1152     //  Default TLS            - Used for legacy/syncronous TransactionScope. Internally we will use TLS to store the ambient transaction.
1153     //
1154     internal enum TxLookup
1155     {
1156         Default,
1157         DefaultCallContext,
1158         DefaultTLS,
1159     }
1160 
1161     //
1162     //  CallContextCurrentData holds the ambient transaction and uses CallContext and ConditionalWeakTable to track the ambient transaction.
1163     //  For async flow scenarios, we should not allow flowing of transaction across app domains. To prevent transaction from flowing across
1164     //  AppDomain/Remoting boundaries, we are using ConditionalWeakTable to hold the actual ambient transaction and store only a object reference
1165     //  in CallContext. When TransactionScope is used to invoke a call across AppDomain/Remoting boundaries, only the object reference will be sent
1166     //  across and not the actaul ambient transaction which is stashed away in the ConditionalWeakTable.
1167     //
1168     internal static class CallContextCurrentData
1169     {
1170         private static AsyncLocal<ContextKey> s_currentTransaction = new AsyncLocal<ContextKey>();
1171 
1172         // ConditionalWeakTable is used to automatically remove the entries that are no longer referenced. This will help prevent leaks in async nested TransactionScope
1173         // usage and when child nested scopes are not syncronized properly.
1174         private static readonly ConditionalWeakTable<ContextKey, ContextData> s_contextDataTable = new ConditionalWeakTable<ContextKey, ContextData>();
1175 
1176         //
1177         //  Set CallContext data with the given contextKey.
1178         //  return the ContextData if already present in contextDataTable, otherwise return the default value.
1179         //
CreateOrGetCurrentData(ContextKey contextKey)1180         public static ContextData CreateOrGetCurrentData(ContextKey contextKey)
1181         {
1182             s_currentTransaction.Value = contextKey;
1183             return s_contextDataTable.GetValue(contextKey, (env) => new ContextData(true));
1184         }
1185 
ClearCurrentData(ContextKey contextKey, bool removeContextData)1186         public static void ClearCurrentData(ContextKey contextKey, bool removeContextData)
1187         {
1188             // Get the current ambient CallContext.
1189             ContextKey key = s_currentTransaction.Value;
1190             if (contextKey != null || key != null)
1191             {
1192                 // removeContextData flag is used for perf optimization to avoid removing from the table in certain nested TransactionScope usage.
1193                 if (removeContextData)
1194                 {
1195                     // if context key is passed in remove that from the contextDataTable, otherwise remove the default context key.
1196                     s_contextDataTable.Remove(contextKey ?? key);
1197                 }
1198 
1199                 if (key != null)
1200                 {
1201                     s_currentTransaction.Value = null;
1202                 }
1203             }
1204         }
1205 
TryGetCurrentData(out ContextData currentData)1206         public static bool TryGetCurrentData(out ContextData currentData)
1207         {
1208             currentData = null;
1209             ContextKey contextKey = s_currentTransaction.Value;
1210             if (contextKey == null)
1211             {
1212                 return false;
1213             }
1214             else
1215             {
1216                 return s_contextDataTable.TryGetValue(contextKey, out currentData);
1217             }
1218         }
1219     }
1220 
1221     //
1222     // MarshalByRefObject is needed for cross AppDomain scenarios where just using object will end up with a different reference when call is made across serialization boundary.
1223     //
1224     internal class ContextKey // : MarshalByRefObject
1225     {
1226     }
1227 
1228     internal class ContextData
1229     {
1230         internal TransactionScope CurrentScope;
1231         internal Transaction CurrentTransaction;
1232 
1233         internal DefaultComContextState DefaultComContextState;
1234         internal WeakReference WeakDefaultComContext;
1235 
1236         internal bool _asyncFlow;
1237 
1238         [ThreadStatic]
1239         private static ContextData t_staticData;
1240 
ContextData(bool asyncFlow)1241         internal ContextData(bool asyncFlow)
1242         {
1243             _asyncFlow = asyncFlow;
1244         }
1245 
1246         internal static ContextData TLSCurrentData
1247         {
1248             get
1249             {
1250                 ContextData data = t_staticData;
1251                 if (data == null)
1252                 {
1253                     data = new ContextData(false);
1254                     t_staticData = data;
1255                 }
1256 
1257                 return data;
1258             }
1259             set
1260             {
1261                 if (value == null && t_staticData != null)
1262                 {
1263                     // set each property to null to retain one TLS ContextData copy.
1264                     t_staticData.CurrentScope = null;
1265                     t_staticData.CurrentTransaction = null;
1266                     t_staticData.DefaultComContextState = DefaultComContextState.Unknown;
1267                     t_staticData.WeakDefaultComContext = null;
1268                 }
1269                 else
1270                 {
1271                     t_staticData = value;
1272                 }
1273             }
1274         }
1275 
LookupContextData(TxLookup defaultLookup)1276         internal static ContextData LookupContextData(TxLookup defaultLookup)
1277         {
1278             ContextData currentData = null;
1279             if (CallContextCurrentData.TryGetCurrentData(out currentData))
1280             {
1281                 if (currentData.CurrentScope == null && currentData.CurrentTransaction == null && defaultLookup != TxLookup.DefaultCallContext)
1282                 {
1283                     // Clear Call Context Data
1284                     CallContextCurrentData.ClearCurrentData(null, true);
1285                     return TLSCurrentData;
1286                 }
1287 
1288                 return currentData;
1289             }
1290             else
1291             {
1292                 return TLSCurrentData;
1293             }
1294         }
1295     }
1296 }
1297