diff --git a/mcs/class/System.Transactions/Makefile b/mcs/class/System.Transactions/Makefile index 9320a7a29e28..6b640bf40406 100644 --- a/mcs/class/System.Transactions/Makefile +++ b/mcs/class/System.Transactions/Makefile @@ -3,7 +3,7 @@ SUBDIRS = include ../../build/rules.make LIBRARY = System.Transactions.dll -LIB_MCS_FLAGS = /r:$(corlib) /r:System.dll +LIB_MCS_FLAGS = /r:$(corlib) /r:System.dll /r:System.Configuration.dll TEST_MCS_FLAGS = /nowarn:1595 $(LIB_MCS_FLAGS) diff --git a/mcs/class/System.Transactions/System.Transactions.dll.sources b/mcs/class/System.Transactions/System.Transactions.dll.sources index 16c2228177dc..36d981ebe951 100644 --- a/mcs/class/System.Transactions/System.Transactions.dll.sources +++ b/mcs/class/System.Transactions/System.Transactions.dll.sources @@ -33,3 +33,6 @@ System.Transactions/TransactionPromotionException.cs System.Transactions/TransactionScope.cs System.Transactions/TransactionScopeOption.cs System.Transactions/TransactionStatus.cs +System.Transactions/Configuration/DefaultSettingsSection.cs +System.Transactions/Configuration/MachineSettingsSection.cs +System.Transactions/Configuration/TransactionsSectionGroup.cs diff --git a/mcs/class/System.Transactions/System.Transactions/Configuration/DefaultSettingsSection.cs b/mcs/class/System.Transactions/System.Transactions/Configuration/DefaultSettingsSection.cs new file mode 100644 index 000000000000..2992a730c47a --- /dev/null +++ b/mcs/class/System.Transactions/System.Transactions/Configuration/DefaultSettingsSection.cs @@ -0,0 +1,40 @@ +// +// DefaultSettingsSection.cs +// +// Author: +// Pablo Ruiz +// +// (C) 2010 Pablo Ruiz. +// + +#if NET_2_0 + +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Text; + +namespace System.Transactions.Configuration +{ + public class DefaultSettingsSection : ConfigurationSection + { + // http://msdn.microsoft.com/en-us/library/system.transactions.configuration.defaultsettingssection.timeout.aspx + [ConfigurationProperty ("timeout", DefaultValue = "00:01:00")] + [TimeSpanValidator (MinValueString = "00:00:00", MaxValueString = "10675199.02:48:05.4775807")] + public TimeSpan Timeout { + get { return (TimeSpan)base["timeout"]; } + set { + // FIXME: Validate timespan value + base["timeout"] = value; + } + } + + // http://msdn.microsoft.com/en-us/library/system.transactions.configuration.defaultsettingssection.distributedtransactionmanagername(v=VS.90).aspx + [ConfigurationProperty ("distributedTransactionManagerName", DefaultValue = "")] + public string DistributedTransactionManagerName { + get { return base["distributedTransactionManagerName"] as string; } + set { base["distributedTransactionManagerName"] = value; } + } + } +} +#endif \ No newline at end of file diff --git a/mcs/class/System.Transactions/System.Transactions/Configuration/MachineSettingsSection.cs b/mcs/class/System.Transactions/System.Transactions/Configuration/MachineSettingsSection.cs new file mode 100644 index 000000000000..5de518665fa4 --- /dev/null +++ b/mcs/class/System.Transactions/System.Transactions/Configuration/MachineSettingsSection.cs @@ -0,0 +1,33 @@ +// +// MachineSettingsSection.cs +// +// Author: +// Pablo Ruiz +// +// (C) 2010 Pablo Ruiz. +// + +#if NET_2_0 + +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Text; + +namespace System.Transactions.Configuration +{ + public class MachineSettingsSection : ConfigurationSection + { + // http://msdn.microsoft.com/en-us/library/system.transactions.configuration.machinesettingssection.maxtimeout.aspx + [ConfigurationProperty("maxTimeout", DefaultValue = "00:10:00")] + [TimeSpanValidator(MinValueString = "00:00:00", MaxValueString = "10675199.02:48:05.4775807")] + public TimeSpan MaxTimeout { + get { return (TimeSpan)base["maxTimeout"]; } + set { + // FIXME: Validate timespan value.. + base["maxTimeout"] = value; + } + } + } +} +#endif \ No newline at end of file diff --git a/mcs/class/System.Transactions/System.Transactions/Configuration/TransactionsSectionGroup.cs b/mcs/class/System.Transactions/System.Transactions/Configuration/TransactionsSectionGroup.cs new file mode 100644 index 000000000000..a43d9a99313f --- /dev/null +++ b/mcs/class/System.Transactions/System.Transactions/Configuration/TransactionsSectionGroup.cs @@ -0,0 +1,43 @@ +// +// TransactionSectionGroup.cs +// +// Author: +// Pablo Ruiz +// +// (C) 2010 Pablo Ruiz. +// + +#if NET_2_0 + +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Text; + +namespace System.Transactions.Configuration +{ + // http://msdn.microsoft.com/en-us/library/system.transactions.configuration.transactionssectiongroup.aspx + public class TransactionsSectionGroup : ConfigurationSectionGroup + { + public static TransactionsSectionGroup GetSectionGroup(System.Configuration.Configuration config) + { + if (config == null) + throw new ArgumentNullException("config"); + + return config.GetSectionGroup("system.transactions") as TransactionsSectionGroup; + } + + [ConfigurationProperty("defaultSettings")] + public DefaultSettingsSection DefaultSettings + { + get { return (DefaultSettingsSection)base.Sections["defaultSettings"]; } + } + + [ConfigurationProperty("machineSettings")] + public MachineSettingsSection MachineSettings + { + get { return (MachineSettingsSection)base.Sections["machineSettings"]; } + } + } +} +#endif \ No newline at end of file diff --git a/mcs/class/System.Transactions/System.Transactions/PreparingEnlistment.cs b/mcs/class/System.Transactions/System.Transactions/PreparingEnlistment.cs index 407a19325a17..031d840430aa 100644 --- a/mcs/class/System.Transactions/System.Transactions/PreparingEnlistment.cs +++ b/mcs/class/System.Transactions/System.Transactions/PreparingEnlistment.cs @@ -20,13 +20,13 @@ public class PreparingEnlistment : Enlistment bool prepared = false; Transaction tx; IEnlistmentNotification enlisted; - //WaitHandle waitHandle; + WaitHandle waitHandle; internal PreparingEnlistment (Transaction tx, IEnlistmentNotification enlisted) { this.tx = tx; this.enlisted = enlisted; - //waitHandle = new ManualResetEvent (false); + waitHandle = new ManualResetEvent (false); } public void ForceRollback () @@ -38,16 +38,16 @@ public void ForceRollback () public void ForceRollback (Exception ex) { tx.Rollback (ex, enlisted); - /* See test RMFail2 - ((ManualResetEvent) waitHandle).Set (); */ + /* See test RMFail2 */ + ((ManualResetEvent) waitHandle).Set (); } [MonoTODO] public void Prepared () { prepared = true; - /* See test RMFail2 - ((ManualResetEvent) waitHandle).Set ();*/ + /* See test RMFail2 */ + ((ManualResetEvent) waitHandle).Set (); } [MonoTODO] @@ -60,9 +60,14 @@ internal bool IsPrepared { get { return prepared; } } - /*internal WaitHandle WaitHandle { + internal WaitHandle WaitHandle { get { return waitHandle; } - }*/ + } + + internal IEnlistmentNotification EnlistmentNotification + { + get { return enlisted; } + } } } diff --git a/mcs/class/System.Transactions/System.Transactions/Transaction.cs b/mcs/class/System.Transactions/System.Transactions/Transaction.cs index dc132fb4a045..b4d8b12b3831 100644 --- a/mcs/class/System.Transactions/System.Transactions/Transaction.cs +++ b/mcs/class/System.Transactions/System.Transactions/Transaction.cs @@ -232,9 +232,12 @@ public void Rollback (Exception ex) } internal void Rollback (Exception ex, IEnlistmentNotification enlisted) - { - if (aborted) - return; + { + if (aborted) + { + FireCompleted (); + return; + } /* See test ExplicitTransaction7 */ if (info.Status == TransactionStatus.Committed) @@ -249,7 +252,9 @@ internal void Rollback (Exception ex, IEnlistmentNotification enlisted) if (durables.Count > 0 && durables [0] != enlisted) durables [0].Rollback (e); - Aborted = true; + Aborted = true; + + FireCompleted (); } bool Aborted { @@ -289,7 +294,17 @@ internal void CommitInternal () this.committing = true; - DoCommit (); + try { + DoCommit (); + } + catch (TransactionException) + { + throw; + } + catch (Exception ex) + { + throw new TransactionAbortedException("Transaction failed", ex); + } } private void DoCommit () @@ -299,28 +314,30 @@ private void DoCommit () /* See test ExplicitTransaction8 */ Rollback (null, null); CheckAborted (); - } - - if (volatiles.Count == 1 && durables.Count == 0) { - /* Special case */ - ISinglePhaseNotification single = volatiles [0] as ISinglePhaseNotification; - if (single != null) { - DoSingleCommit (single); - Complete (); - return; - } - } - - if (volatiles.Count > 0) - DoPreparePhase (); - - if (durables.Count > 0) - DoSingleCommit (durables [0]); - - if (volatiles.Count > 0) - DoCommitPhase (); - - Complete (); + } + + if (volatiles.Count == 1 && durables.Count == 0) + { + /* Special case */ + ISinglePhaseNotification single = volatiles[0] as ISinglePhaseNotification; + if (single != null) + { + DoSingleCommit(single); + Complete(); + return; + } + } + + if (volatiles.Count > 0) + DoPreparePhase(); + + if (durables.Count > 0) + DoSingleCommit(durables[0]); + + if (volatiles.Count > 0) + DoCommitPhase(); + + Complete(); } private void Complete () @@ -329,7 +346,9 @@ private void Complete () committed = true; if (!aborted) - info.Status = TransactionStatus.Committed; + info.Status = TransactionStatus.Committed; + + FireCompleted (); } internal void InitScope (TransactionScope scope) @@ -342,28 +361,40 @@ internal void InitScope (TransactionScope scope) throw new InvalidOperationException ("Commit has already been called on this transaction."); Scope = scope; + } + + static void PrepareCallbackWrapper(object state) + { + PreparingEnlistment enlist = state as PreparingEnlistment; + enlist.EnlistmentNotification.Prepare(enlist); } void DoPreparePhase () - { - PreparingEnlistment pe; - foreach (IEnlistmentNotification enlisted in volatiles) { - pe = new PreparingEnlistment (this, enlisted); - - enlisted.Prepare (pe); - - /* FIXME: Where should this timeout value come from? - current scope? - Wait after all Prepare()'s are sent - pe.WaitHandle.WaitOne (new TimeSpan (0,0,5), true); */ - - if (!pe.IsPrepared) { + { + // Call prepare on all volatile managers. + foreach (IEnlistmentNotification enlist in volatiles) + { + PreparingEnlistment pe = new PreparingEnlistment (this, enlist); + ThreadPool.QueueUserWorkItem (new WaitCallback(PrepareCallbackWrapper), pe); + + /* Wait (with timeout) for manager to prepare */ + TimeSpan timeout = Scope != null ? Scope.Timeout : TransactionManager.DefaultTimeout; + + // FIXME: Should we managers in parallel or on-by-one? + if (!pe.WaitHandle.WaitOne(timeout, true)) + { + this.Aborted = true; + throw new TimeoutException("Transaction timedout"); + } + + if (!pe.IsPrepared) + { /* FIXME: if not prepared & !aborted as yet, then - this is inDoubt ? . For now, setting aborted = true */ - Aborted = true; - break; - } - } + this is inDoubt ? . For now, setting aborted = true */ + Aborted = true; + break; + } + } /* Either InDoubt(tmp) or Prepare failed and Tx has rolledback */ @@ -393,6 +424,12 @@ void CheckAborted () { if (aborted) throw new TransactionAbortedException ("Transaction has aborted", innerException); + } + + void FireCompleted () + { + if (TransactionCompleted != null) + TransactionCompleted (this, new TransactionEventArgs(this)); } static void EnsureIncompleteCurrentScope () diff --git a/mcs/class/System.Transactions/System.Transactions/TransactionEventArgs.cs b/mcs/class/System.Transactions/System.Transactions/TransactionEventArgs.cs index ebcca98870a5..92eea07d1d66 100644 --- a/mcs/class/System.Transactions/System.Transactions/TransactionEventArgs.cs +++ b/mcs/class/System.Transactions/System.Transactions/TransactionEventArgs.cs @@ -12,9 +12,21 @@ namespace System.Transactions { public class TransactionEventArgs : EventArgs - { + { + private Transaction transaction; + + public TransactionEventArgs() + { + } + + internal TransactionEventArgs(Transaction transaction) + : this() + { + this.transaction = transaction; + } + public Transaction Transaction { - get { throw new NotImplementedException (); } + get { return transaction; } } } } diff --git a/mcs/class/System.Transactions/System.Transactions/TransactionManager.cs b/mcs/class/System.Transactions/System.Transactions/TransactionManager.cs index 85d12169e10c..a7a62b6388e5 100644 --- a/mcs/class/System.Transactions/System.Transactions/TransactionManager.cs +++ b/mcs/class/System.Transactions/System.Transactions/TransactionManager.cs @@ -8,19 +8,38 @@ // (C)2005 Novell Inc, // (C)2006 Novell Inc, // -#if NET_2_0 +#if NET_2_0 +using System.Configuration; +using System.Transactions.Configuration; namespace System.Transactions { public static class TransactionManager { - /* 60 secs */ - static TimeSpan defaultTimeout = new TimeSpan (0, 1, 0); - /* 10 mins */ - static TimeSpan maxTimeout = new TimeSpan (0, 10, 0); + static TransactionManager () + { + defaultSettings = ConfigurationManager.GetSection ("system.transactions/defaultSettings") as DefaultSettingsSection; + machineSettings = ConfigurationManager.GetSection ("system.transactions/machineSettings") as MachineSettingsSection; + } + + static DefaultSettingsSection defaultSettings; + static MachineSettingsSection machineSettings; + static TimeSpan defaultTimeout = new TimeSpan (0, 1, 0); /* 60 secs */ + static TimeSpan maxTimeout = new TimeSpan (0, 10, 0); /* 10 mins */ - public static TimeSpan DefaultTimeout { - get { return defaultTimeout; } + public static TimeSpan DefaultTimeout { + get { + // Obtain timeout from configuration setting.. + // - http://msdn.microsoft.com/en-us/library/ms973865.aspx + // - http://sankarsan.wordpress.com/2009/02/01/transaction-timeout-in-systemtransactions/ + // 1. sys.txs/defaultSettings[@timeout] + // 2. defaultTimeout + + if (defaultSettings != null) + return defaultSettings.Timeout; + + return defaultTimeout; + } } [MonoTODO ("Not implemented")] @@ -30,7 +49,13 @@ public static HostCurrentTransactionCallback HostCurrentCallback { } public static TimeSpan MaximumTimeout { - get { return maxTimeout; } + get { + + if (machineSettings != null) + return machineSettings.MaxTimeout; + + return maxTimeout; + } } [MonoTODO ("Not implemented")] diff --git a/mcs/class/System.Transactions/System.Transactions/TransactionScope.cs b/mcs/class/System.Transactions/System.Transactions/TransactionScope.cs index ca5b5822e24b..d6138bee2846 100644 --- a/mcs/class/System.Transactions/System.Transactions/TransactionScope.cs +++ b/mcs/class/System.Transactions/System.Transactions/TransactionScope.cs @@ -22,7 +22,8 @@ public sealed class TransactionScope : IDisposable Transaction transaction; Transaction oldTransaction; - TransactionScope parentScope; + TransactionScope parentScope; + TimeSpan timeout; /* Num of non-disposed nested scopes */ int nested; @@ -90,7 +91,8 @@ void Initialize (TransactionScopeOption scopeOption, { completed = false; isRoot = false; - nested = 0; + nested = 0; + this.timeout = timeout; oldTransaction = Transaction.CurrentInternal; @@ -139,6 +141,11 @@ public void Complete () internal bool IsComplete { get { return completed; } + } + + internal TimeSpan Timeout + { + get { return timeout; } } public void Dispose () diff --git a/mcs/class/System.Transactions/Test/EnlistTest.cs b/mcs/class/System.Transactions/Test/EnlistTest.cs index 1342f2f7c072..9d820c18b852 100644 --- a/mcs/class/System.Transactions/Test/EnlistTest.cs +++ b/mcs/class/System.Transactions/Test/EnlistTest.cs @@ -257,7 +257,8 @@ public void Vol2_Dur1_Fail1 () [Test] [Ignore ( "Correct this test, it should throw TimeOutException or something" )] public void Vol2_Dur1_Fail2 () - { + { + TransactionAbortedException exception = null; IntResourceManager [] irm = new IntResourceManager [4]; irm [0] = new IntResourceManager (1); irm [1] = new IntResourceManager (3); @@ -282,13 +283,67 @@ public void Vol2_Dur1_Fail2 () scope.Complete (); } } - catch (TransactionAbortedException) { + catch (TransactionAbortedException ex) { irm [0].CheckSPC ( "irm [0]" ); /* Volatile RMs get 2PC Prepare, and then get rolled back */ for (int i = 1; i < 4; i++) - irm [i].Check ( 0, 1, 0, 1, 0, "irm [" + i + "]" ); - } + irm [i].Check ( 0, 1, 0, 1, 0, "irm [" + i + "]" ); + + exception = ex; + } + + Assert.IsNotNull(exception, "Expected TransactionAbortedException not thrown!"); + Assert.IsNotNull(exception.InnerException, "TransactionAbortedException has no inner exception!"); + Assert.AreEqual(typeof(TimeoutException), exception.InnerException.GetType()); + } + + /* Same as Vol2_Dur1_Fail2, but with a volatile manager timming out */ + [Test] + [Ignore ( "Correct this test, it should throw TimeOutException or something" )] + public void Vol2_Dur1_Fail2b() + { + TransactionAbortedException exception = null; + IntResourceManager[] irm = new IntResourceManager[4]; + irm[0] = new IntResourceManager(1); + irm[1] = new IntResourceManager(3); + irm[2] = new IntResourceManager(5); + irm[3] = new IntResourceManager(7); + + irm[0].IgnoreSPC = true; + irm[1].Volatile = false; + + for (int i = 0; i < 4; i++) + irm[i].UseSingle = true; + + /* Durable RM irm[2] does on SPC, so + * all volatile RMs get Rollback */ + try + { + using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, new TimeSpan(0, 0, 5))) + { + irm[0].Value = 2; + irm[1].Value = 6; + irm[2].Value = 10; + irm[3].Value = 14; + + scope.Complete(); + } + } + catch (TransactionAbortedException ex) + { + irm[0].CheckSPC("irm [0]"); + + /* Volatile RMs get 2PC Prepare, and then get rolled back */ + for (int i = 1; i < 4; i++) + irm[i].Check(0, 1, 0, 1, 0, "irm [" + i + "]"); + + exception = ex; + } + + Assert.IsNotNull(exception, "Expected TransactionAbortedException not thrown!"); + Assert.IsNotNull(exception.InnerException, "TransactionAbortedException has no inner exception!"); + Assert.AreEqual(typeof(TimeoutException), exception.InnerException.GetType()); } /* >1vol + 1 durable @@ -499,8 +554,35 @@ public void TransactionDispose3 () irm.Check (1, 1, 0, 0, "Dispose transaction"); Assert.AreEqual (5, irm.Value); + } + + [Test] + public void TransactionCompleted_Committed () + { + bool called = false; + using (var ts = new TransactionScope ()) + { + var tr = Transaction.Current; + tr.TransactionCompleted += (s, e) => called = true; + ts.Complete (); + } + + Assert.IsTrue (called, "TransactionCompleted event handler not called!"); + } + + [Test] + public void TransactionCompleted_Rollback () + { + bool called = false; + using (var ts = new TransactionScope ()) + { + var tr = Transaction.Current; + tr.TransactionCompleted += (s, e) => called = true; + // Not calling ts.Complete() on purpose.. + } + + Assert.IsTrue (called, "TransactionCompleted event handler not called!"); } - #endregion } diff --git a/mcs/class/System.Transactions/Test/TransactionScopeTest.cs b/mcs/class/System.Transactions/Test/TransactionScopeTest.cs index 3e29a639077b..25d812ed90c0 100644 --- a/mcs/class/System.Transactions/Test/TransactionScopeTest.cs +++ b/mcs/class/System.Transactions/Test/TransactionScopeTest.cs @@ -454,8 +454,6 @@ public void RMFail1 () } [Test] - [Category("NotWorking")] - [Ignore("NotWorking")] public void RMFail2 () { IntResourceManager irm = new IntResourceManager (1);