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