diff options
author | Pablo MartÃnez <crackbashridge@gmail.com> | 2020-07-08 22:05:42 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-08 22:05:42 +0300 |
commit | 9c0121161c1c421753ed99298a1fb4557ea233fe (patch) | |
tree | c6d2bbd0c812c7d82a8e9e8f40469fc1bd388a66 /mcs | |
parent | 5bd9f53dc0045b38668ff20bb4916c02dbefcb73 (diff) |
Init a timer on TransactionScope initialization to be able to timeout (#20080)
Fixes #19955
Diffstat (limited to 'mcs')
3 files changed, 96 insertions, 12 deletions
diff --git a/mcs/class/System.Transactions/System.Transactions/Transaction.cs b/mcs/class/System.Transactions/System.Transactions/Transaction.cs index e1de05591ce..462a0f9bf74 100644 --- a/mcs/class/System.Transactions/System.Transactions/Transaction.cs +++ b/mcs/class/System.Transactions/System.Transactions/Transaction.cs @@ -23,6 +23,8 @@ namespace System.Transactions [ThreadStatic] static Transaction ambient; + Transaction internalTransaction; + IsolationLevel level; TransactionInformation info; @@ -83,6 +85,8 @@ namespace System.Transactions volatiles = other.Volatiles; durables = other.Durables; pspe = other.Pspe; + TransactionCompletedInternal = other.TransactionCompletedInternal; + internalTransaction = other; } [MonoTODO] @@ -92,7 +96,29 @@ namespace System.Transactions throw new NotImplementedException (); } - public event TransactionCompletedEventHandler TransactionCompleted; + internal event TransactionCompletedEventHandler TransactionCompletedInternal; + + // Transaction B was cloned from transaction A. Add event handlers to A + // when they are added to B. This will not work when new handlers are added to A + // as A has no reference to B. + public event TransactionCompletedEventHandler TransactionCompleted + { + add + { + if (this.internalTransaction != null) + this.internalTransaction.TransactionCompleted += value; + + TransactionCompletedInternal += value; + } + + remove + { + if (this.internalTransaction != null) + this.internalTransaction.TransactionCompleted -= value; + + TransactionCompletedInternal -= value; + } + } public static Transaction Current { get { @@ -378,7 +404,7 @@ namespace System.Transactions private void DoCommit () { /* Scope becomes null in TransactionScope.Dispose */ - if (Scope != null) { + if (Scope != null && (!Scope.IsComplete || !Scope.IsDisposed)) { /* See test ExplicitTransaction8 */ Rollback (null, null); CheckAborted (); @@ -529,14 +555,14 @@ namespace System.Transactions void CheckAborted () { - if (aborted) + if (aborted || (Scope != null && Scope.IsAborted)) throw new TransactionAbortedException ("Transaction has aborted", innerException); } void FireCompleted () { - if (TransactionCompleted != null) - TransactionCompleted (this, new TransactionEventArgs(this)); + if (TransactionCompletedInternal != null) + TransactionCompletedInternal (this, new TransactionEventArgs(this)); } static void EnsureIncompleteCurrentScope () @@ -548,4 +574,3 @@ namespace System.Transactions } } } - diff --git a/mcs/class/System.Transactions/System.Transactions/TransactionScope.cs b/mcs/class/System.Transactions/System.Transactions/TransactionScope.cs index 503a4b6b9d9..9cb38a710be 100644 --- a/mcs/class/System.Transactions/System.Transactions/TransactionScope.cs +++ b/mcs/class/System.Transactions/System.Transactions/TransactionScope.cs @@ -9,6 +9,7 @@ // (C)2006 Novell Inc, // +using System.Threading; using DTCOption = System.Transactions.EnterpriseServicesInteropOption; @@ -19,6 +20,7 @@ namespace System.Transactions static TransactionOptions defaultOptions = new TransactionOptions (0, TransactionManager.DefaultTimeout); + Timer scopeTimer; Transaction transaction; Transaction oldTransaction; TransactionScope parentScope; @@ -29,6 +31,7 @@ namespace System.Transactions bool disposed; bool completed; + bool aborted; bool isRoot; bool asyncFlowEnabled; @@ -99,7 +102,7 @@ namespace System.Transactions DTCOption interopOption) { Initialize (scopeOption, null, transactionOptions, interopOption, - TransactionManager.DefaultTimeout, TransactionScopeAsyncFlowOption.Suppress); + transactionOptions.Timeout, TransactionScopeAsyncFlowOption.Suppress); } public TransactionScope (Transaction transactionToUse, @@ -143,8 +146,35 @@ namespace System.Transactions transaction.InitScope (this); if (parentScope != null) parentScope.nested ++; + if (timeout != TimeSpan.Zero) + scopeTimer = new Timer (TransactionScope.TimerCallback, this, scopeTimeout, TimeSpan.Zero); } + private static void TimerCallback (object state) + { + TransactionScope scope = state as TransactionScope; + if (null == scope) + { + throw new TransactionException ("TransactionScopeTimerObjectInvalid", null); + } + + scope.TimeoutScope (); + } + + private void TimeoutScope() + { + if (!completed && transaction != null) + { + try + { + this.transaction.Rollback (); + this.aborted = true; + } + catch (ObjectDisposedException ex) { } + catch (TransactionException txEx) { } + } + } + Transaction InitTransaction (Transaction tx, TransactionScopeOption scopeOption, TransactionOptions options) { if (tx != null) @@ -181,6 +211,14 @@ namespace System.Transactions completed = true; } + internal bool IsAborted { + get { return aborted; } + } + + internal bool IsDisposed { + get { return disposed; } + } + internal bool IsComplete { get { return completed; } } @@ -214,6 +252,9 @@ namespace System.Transactions throw new InvalidOperationException ("Transaction.Current has changed inside of the TransactionScope"); } + if (scopeTimer != null) + scopeTimer.Dispose(); + if (asyncFlowEnabled) { if (oldTransaction != null) oldTransaction.Scope = parentScope; @@ -229,7 +270,10 @@ namespace System.Transactions transaction.Scope = null; - if (!IsComplete) { + if (IsAborted) { + throw new TransactionAbortedException("Transaction has aborted"); + } + else if (!IsComplete) { transaction.Rollback (); variedTransaction.Rollback(); return; @@ -251,9 +295,11 @@ namespace System.Transactions /* scope was not in a transaction, (Suppress) */ return; - transaction.Scope = null; - - if (!IsComplete) + if (IsAborted) { + transaction.Scope = null; + throw new TransactionAbortedException("Transaction has aborted"); + } + else if (!IsComplete) { transaction.Rollback(); return; @@ -265,10 +311,10 @@ namespace System.Transactions transaction.CommitInternal(); + transaction.Scope = null; } } } } - diff --git a/mcs/class/System.Transactions/Test/TransactionScopeTest.cs b/mcs/class/System.Transactions/Test/TransactionScopeTest.cs index a741fcf4775..34e912c94fe 100644 --- a/mcs/class/System.Transactions/Test/TransactionScopeTest.cs +++ b/mcs/class/System.Transactions/Test/TransactionScopeTest.cs @@ -11,6 +11,7 @@ using System; using NUnit.Framework; using System.Transactions; +using System.Threading; namespace MonoTests.System.Transactions { @@ -1114,6 +1115,18 @@ namespace MonoTests.System.Transactions Assert.AreEqual(IsolationLevel.ReadCommitted, Transaction.Current.IsolationLevel); } } + + [Test] + [ExpectedException(typeof(TransactionAbortedException))] + public void TransactionScopeTimeout() + { + Assert.IsNull(Transaction.Current, "Ambient transaction exists (before)"); + using (var ts = new TransactionScope(TransactionScopeOption.Required, TimeSpan.FromMilliseconds(1))) + { + Thread.Sleep(100); + ts.Complete(); + } + } } } |