1 // 2 // Transaction.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 using System.Collections; 13 using System.Collections.Generic; 14 using System.Security.Permissions; 15 using System.Runtime.Serialization; 16 using System.Threading; 17 18 namespace System.Transactions 19 { 20 [Serializable] 21 public class Transaction : IDisposable, ISerializable 22 { 23 [ThreadStatic] 24 static Transaction ambient; 25 26 IsolationLevel level; 27 TransactionInformation info; 28 29 ArrayList dependents = new ArrayList (); 30 31 /* Volatile enlistments */ 32 List <IEnlistmentNotification> volatiles; 33 34 /* Durable enlistments 35 Durable RMs can also have 2 Phase commit but 36 not in LTM, and that is what we are supporting 37 right now 38 */ 39 List <ISinglePhaseNotification> durables; 40 41 IPromotableSinglePhaseNotification pspe = null; 42 AsyncCommit()43 delegate void AsyncCommit (); 44 45 AsyncCommit asyncCommit = null; 46 bool committing; 47 bool committed = false; 48 bool aborted = false; 49 TransactionScope scope = null; 50 51 Exception innerException; 52 Guid tag = Guid.NewGuid (); 53 54 internal List <IEnlistmentNotification> Volatiles { 55 get { 56 if (volatiles == null) 57 volatiles = new List <IEnlistmentNotification> (); 58 return volatiles; 59 } 60 } 61 62 internal List <ISinglePhaseNotification> Durables { 63 get { 64 if (durables == null) 65 durables = new List <ISinglePhaseNotification> (); 66 return durables; 67 } 68 } 69 70 internal IPromotableSinglePhaseNotification Pspe { get { return pspe; } } 71 Transaction()72 internal Transaction () 73 { 74 info = new TransactionInformation (); 75 level = IsolationLevel.Serializable; 76 } 77 Transaction(Transaction other)78 internal Transaction (Transaction other) 79 { 80 level = other.level; 81 info = other.info; 82 dependents = other.dependents; 83 volatiles = other.Volatiles; 84 durables = other.Durables; 85 pspe = other.Pspe; 86 } 87 88 [MonoTODO] ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)89 void ISerializable.GetObjectData (SerializationInfo info, 90 StreamingContext context) 91 { 92 throw new NotImplementedException (); 93 } 94 95 public event TransactionCompletedEventHandler TransactionCompleted; 96 97 public static Transaction Current { 98 get { 99 EnsureIncompleteCurrentScope (); 100 return CurrentInternal; 101 } 102 set { 103 EnsureIncompleteCurrentScope (); 104 CurrentInternal = value; 105 } 106 } 107 108 internal static Transaction CurrentInternal { 109 get { return ambient; } 110 set { ambient = value; } 111 } 112 113 public IsolationLevel IsolationLevel { 114 get { 115 EnsureIncompleteCurrentScope (); 116 return level; 117 } 118 } 119 120 public TransactionInformation TransactionInformation { 121 get { 122 EnsureIncompleteCurrentScope (); 123 return info; 124 } 125 } 126 Clone()127 public Transaction Clone () 128 { 129 return new Transaction (this); 130 } 131 Dispose()132 public void Dispose () 133 { 134 if (TransactionInformation.Status == TransactionStatus.Active) 135 Rollback(); 136 } 137 138 [MonoTODO] DependentClone( DependentCloneOption cloneOption)139 public DependentTransaction DependentClone ( 140 DependentCloneOption cloneOption) 141 { 142 DependentTransaction d = 143 new DependentTransaction (this, cloneOption); 144 dependents.Add (d); 145 return d; 146 } 147 148 [MonoTODO ("Only SinglePhase commit supported for durable resource managers.")] 149 [PermissionSetAttribute (SecurityAction.LinkDemand)] EnlistDurable(Guid resourceManagerIdentifier, IEnlistmentNotification enlistmentNotification, EnlistmentOptions enlistmentOptions)150 public Enlistment EnlistDurable (Guid resourceManagerIdentifier, 151 IEnlistmentNotification enlistmentNotification, 152 EnlistmentOptions enlistmentOptions) 153 { 154 throw new NotImplementedException ("DTC unsupported, only SinglePhase commit supported for durable resource managers."); 155 } 156 157 [MonoTODO ("Only Local Transaction Manager supported. Cannot have more than 1 durable resource per transaction. Only EnlistmentOptions.None supported yet.")] 158 [PermissionSetAttribute (SecurityAction.LinkDemand)] EnlistDurable(Guid resourceManagerIdentifier, ISinglePhaseNotification singlePhaseNotification, EnlistmentOptions enlistmentOptions)159 public Enlistment EnlistDurable (Guid resourceManagerIdentifier, 160 ISinglePhaseNotification singlePhaseNotification, 161 EnlistmentOptions enlistmentOptions) 162 { 163 EnsureIncompleteCurrentScope (); 164 if (pspe != null || Durables.Count > 0) 165 throw new NotImplementedException ("DTC unsupported, multiple durable resource managers aren't supported."); 166 167 if (enlistmentOptions != EnlistmentOptions.None) 168 throw new NotImplementedException ("EnlistmentOptions other than None aren't supported"); 169 170 Durables.Add (singlePhaseNotification); 171 172 /* FIXME: Enlistment ?? */ 173 return new Enlistment (); 174 } 175 EnlistPromotableSinglePhase( IPromotableSinglePhaseNotification promotableSinglePhaseNotification)176 public bool EnlistPromotableSinglePhase ( 177 IPromotableSinglePhaseNotification promotableSinglePhaseNotification) 178 { 179 EnsureIncompleteCurrentScope (); 180 181 // The specs aren't entirely clear on whether we can have volatile RMs along with a PSPE, but 182 // I'm assuming that yes based on: http://social.msdn.microsoft.com/Forums/br/windowstransactionsprogramming/thread/3df6d4d3-0d82-47c4-951a-cd31140950b3 183 if (pspe != null || Durables.Count > 0) 184 return false; 185 186 pspe = promotableSinglePhaseNotification; 187 pspe.Initialize(); 188 189 return true; 190 } 191 SetDistributedTransactionIdentifier(IPromotableSinglePhaseNotification promotableNotification, Guid distributedTransactionIdentifier)192 public void SetDistributedTransactionIdentifier (IPromotableSinglePhaseNotification promotableNotification, Guid distributedTransactionIdentifier) 193 { 194 throw new NotImplementedException (); 195 } 196 EnlistPromotableSinglePhase(IPromotableSinglePhaseNotification promotableSinglePhaseNotification, Guid promoterType)197 public bool EnlistPromotableSinglePhase (IPromotableSinglePhaseNotification promotableSinglePhaseNotification, Guid promoterType) 198 { 199 throw new NotImplementedException (); 200 } 201 GetPromotedToken()202 public byte[] GetPromotedToken () 203 { 204 throw new NotImplementedException (); 205 } 206 207 public Guid PromoterType 208 { 209 get { throw new NotImplementedException (); } 210 } 211 212 [MonoTODO ("EnlistmentOptions being ignored")] EnlistVolatile( IEnlistmentNotification enlistmentNotification, EnlistmentOptions enlistmentOptions)213 public Enlistment EnlistVolatile ( 214 IEnlistmentNotification enlistmentNotification, 215 EnlistmentOptions enlistmentOptions) 216 { 217 return EnlistVolatileInternal (enlistmentNotification, enlistmentOptions); 218 } 219 220 [MonoTODO ("EnlistmentOptions being ignored")] EnlistVolatile( ISinglePhaseNotification singlePhaseNotification, EnlistmentOptions enlistmentOptions)221 public Enlistment EnlistVolatile ( 222 ISinglePhaseNotification singlePhaseNotification, 223 EnlistmentOptions enlistmentOptions) 224 { 225 /* FIXME: Anything extra reqd for this? */ 226 return EnlistVolatileInternal (singlePhaseNotification, enlistmentOptions); 227 } 228 EnlistVolatileInternal( IEnlistmentNotification notification, EnlistmentOptions options)229 private Enlistment EnlistVolatileInternal ( 230 IEnlistmentNotification notification, 231 EnlistmentOptions options) 232 { 233 EnsureIncompleteCurrentScope (); 234 /* FIXME: Handle options.EnlistDuringPrepareRequired */ 235 Volatiles.Add (notification); 236 237 /* FIXME: Enlistment.. ? */ 238 return new Enlistment (); 239 } 240 241 [MonoTODO ("Only Local Transaction Manager supported. Cannot have more than 1 durable resource per transaction.")] 242 [PermissionSetAttribute (SecurityAction.LinkDemand)] PromoteAndEnlistDurable( Guid manager, IPromotableSinglePhaseNotification promotableNotification, ISinglePhaseNotification notification, EnlistmentOptions options)243 public Enlistment PromoteAndEnlistDurable ( 244 Guid manager, 245 IPromotableSinglePhaseNotification promotableNotification, 246 ISinglePhaseNotification notification, 247 EnlistmentOptions options) 248 { 249 throw new NotImplementedException ("DTC unsupported, multiple durable resource managers aren't supported."); 250 } 251 Equals(object obj)252 public override bool Equals (object obj) 253 { 254 return Equals (obj as Transaction); 255 } 256 257 // FIXME: Check whether this is correct (currently, GetHashCode() uses 'dependents' but this doesn't) Equals(Transaction t)258 private bool Equals (Transaction t) 259 { 260 if (ReferenceEquals (t, this)) 261 return true; 262 if (ReferenceEquals (t, null)) 263 return false; 264 return this.level == t.level && 265 this.info == t.info; 266 } 267 operator ==(Transaction x, Transaction y)268 public static bool operator == (Transaction x, Transaction y) 269 { 270 if (ReferenceEquals (x, null)) 271 return ReferenceEquals (y, null); 272 return x.Equals (y); 273 } 274 operator !=(Transaction x, Transaction y)275 public static bool operator != (Transaction x, Transaction y) 276 { 277 return !(x == y); 278 } 279 GetHashCode()280 public override int GetHashCode () 281 { 282 return (int) level ^ info.GetHashCode () ^ dependents.GetHashCode (); 283 } 284 Rollback()285 public void Rollback () 286 { 287 Rollback (null); 288 } 289 Rollback(Exception e)290 public void Rollback (Exception e) 291 { 292 EnsureIncompleteCurrentScope (); 293 Rollback (e, null); 294 } 295 Rollback(Exception ex, object abortingEnlisted)296 internal void Rollback (Exception ex, object abortingEnlisted) 297 { 298 if (aborted) 299 { 300 FireCompleted (); 301 return; 302 } 303 304 /* See test ExplicitTransaction7 */ 305 if (info.Status == TransactionStatus.Committed) 306 throw new TransactionException ("Transaction has already been committed. Cannot accept any new work."); 307 308 // Save thrown exception as 'reason' of transaction's abort. 309 innerException = ex; 310 311 SinglePhaseEnlistment e = new SinglePhaseEnlistment(); 312 foreach (IEnlistmentNotification prep in Volatiles) 313 if (prep != abortingEnlisted) 314 prep.Rollback (e); 315 316 var durables = Durables; 317 if (durables.Count > 0 && durables [0] != abortingEnlisted) 318 durables [0].Rollback (e); 319 320 if (pspe != null && pspe != abortingEnlisted) 321 pspe.Rollback (e); 322 323 Aborted = true; 324 325 FireCompleted (); 326 } 327 328 bool Aborted { 329 get { return aborted; } 330 set { 331 aborted = value; 332 if (aborted) 333 info.Status = TransactionStatus.Aborted; 334 } 335 } 336 337 internal TransactionScope Scope { 338 get { return scope; } 339 set { scope = value; } 340 } 341 BeginCommitInternal(AsyncCallback callback)342 protected IAsyncResult BeginCommitInternal (AsyncCallback callback) 343 { 344 if (committed || committing) 345 throw new InvalidOperationException ("Commit has already been called for this transaction."); 346 347 this.committing = true; 348 349 asyncCommit = new AsyncCommit (DoCommit); 350 return asyncCommit.BeginInvoke (callback, null); 351 } 352 EndCommitInternal(IAsyncResult ar)353 protected void EndCommitInternal (IAsyncResult ar) 354 { 355 asyncCommit.EndInvoke (ar); 356 } 357 CommitInternal()358 internal void CommitInternal () 359 { 360 if (committed || committing) 361 throw new InvalidOperationException ("Commit has already been called for this transaction."); 362 363 this.committing = true; 364 365 try { 366 DoCommit (); 367 } 368 catch (TransactionException) 369 { 370 throw; 371 } 372 catch (Exception ex) 373 { 374 throw new TransactionAbortedException("Transaction failed", ex); 375 } 376 } 377 DoCommit()378 private void DoCommit () 379 { 380 /* Scope becomes null in TransactionScope.Dispose */ 381 if (Scope != null) { 382 /* See test ExplicitTransaction8 */ 383 Rollback (null, null); 384 CheckAborted (); 385 } 386 387 var volatiles = Volatiles; 388 var durables = Durables; 389 if (volatiles.Count == 1 && durables.Count == 0) 390 { 391 /* Special case */ 392 ISinglePhaseNotification single = volatiles[0] as ISinglePhaseNotification; 393 if (single != null) 394 { 395 DoSingleCommit(single); 396 Complete(); 397 return; 398 } 399 } 400 401 if (volatiles.Count > 0) 402 DoPreparePhase(); 403 404 if (durables.Count > 0) 405 DoSingleCommit(durables[0]); 406 407 if (pspe != null) 408 DoSingleCommit(pspe); 409 410 if (volatiles.Count > 0) 411 DoCommitPhase(); 412 413 Complete(); 414 } 415 Complete()416 private void Complete () 417 { 418 committing = false; 419 committed = true; 420 421 if (!aborted) 422 info.Status = TransactionStatus.Committed; 423 424 FireCompleted (); 425 } 426 InitScope(TransactionScope scope)427 internal void InitScope (TransactionScope scope) 428 { 429 /* See test NestedTransactionScope10 */ 430 CheckAborted (); 431 432 /* See test ExplicitTransaction6a */ 433 if (committed) 434 throw new InvalidOperationException ("Commit has already been called on this transaction."); 435 436 Scope = scope; 437 } 438 PrepareCallbackWrapper(object state)439 static void PrepareCallbackWrapper(object state) 440 { 441 PreparingEnlistment enlist = state as PreparingEnlistment; 442 443 try 444 { 445 enlist.EnlistmentNotification.Prepare(enlist); 446 } 447 catch (Exception ex) 448 { 449 // Oops! Unhandled exception.. we should notify 450 // to our caller thread that preparing has failed. 451 // This usually happends when an exception is 452 // thrown by code enlistment.Rollback() methods 453 // executed inside prepare.ForceRollback(ex). 454 enlist.Exception = ex; 455 456 // Just in case enlistment did not call Prepared() 457 // we need to manually set WH to avoid transaction 458 // from failing due to transaction timeout. 459 if (!enlist.IsPrepared) 460 ((ManualResetEvent)enlist.WaitHandle).Set(); 461 } 462 } 463 DoPreparePhase()464 void DoPreparePhase () 465 { 466 // Call prepare on all volatile managers. 467 foreach (IEnlistmentNotification enlist in Volatiles) 468 { 469 PreparingEnlistment pe = new PreparingEnlistment (this, enlist); 470 ThreadPool.QueueUserWorkItem (new WaitCallback(PrepareCallbackWrapper), pe); 471 472 /* Wait (with timeout) for manager to prepare */ 473 TimeSpan timeout = Scope != null ? Scope.Timeout : TransactionManager.DefaultTimeout; 474 475 // FIXME: Should we managers in parallel or on-by-one? 476 if (!pe.WaitHandle.WaitOne(timeout, true)) 477 { 478 this.Aborted = true; 479 throw new TimeoutException("Transaction timedout"); 480 } 481 482 if (pe.Exception != null) 483 { 484 innerException = pe.Exception; 485 Aborted = true; 486 break; 487 } 488 489 if (!pe.IsPrepared) 490 { 491 /* FIXME: if not prepared & !aborted as yet, then 492 this is inDoubt ? . For now, setting aborted = true */ 493 Aborted = true; 494 break; 495 } 496 } 497 498 /* Either InDoubt(tmp) or Prepare failed and 499 Tx has rolledback */ 500 CheckAborted (); 501 } 502 DoCommitPhase()503 void DoCommitPhase () 504 { 505 foreach (IEnlistmentNotification enlisted in Volatiles) { 506 Enlistment e = new Enlistment (); 507 enlisted.Commit (e); 508 /* Note: e.Done doesn't matter for volatile RMs */ 509 } 510 } 511 DoSingleCommit(ISinglePhaseNotification single)512 void DoSingleCommit (ISinglePhaseNotification single) 513 { 514 if (single == null) 515 return; 516 517 single.SinglePhaseCommit (new SinglePhaseEnlistment (this, single)); 518 CheckAborted (); 519 } 520 DoSingleCommit(IPromotableSinglePhaseNotification single)521 void DoSingleCommit (IPromotableSinglePhaseNotification single) 522 { 523 if (single == null) 524 return; 525 526 single.SinglePhaseCommit (new SinglePhaseEnlistment (this, single)); 527 CheckAborted (); 528 } 529 CheckAborted()530 void CheckAborted () 531 { 532 if (aborted) 533 throw new TransactionAbortedException ("Transaction has aborted", innerException); 534 } 535 FireCompleted()536 void FireCompleted () 537 { 538 if (TransactionCompleted != null) 539 TransactionCompleted (this, new TransactionEventArgs(this)); 540 } 541 EnsureIncompleteCurrentScope()542 static void EnsureIncompleteCurrentScope () 543 { 544 if (CurrentInternal == null) 545 return; 546 if (CurrentInternal.Scope != null && CurrentInternal.Scope.IsComplete) 547 throw new InvalidOperationException ("The current TransactionScope is already complete"); 548 } 549 } 550 } 551 552