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