Made changes suggested by marek.
[mono.git] / mcs / class / System.Transactions / System.Transactions / TransactionScope.cs
1 //
2 // TransactionScope.cs
3 //
4 // Author:
5 //      Atsushi Enomoto  <atsushi@ximian.com>
6 //      Ankit Jain       <JAnkit@novell.com>
7 //
8 // (C)2005 Novell Inc,
9 // (C)2006 Novell Inc,
10 //
11
12
13 using DTCOption = System.Transactions.EnterpriseServicesInteropOption;
14
15 namespace System.Transactions
16 {
17         public sealed class TransactionScope : IDisposable
18         {
19                 static TransactionOptions defaultOptions =
20                         new TransactionOptions (0, TransactionManager.DefaultTimeout);
21
22                 Transaction transaction;
23                 Transaction oldTransaction;
24                 TransactionScope parentScope;
25                 TimeSpan timeout;
26
27                 /* Num of non-disposed nested scopes */
28                 int nested;
29
30                 bool disposed;
31                 bool completed;
32                 bool isRoot;
33
34                 bool asyncFlowEnabled;
35
36                 public TransactionScope ()
37                         : this (TransactionScopeOption.Required,
38                                 TransactionManager.DefaultTimeout)
39                 {
40                 }
41
42                 public TransactionScope(TransactionScopeAsyncFlowOption asyncFlow)
43                         : this(TransactionScopeOption.Required,
44                                 TransactionManager.DefaultTimeout, asyncFlow)
45                 {
46                 }
47
48                 public TransactionScope (Transaction transaction)
49                         : this (transaction, TransactionManager.DefaultTimeout)
50                 {
51                 }
52
53                 public TransactionScope (Transaction transaction,
54                         TimeSpan timeout)
55                         : this (transaction, timeout, DTCOption.None)
56                 {
57                 }
58
59                 [MonoTODO ("EnterpriseServicesInteropOption not supported.")]
60                 public TransactionScope (Transaction transaction,
61                         TimeSpan timeout, DTCOption opt)
62                 {
63                         Initialize (TransactionScopeOption.Required,
64                                 transaction, defaultOptions, opt, timeout, TransactionScopeAsyncFlowOption.Suppress);
65                 }
66
67                 public TransactionScope (TransactionScopeOption option)
68                         : this (option, TransactionManager.DefaultTimeout)
69                 {
70                 }
71
72                 public TransactionScope(TransactionScopeOption option, TransactionScopeAsyncFlowOption asyncFlow)
73                         : this(option, TransactionManager.DefaultTimeout, asyncFlow)
74                 {
75                 }
76
77         public TransactionScope (TransactionScopeOption option,
78                         TimeSpan timeout, TransactionScopeAsyncFlowOption asyncFlow)
79                 {
80                         Initialize (option, null, defaultOptions,
81                                 DTCOption.None, timeout, asyncFlow);
82                 }
83
84                 public TransactionScope (TransactionScopeOption scopeOption,
85                         TransactionOptions options)
86                         : this (scopeOption, options, DTCOption.None)
87                 {
88                 }
89
90                 [MonoTODO ("EnterpriseServicesInteropOption not supported")]
91                 public TransactionScope (TransactionScopeOption scopeOption,
92                         TransactionOptions options,
93                         DTCOption opt)
94                 {
95                         Initialize (scopeOption, null, options, opt,
96                                 TransactionManager.DefaultTimeout, TransactionScopeAsyncFlowOption.Suppress);
97                 }
98
99                 void Initialize (TransactionScopeOption scopeOption,
100                         Transaction tx, TransactionOptions options,
101                         DTCOption interop, TimeSpan timeout, TransactionScopeAsyncFlowOption asyncFlow)
102                 {
103                         completed = false;
104                         isRoot = false;
105                         nested = 0;
106                         asyncFlowEnabled = asyncFlow == TransactionScopeAsyncFlowOption.Enabled;
107
108                         if (timeout < TimeSpan.Zero)
109                                 throw new ArgumentOutOfRangeException ("timeout");
110
111                         this.timeout = timeout;
112
113                         oldTransaction = Transaction.CurrentInternal;
114
115                         Transaction.CurrentInternal = transaction = InitTransaction (tx, scopeOption);
116                         if (transaction != null)
117                                 transaction.InitScope (this);
118                         if (parentScope != null)
119                                 parentScope.nested ++;
120                 }
121
122                 Transaction InitTransaction (Transaction tx, TransactionScopeOption scopeOption)
123                 {
124                         if (tx != null)
125                                 return tx;
126                                 
127                         if (scopeOption == TransactionScopeOption.Suppress) {
128                                 if (Transaction.CurrentInternal != null)
129                                         parentScope = Transaction.CurrentInternal.Scope;
130                                 return null;
131                         }
132
133                         if (scopeOption == TransactionScopeOption.Required) {
134                                 if (Transaction.CurrentInternal == null) {
135                                         isRoot = true;
136                                         return new Transaction ();
137                                 }
138
139                                 parentScope = Transaction.CurrentInternal.Scope;
140                                 return Transaction.CurrentInternal;
141                         }
142
143                         /* RequiresNew */
144                         if (Transaction.CurrentInternal != null)
145                                 parentScope = Transaction.CurrentInternal.Scope;
146                         isRoot = true;
147                         return new Transaction ();
148                 }
149
150                 public void Complete ()
151                 {
152                         if (completed)
153                                 throw new InvalidOperationException ("The current TransactionScope is already complete. You should dispose the TransactionScope.");
154
155                         completed = true;
156                 }
157
158                 internal bool IsComplete {
159                         get { return completed; }
160                 }
161
162                 internal TimeSpan Timeout
163                 {
164                         get { return timeout; }
165                 }
166
167                 public void Dispose ()
168                 {
169                         if (disposed)
170                                 return;
171
172                         disposed = true;
173
174                         if (parentScope != null)
175                                 parentScope.nested --;
176
177                         if (nested > 0) {
178                                 transaction.Rollback ();
179                                 throw new InvalidOperationException ("TransactionScope nested incorrectly");
180                         }
181
182                         if (Transaction.CurrentInternal != transaction && !asyncFlowEnabled) {
183                                 if (transaction != null)
184                                         transaction.Rollback ();
185                                 if (Transaction.CurrentInternal != null)
186                                         Transaction.CurrentInternal.Rollback ();
187
188                                 throw new InvalidOperationException ("Transaction.Current has changed inside of the TransactionScope");
189                         } 
190
191                         if (asyncFlowEnabled) {
192                                 if (oldTransaction != null)
193                                         oldTransaction.Scope = parentScope;
194
195                                 var variedTransaction = Transaction.CurrentInternal;
196
197                                 if (transaction == null && variedTransaction == null)
198                                         /* scope was not in a transaction, (Suppress) */
199                                         return;
200
201                                 variedTransaction.Scope = parentScope;
202                                 Transaction.CurrentInternal = oldTransaction;
203
204                                 transaction.Scope = null;
205
206                                 if (!IsComplete) {
207                                         transaction.Rollback ();
208                                         variedTransaction.Rollback();
209                                         return;
210                                 }
211
212                                 if (!isRoot)
213                                         /* Non-root scope has completed+ended */
214                                         return;
215
216                                 variedTransaction.CommitInternal();
217                                 transaction.CommitInternal();
218                         } else {
219                                 if (Transaction.CurrentInternal == oldTransaction && oldTransaction != null)
220                                         oldTransaction.Scope = parentScope;
221
222                                 Transaction.CurrentInternal = oldTransaction;
223
224                                 if (transaction == null)
225                                         /* scope was not in a transaction, (Suppress) */
226                                         return;
227
228                                 transaction.Scope = null;
229
230                                 if (!IsComplete)
231                                 {
232                                         transaction.Rollback();
233                                         return;
234                                 }
235
236                                 if (!isRoot)
237                                         /* Non-root scope has completed+ended */
238                                         return;
239
240                                 transaction.CommitInternal();
241
242                         }
243                 }
244
245
246         }
247 }
248