1 //---------------------------------------------------------------- 2 // Copyright (c) Microsoft Corporation. All rights reserved. 3 //---------------------------------------------------------------- 4 5 namespace System.Runtime.DurableInstancing 6 { 7 using System.Collections.Generic; 8 using System.Linq; 9 using System.Threading; 10 using System.Transactions; 11 using System.Xml.Linq; 12 using System.Runtime.Diagnostics; 13 14 [Fx.Tag.XamlVisible(false)] 15 public sealed class InstanceHandle 16 { 17 [Fx.Tag.SynchronizationObject(Blocking = false)] 18 readonly object thisLock = new object(); 19 20 object providerObject; 21 bool providerObjectSet; 22 bool needFreedNotification; 23 PreparingEnlistment pendingPreparingEnlistment; 24 AcquireContextAsyncResult pendingRollback; 25 InstanceHandleReference inProgressBind; 26 WaitForEventsAsyncResult waitResult; 27 HashSet<XName> boundOwnerEvents; 28 HashSet<InstancePersistenceEvent> pendingOwnerEvents; 29 EventTraceActivity eventTraceActivity; 30 31 // Fields used to implement an atomic Guid Id get/set property. 32 Guid id; 33 volatile bool idIsSet; 34 InstanceHandle(InstanceStore store, InstanceOwner owner)35 internal InstanceHandle(InstanceStore store, InstanceOwner owner) 36 { 37 Fx.Assert(store != null, "Shouldn't be possible."); 38 39 Version = -1; 40 Store = store; 41 Owner = owner; 42 View = new InstanceView(owner); 43 IsValid = true; 44 } 45 InstanceHandle(InstanceStore store, InstanceOwner owner, Guid instanceId)46 internal InstanceHandle(InstanceStore store, InstanceOwner owner, Guid instanceId) 47 { 48 Fx.Assert(store != null, "Shouldn't be possible here either."); 49 Fx.Assert(instanceId != Guid.Empty, "Should be validating this."); 50 51 Version = -1; 52 Store = store; 53 Owner = owner; 54 Id = instanceId; 55 View = new InstanceView(owner, instanceId); 56 IsValid = true; 57 if (Fx.Trace.IsEtwProviderEnabled) 58 { 59 eventTraceActivity = new EventTraceActivity(instanceId); 60 } 61 } 62 63 64 public bool IsValid { get; private set; } 65 66 67 internal InstanceView View { get; private set; } 68 internal InstanceStore Store { get; private set; } 69 70 internal InstanceOwner Owner { get; private set; } 71 72 // Since writing to a Guid field is not atomic, we need synchronization between reading and writing. The idIsSet boolean field can only 73 // appear true once the id field is completely written due to the memory barriers implied by the reads and writes to a volatile field. 74 // Writes to bool fields are atomic, and this property is only written to once. By checking the bool prior to reading the Guid, we can 75 // be sure that the Guid is fully materialized when read. 76 internal Guid Id 77 { 78 get 79 { 80 // this.idIsSet is volatile. 81 if (!this.idIsSet) 82 { 83 return Guid.Empty; 84 } 85 return this.id; 86 } 87 88 private set 89 { 90 Fx.Assert(value != Guid.Empty, "Cannot set an empty Id."); 91 Fx.Assert(this.id == Guid.Empty, "Cannot set Id more than once."); 92 Fx.Assert(!this.idIsSet, "idIsSet out of sync with id."); 93 94 this.id = value; 95 96 if (Fx.Trace.IsEtwProviderEnabled) 97 { 98 eventTraceActivity = new EventTraceActivity(value); 99 } 100 101 // this.isIdSet is volatile. 102 this.idIsSet = true; 103 } 104 } 105 106 internal long Version { get; private set; } 107 108 internal InstanceHandle ConflictingHandle { get; set; } 109 110 internal object ProviderObject 111 { 112 get 113 { 114 return this.providerObject; 115 } 116 set 117 { 118 this.providerObject = value; 119 this.providerObjectSet = true; 120 } 121 } 122 123 internal EventTraceActivity EventTraceActivity 124 { 125 get 126 { 127 return this.eventTraceActivity; 128 } 129 } 130 131 // When non-null, a transaction is pending. 132 AcquireContextAsyncResult CurrentTransactionalAsyncResult { get; set; } 133 134 bool OperationPending { get; set; } 135 bool TooLateToEnlist { get; set; } 136 AcquireContextAsyncResult AcquirePending { get; set; } 137 InstancePersistenceContext CurrentExecutionContext { get; set; } 138 139 object ThisLock 140 { 141 get 142 { 143 return this.thisLock; 144 } 145 } 146 147 Free()148 public void Free() 149 { 150 if (!this.providerObjectSet) 151 { 152 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.HandleFreedBeforeInitialized)); 153 } 154 155 if (!IsValid) 156 { 157 return; 158 } 159 160 List<InstanceHandleReference> handlesPendingResolution = null; 161 WaitForEventsAsyncResult resultToCancel = null; 162 163 try 164 { 165 bool needNotification = false; 166 InstancePersistenceContext currentContext = null; 167 168 lock (ThisLock) 169 { 170 if (!IsValid) 171 { 172 return; 173 } 174 IsValid = false; 175 176 IEnumerable<XName> eventsToUnbind = null; 177 if (this.pendingOwnerEvents != null && this.pendingOwnerEvents.Count > 0) 178 { 179 eventsToUnbind = this.pendingOwnerEvents.Select(persistenceEvent => persistenceEvent.Name); 180 } 181 if (this.boundOwnerEvents != null && this.boundOwnerEvents.Count > 0) 182 { 183 eventsToUnbind = eventsToUnbind == null ? this.boundOwnerEvents : eventsToUnbind.Concat(this.boundOwnerEvents); 184 } 185 if (eventsToUnbind != null) 186 { 187 Fx.Assert(Owner != null, "How do we have owner events without an owner."); 188 Store.RemoveHandleFromEvents(this, eventsToUnbind, Owner); 189 } 190 if (this.waitResult != null) 191 { 192 resultToCancel = this.waitResult; 193 this.waitResult = null; 194 } 195 196 if (OperationPending) 197 { 198 if (AcquirePending != null) 199 { 200 // If in this stage, we need to short-circuit the pending transaction. 201 Fx.Assert(CurrentTransactionalAsyncResult != null, "Should have a pending transaction if we are waiting for it."); 202 CurrentTransactionalAsyncResult.WaitForHostTransaction.Set(); 203 this.needFreedNotification = true; 204 } 205 else 206 { 207 // Here, just notify the currently executing command. 208 Fx.Assert(CurrentExecutionContext != null, "Must have either this or AcquirePending set."); 209 currentContext = CurrentExecutionContext; 210 } 211 } 212 else 213 { 214 needNotification = true; 215 216 if (this.inProgressBind != null) 217 { 218 Owner.CancelBind(ref this.inProgressBind, ref handlesPendingResolution); 219 } 220 else if (Version != -1) 221 { 222 // This means the handle was successfully bound in the past. Need to remove it from the table of handles. 223 Owner.Unbind(this); 224 } 225 } 226 } 227 228 if (currentContext != null) 229 { 230 // Need to do this not in a lock. 231 currentContext.NotifyHandleFree(); 232 233 lock (ThisLock) 234 { 235 if (OperationPending) 236 { 237 this.needFreedNotification = true; 238 239 // Cancel any pending lock reclaim here. 240 if (this.inProgressBind != null) 241 { 242 Fx.Assert(Owner != null, "Must be bound to owner to have an inProgressBind for the lock in CancelReclaim."); 243 244 // Null reason defaults to OperationCanceledException. (Defer creating it since this might not be a 245 // reclaim attempt, but we don't know until we take the HandlesLock.) 246 Owner.FaultBind(ref this.inProgressBind, ref handlesPendingResolution, null); 247 } 248 } 249 else 250 { 251 needNotification = true; 252 } 253 } 254 } 255 256 if (needNotification) 257 { 258 Store.FreeInstanceHandle(this, ProviderObject); 259 } 260 } 261 finally 262 { 263 if (resultToCancel != null) 264 { 265 resultToCancel.Canceled(); 266 } 267 268 InstanceOwner.ResolveHandles(handlesPendingResolution); 269 } 270 } 271 BindOwnerEvent(InstancePersistenceEvent persistenceEvent)272 internal void BindOwnerEvent(InstancePersistenceEvent persistenceEvent) 273 { 274 lock (ThisLock) 275 { 276 Fx.Assert(OperationPending, "Should only be called during an operation."); 277 Fx.Assert(AcquirePending == null, "Should only be called after acquiring the transaction."); 278 Fx.Assert(Owner != null, "Must be bound to owner to have an owner-scoped event."); 279 280 if (IsValid && (this.boundOwnerEvents == null || !this.boundOwnerEvents.Contains(persistenceEvent.Name))) 281 { 282 if (this.pendingOwnerEvents == null) 283 { 284 this.pendingOwnerEvents = new HashSet<InstancePersistenceEvent>(); 285 } 286 else if (this.pendingOwnerEvents.Contains(persistenceEvent)) 287 { 288 return; 289 } 290 this.pendingOwnerEvents.Add(persistenceEvent); 291 Store.PendHandleToEvent(this, persistenceEvent, Owner); 292 } 293 } 294 } 295 StartPotentialBind()296 internal void StartPotentialBind() 297 { 298 lock (ThisLock) 299 { 300 Fx.AssertAndThrow(Version == -1, "Handle already bound to a lock."); 301 302 Fx.Assert(OperationPending, "Should only be called during an operation."); 303 Fx.Assert(AcquirePending == null, "Should only be called after acquiring the transaction."); 304 Fx.Assert(this.inProgressBind == null, "StartPotentialBind should only be called once per command."); 305 Fx.Assert(Owner != null, "Must be bound to owner to have an inProgressBind for the lock."); 306 307 Owner.StartBind(this, ref this.inProgressBind); 308 } 309 } 310 BindOwner(InstanceOwner owner)311 internal void BindOwner(InstanceOwner owner) 312 { 313 Fx.Assert(owner != null, "Null owner passed to BindOwner."); 314 315 lock (ThisLock) 316 { 317 Fx.Assert(this.inProgressBind == null, "How did we get a bind in progress without an owner?"); 318 319 Fx.Assert(Owner == null, "BindOwner called when we already have an owner."); 320 Owner = owner; 321 } 322 } 323 BindInstance(Guid instanceId)324 internal void BindInstance(Guid instanceId) 325 { 326 Fx.Assert(instanceId != Guid.Empty, "BindInstance called with empty Guid."); 327 328 List<InstanceHandleReference> handlesPendingResolution = null; 329 try 330 { 331 lock (ThisLock) 332 { 333 Fx.Assert(Id == Guid.Empty, "Instance already boud in BindInstance."); 334 Id = instanceId; 335 336 Fx.Assert(OperationPending, "BindInstance should only be called during an operation."); 337 Fx.Assert(AcquirePending == null, "BindInstance should only be called after acquiring the transaction."); 338 if (this.inProgressBind != null) 339 { 340 Fx.Assert(Owner != null, "Must be bound to owner to have an inProgressBind for the lock."); 341 Owner.InstanceBound(ref this.inProgressBind, ref handlesPendingResolution); 342 } 343 } 344 } 345 finally 346 { 347 InstanceOwner.ResolveHandles(handlesPendingResolution); 348 } 349 } 350 Bind(long instanceVersion)351 internal void Bind(long instanceVersion) 352 { 353 Fx.AssertAndThrow(instanceVersion >= 0, "Negative instanceVersion passed to Bind."); 354 Fx.Assert(Owner != null, "Bind called before owner bound."); 355 Fx.Assert(Id != Guid.Empty, "Bind called before instance bound."); 356 357 lock (ThisLock) 358 { 359 Fx.AssertAndThrow(Version == -1, "This should only be reachable once per handle."); 360 Version = instanceVersion; 361 362 Fx.Assert(OperationPending, "Bind should only be called during an operation."); 363 Fx.Assert(AcquirePending == null, "Bind should only be called after acquiring the transaction."); 364 if (this.inProgressBind == null) 365 { 366 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.BindLockRequiresCommandFlag)); 367 } 368 } 369 } 370 371 // Returns null if an InstanceHandleConflictException should be thrown. StartReclaim(long instanceVersion)372 internal AsyncWaitHandle StartReclaim(long instanceVersion) 373 { 374 List<InstanceHandleReference> handlesPendingResolution = null; 375 try 376 { 377 lock (ThisLock) 378 { 379 Fx.AssertAndThrow(Version == -1, "StartReclaim should only be reachable if the lock hasn't been bound."); 380 381 Fx.Assert(OperationPending, "StartReclaim should only be called during an operation."); 382 Fx.Assert(AcquirePending == null, "StartReclaim should only be called after acquiring the transaction."); 383 if (this.inProgressBind == null) 384 { 385 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.BindLockRequiresCommandFlag)); 386 } 387 388 Fx.Assert(Owner != null, "Must be bound to owner to have an inProgressBind for the lock in StartReclaim."); 389 return Owner.InitiateLockResolution(instanceVersion, ref this.inProgressBind, ref handlesPendingResolution); 390 } 391 } 392 finally 393 { 394 InstanceOwner.ResolveHandles(handlesPendingResolution); 395 } 396 } 397 398 // After calling this method, the caller doesn't need to wait for the wait handle to become set (but they can). CancelReclaim(Exception reason)399 internal void CancelReclaim(Exception reason) 400 { 401 List<InstanceHandleReference> handlesPendingResolution = null; 402 try 403 { 404 lock (ThisLock) 405 { 406 if (this.inProgressBind == null) 407 { 408 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.DoNotCompleteTryCommandWithPendingReclaim)); 409 } 410 411 Fx.Assert(Owner != null, "Must be bound to owner to have an inProgressBind for the lock in CancelReclaim."); 412 Owner.FaultBind(ref this.inProgressBind, ref handlesPendingResolution, reason); 413 } 414 } 415 finally 416 { 417 InstanceOwner.ResolveHandles(handlesPendingResolution); 418 } 419 } 420 421 // Returns the false if an InstanceHandleConflictException should be thrown. FinishReclaim(ref long instanceVersion)422 internal bool FinishReclaim(ref long instanceVersion) 423 { 424 List<InstanceHandleReference> handlesPendingResolution = null; 425 try 426 { 427 lock (ThisLock) 428 { 429 if (this.inProgressBind == null) 430 { 431 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.DoNotCompleteTryCommandWithPendingReclaim)); 432 } 433 434 Fx.Assert(Owner != null, "Must be bound to owner to have an inProgressBind for the lock in CancelReclaim."); 435 if (!Owner.FinishBind(ref this.inProgressBind, ref instanceVersion, ref handlesPendingResolution)) 436 { 437 return false; 438 } 439 440 Fx.AssertAndThrow(Version == -1, "Should only be able to set the version once per handle."); 441 Fx.AssertAndThrow(instanceVersion >= 0, "Incorrect version resulting from conflict resolution."); 442 Version = instanceVersion; 443 return true; 444 } 445 } 446 finally 447 { 448 InstanceOwner.ResolveHandles(handlesPendingResolution); 449 } 450 } 451 452 [Fx.Tag.Blocking(CancelMethod = "Free")] AcquireExecutionContext(Transaction hostTransaction, TimeSpan timeout)453 internal InstancePersistenceContext AcquireExecutionContext(Transaction hostTransaction, TimeSpan timeout) 454 { 455 bool setOperationPending = false; 456 InstancePersistenceContext result = null; 457 try 458 { 459 result = AcquireContextAsyncResult.End(new AcquireContextAsyncResult(this, hostTransaction, timeout, out setOperationPending)); 460 Fx.AssertAndThrow(result != null, "Null result returned from AcquireContextAsyncResult (synchronous)."); 461 return result; 462 } 463 finally 464 { 465 if (result == null && setOperationPending) 466 { 467 FinishOperation(); 468 } 469 } 470 } 471 BeginAcquireExecutionContext(Transaction hostTransaction, TimeSpan timeout, AsyncCallback callback, object state)472 internal IAsyncResult BeginAcquireExecutionContext(Transaction hostTransaction, TimeSpan timeout, AsyncCallback callback, object state) 473 { 474 bool setOperationPending = false; 475 IAsyncResult result = null; 476 try 477 { 478 result = new AcquireContextAsyncResult(this, hostTransaction, timeout, out setOperationPending, callback, state); 479 return result; 480 } 481 finally 482 { 483 if (result == null && setOperationPending) 484 { 485 FinishOperation(); 486 } 487 } 488 } 489 490 [Fx.Tag.Blocking(CancelMethod = "Free", Conditional = "!result.IsCompleted")] EndAcquireExecutionContext(IAsyncResult result)491 internal InstancePersistenceContext EndAcquireExecutionContext(IAsyncResult result) 492 { 493 return AcquireContextAsyncResult.End(result); 494 } 495 ReleaseExecutionContext()496 internal void ReleaseExecutionContext() 497 { 498 Fx.Assert(OperationPending, "ReleaseExecutionContext called with no operation pending."); 499 FinishOperation(); 500 } 501 502 // Returns null if an InstanceHandleConflictException should be thrown. Commit(InstanceView newState)503 internal InstanceView Commit(InstanceView newState) 504 { 505 Fx.Assert(newState != null, "Null view passed to Commit."); 506 newState.MakeReadOnly(); 507 View = newState; 508 509 List<InstanceHandleReference> handlesPendingResolution = null; 510 InstanceHandle handleToFree = null; 511 List<InstancePersistenceEvent> normals = null; 512 WaitForEventsAsyncResult resultToComplete = null; 513 try 514 { 515 lock (ThisLock) 516 { 517 if (this.inProgressBind != null) 518 { 519 // If there's a Version, it should be committed. 520 if (Version != -1) 521 { 522 if (!Owner.TryCompleteBind(ref this.inProgressBind, ref handlesPendingResolution, out handleToFree)) 523 { 524 return null; 525 } 526 } 527 else 528 { 529 Fx.Assert(OperationPending, "Should have cancelled this bind in FinishOperation."); 530 Fx.Assert(AcquirePending == null, "Should not be in Commit during AcquirePending."); 531 Owner.CancelBind(ref this.inProgressBind, ref handlesPendingResolution); 532 } 533 } 534 535 if (this.pendingOwnerEvents != null && IsValid) 536 { 537 if (this.boundOwnerEvents == null) 538 { 539 this.boundOwnerEvents = new HashSet<XName>(); 540 } 541 542 foreach (InstancePersistenceEvent persistenceEvent in this.pendingOwnerEvents) 543 { 544 if (!this.boundOwnerEvents.Add(persistenceEvent.Name)) 545 { 546 Fx.Assert("Should not have conflicts between pending and bound events."); 547 continue; 548 } 549 550 InstancePersistenceEvent normal = Store.AddHandleToEvent(this, persistenceEvent, Owner); 551 if (normal != null) 552 { 553 if (normals == null) 554 { 555 normals = new List<InstancePersistenceEvent>(this.pendingOwnerEvents.Count); 556 } 557 normals.Add(normal); 558 } 559 } 560 561 this.pendingOwnerEvents = null; 562 563 if (normals != null && this.waitResult != null) 564 { 565 resultToComplete = this.waitResult; 566 this.waitResult = null; 567 } 568 } 569 570 return View; 571 } 572 } 573 finally 574 { 575 InstanceOwner.ResolveHandles(handlesPendingResolution); 576 577 // This is a convenience, it is not required for correctness. 578 if (handleToFree != null) 579 { 580 Fx.Assert(!object.ReferenceEquals(handleToFree, this), "Shouldn't have been told to free ourselves."); 581 handleToFree.Free(); 582 } 583 584 if (resultToComplete != null) 585 { 586 resultToComplete.Signaled(normals); 587 } 588 } 589 } 590 OnPrepare(PreparingEnlistment preparingEnlistment)591 void OnPrepare(PreparingEnlistment preparingEnlistment) 592 { 593 bool prepareNeeded = false; 594 lock (ThisLock) 595 { 596 if (TooLateToEnlist) 597 { 598 // Skip this if somehow we already got rolled back or committed. 599 return; 600 } 601 TooLateToEnlist = true; 602 if (OperationPending && AcquirePending == null) 603 { 604 Fx.Assert(CurrentExecutionContext != null, "Should either be acquiring or executing in Prepare."); 605 this.pendingPreparingEnlistment = preparingEnlistment; 606 } 607 else 608 { 609 prepareNeeded = true; 610 } 611 } 612 if (prepareNeeded) 613 { 614 preparingEnlistment.Prepared(); 615 } 616 } 617 OnRollBack(AcquireContextAsyncResult rollingBack)618 void OnRollBack(AcquireContextAsyncResult rollingBack) 619 { 620 bool rollbackNeeded = false; 621 lock (ThisLock) 622 { 623 TooLateToEnlist = true; 624 if (OperationPending && AcquirePending == null) 625 { 626 Fx.Assert(CurrentExecutionContext != null, "Should either be acquiring or executing in RollBack."); 627 this.pendingRollback = rollingBack; 628 629 // Don't prepare and roll back. 630 this.pendingPreparingEnlistment = null; 631 } 632 else 633 { 634 rollbackNeeded = true; 635 } 636 } 637 if (rollbackNeeded) 638 { 639 rollingBack.RollBack(); 640 } 641 } 642 FinishOperation()643 void FinishOperation() 644 { 645 List<InstanceHandleReference> handlesPendingResolution = null; 646 try 647 { 648 bool needNotification; 649 PreparingEnlistment preparingEnlistment; 650 AcquireContextAsyncResult pendingRollback; 651 lock (ThisLock) 652 { 653 OperationPending = false; 654 AcquirePending = null; 655 CurrentExecutionContext = null; 656 657 // This means we could have bound the handle, but didn't - clear the state here. 658 if (this.inProgressBind != null && (Version == -1 || !IsValid)) 659 { 660 Owner.CancelBind(ref this.inProgressBind, ref handlesPendingResolution); 661 } 662 else if (Version != -1 && !IsValid) 663 { 664 // This means the handle was successfully bound in the past. Need to remove it from the table of handles. 665 Owner.Unbind(this); 666 } 667 668 needNotification = this.needFreedNotification; 669 this.needFreedNotification = false; 670 671 preparingEnlistment = this.pendingPreparingEnlistment; 672 this.pendingPreparingEnlistment = null; 673 674 pendingRollback = this.pendingRollback; 675 this.pendingRollback = null; 676 } 677 try 678 { 679 if (needNotification) 680 { 681 Store.FreeInstanceHandle(this, ProviderObject); 682 } 683 } 684 finally 685 { 686 if (pendingRollback != null) 687 { 688 Fx.Assert(preparingEnlistment == null, "Should not have both."); 689 pendingRollback.RollBack(); 690 } 691 else if (preparingEnlistment != null) 692 { 693 preparingEnlistment.Prepared(); 694 } 695 } 696 } 697 finally 698 { 699 InstanceOwner.ResolveHandles(handlesPendingResolution); 700 } 701 } 702 StartWaiting(WaitForEventsAsyncResult result, IOThreadTimer timeoutTimer, TimeSpan timeout)703 List<InstancePersistenceEvent> StartWaiting(WaitForEventsAsyncResult result, IOThreadTimer timeoutTimer, TimeSpan timeout) 704 { 705 lock (ThisLock) 706 { 707 if (this.waitResult != null) 708 { 709 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.WaitAlreadyInProgress)); 710 } 711 if (!IsValid) 712 { 713 throw Fx.Exception.AsError(new OperationCanceledException(SRCore.HandleFreed)); 714 } 715 716 if (this.boundOwnerEvents != null && this.boundOwnerEvents.Count > 0) 717 { 718 Fx.Assert(Owner != null, "How do we have owner events without an owner."); 719 List<InstancePersistenceEvent> readyEvents = Store.SelectSignaledEvents(this.boundOwnerEvents, Owner); 720 if (readyEvents != null) 721 { 722 Fx.Assert(readyEvents.Count != 0, "Should not return a zero-length list."); 723 return readyEvents; 724 } 725 } 726 727 this.waitResult = result; 728 729 // This is done here to be under the lock. That way it doesn't get canceled before it is set. 730 if (timeoutTimer != null) 731 { 732 timeoutTimer.Set(timeout); 733 } 734 735 return null; 736 } 737 } 738 CancelWaiting(WaitForEventsAsyncResult result)739 bool CancelWaiting(WaitForEventsAsyncResult result) 740 { 741 lock (ThisLock) 742 { 743 Fx.Assert(result != null, "Null result passed to CancelWaiting."); 744 if (!object.ReferenceEquals(this.waitResult, result)) 745 { 746 return false; 747 } 748 this.waitResult = null; 749 return true; 750 } 751 } 752 EventReady(InstancePersistenceEvent persistenceEvent)753 internal void EventReady(InstancePersistenceEvent persistenceEvent) 754 { 755 WaitForEventsAsyncResult resultToComplete = null; 756 lock (ThisLock) 757 { 758 if (this.waitResult != null) 759 { 760 resultToComplete = this.waitResult; 761 this.waitResult = null; 762 } 763 } 764 765 if (resultToComplete != null) 766 { 767 resultToComplete.Signaled(persistenceEvent); 768 } 769 } 770 BeginWaitForEvents(InstanceHandle handle, TimeSpan timeout, AsyncCallback callback, object state)771 internal static IAsyncResult BeginWaitForEvents(InstanceHandle handle, TimeSpan timeout, AsyncCallback callback, object state) 772 { 773 return new WaitForEventsAsyncResult(handle, timeout, callback, state); 774 } 775 EndWaitForEvents(IAsyncResult result)776 internal static List<InstancePersistenceEvent> EndWaitForEvents(IAsyncResult result) 777 { 778 return WaitForEventsAsyncResult.End(result); 779 } 780 781 class AcquireContextAsyncResult : AsyncResult, IEnlistmentNotification 782 { 783 static Action<object, TimeoutException> onHostTransaction = new Action<object, TimeoutException>(OnHostTransaction); 784 785 readonly InstanceHandle handle; 786 readonly TimeoutHelper timeoutHelper; 787 788 InstancePersistenceContext executionContext; 789 AcquireContextAsyncResult(InstanceHandle handle, Transaction hostTransaction, TimeSpan timeout, out bool setOperationPending, AsyncCallback callback, object state)790 public AcquireContextAsyncResult(InstanceHandle handle, Transaction hostTransaction, TimeSpan timeout, out bool setOperationPending, AsyncCallback callback, object state) 791 : this(handle, hostTransaction, timeout, out setOperationPending, false, callback, state) 792 { 793 } 794 795 [Fx.Tag.Blocking(CancelMethod = "Free", CancelDeclaringType = typeof(InstanceHandle))] AcquireContextAsyncResult(InstanceHandle handle, Transaction hostTransaction, TimeSpan timeout, out bool setOperationPending)796 public AcquireContextAsyncResult(InstanceHandle handle, Transaction hostTransaction, TimeSpan timeout, out bool setOperationPending) 797 : this(handle, hostTransaction, timeout, out setOperationPending, true, null, null) 798 { 799 } 800 801 [Fx.Tag.Blocking(CancelMethod = "Free", CancelDeclaringType = typeof(InstanceHandle), Conditional = "synchronous")] AcquireContextAsyncResult(InstanceHandle handle, Transaction hostTransaction, TimeSpan timeout, out bool setOperationPending, bool synchronous, AsyncCallback callback, object state)802 AcquireContextAsyncResult(InstanceHandle handle, Transaction hostTransaction, TimeSpan timeout, out bool setOperationPending, bool synchronous, AsyncCallback callback, object state) 803 : base(callback, state) 804 { 805 // Need to report back to the caller whether or not we set OperationPending. 806 setOperationPending = false; 807 808 this.handle = handle; 809 HostTransaction = hostTransaction; 810 this.timeoutHelper = new TimeoutHelper(timeout); 811 812 AcquireContextAsyncResult transactionWait; 813 bool reuseContext = false; 814 lock (this.handle.ThisLock) 815 { 816 if (!this.handle.IsValid) 817 { 818 throw Fx.Exception.AsError(new OperationCanceledException(SRCore.HandleFreed)); 819 } 820 821 if (this.handle.OperationPending) 822 { 823 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.CommandExecutionCannotOverlap)); 824 } 825 setOperationPending = true; 826 this.handle.OperationPending = true; 827 828 transactionWait = this.handle.CurrentTransactionalAsyncResult; 829 if (transactionWait != null) 830 { 831 Fx.Assert(this.handle.AcquirePending == null, "Overlapped acquires pending."); 832 833 // If the transaction matches but is already completed (or completing), the easiest ting to do 834 // is wait for it to complete, then try to re-enlist, and have that failure be the failure mode for Execute. 835 // We do that by following the regular, non-matching transaction path. 836 if (transactionWait.HostTransaction.Equals(hostTransaction) && !this.handle.TooLateToEnlist) 837 { 838 reuseContext = true; 839 this.executionContext = transactionWait.ReuseContext(); 840 this.handle.CurrentExecutionContext = this.executionContext; 841 } 842 else 843 { 844 this.handle.AcquirePending = this; 845 } 846 } 847 } 848 849 if (transactionWait != null) 850 { 851 Fx.Assert(transactionWait.IsCompleted, "Old AsyncResult must be completed by now."); 852 853 // Reuse the existing InstanceExecutionContext if this is the same transaction we're waiting for. 854 if (reuseContext) 855 { 856 Complete(true); 857 return; 858 } 859 860 TimeSpan waitTimeout = this.timeoutHelper.RemainingTime(); 861 if (synchronous) 862 { 863 if (!transactionWait.WaitForHostTransaction.Wait(waitTimeout)) 864 { 865 throw Fx.Exception.AsError(new TimeoutException(InternalSR.TimeoutOnOperation(waitTimeout))); 866 } 867 } 868 else 869 { 870 if (!transactionWait.WaitForHostTransaction.WaitAsync(AcquireContextAsyncResult.onHostTransaction, this, waitTimeout)) 871 { 872 return; 873 } 874 } 875 } 876 877 if (DoAfterTransaction()) 878 { 879 Complete(true); 880 } 881 } 882 883 public Transaction HostTransaction { get; private set; } 884 public AsyncWaitHandle WaitForHostTransaction { get; private set; } 885 End(IAsyncResult result)886 public static InstancePersistenceContext End(IAsyncResult result) 887 { 888 AcquireContextAsyncResult pThis = AsyncResult.End<AcquireContextAsyncResult>(result); 889 Fx.Assert(pThis.executionContext != null, "Somehow the execution context didn't get set."); 890 return pThis.executionContext; 891 } 892 RollBack()893 internal void RollBack() 894 { 895 if (this.executionContext.IsHandleDoomedByRollback) 896 { 897 this.handle.Free(); 898 } 899 else 900 { 901 Fx.Assert(this.handle.inProgressBind == null, "Either this should have been bound to a lock, hence dooming the handle by rollback, or this should have been cancelled in FinishOperation."); 902 Fx.Assert(this.handle.pendingOwnerEvents == null, "Either this should have doomed the handle or already been committed."); 903 WaitForHostTransaction.Set(); 904 } 905 } 906 OnHostTransaction(object state, TimeoutException timeoutException)907 static void OnHostTransaction(object state, TimeoutException timeoutException) 908 { 909 AcquireContextAsyncResult pThis = (AcquireContextAsyncResult)state; 910 Exception exception = timeoutException; 911 bool completeSelf = exception != null; 912 if (!completeSelf) 913 { 914 try 915 { 916 if (pThis.DoAfterTransaction()) 917 { 918 completeSelf = true; 919 } 920 } 921 catch (Exception e) 922 { 923 if (Fx.IsFatal(e)) 924 { 925 throw; 926 } 927 exception = e; 928 completeSelf = true; 929 } 930 } 931 if (completeSelf) 932 { 933 if (exception != null) 934 { 935 pThis.handle.FinishOperation(); 936 } 937 pThis.Complete(false, exception); 938 } 939 } 940 DoAfterTransaction()941 bool DoAfterTransaction() 942 { 943 AcquireContextAsyncResult setWaitTo = null; 944 try 945 { 946 lock (this.handle.ThisLock) 947 { 948 if (!this.handle.IsValid) 949 { 950 throw Fx.Exception.AsError(new OperationCanceledException(SRCore.HandleFreed)); 951 } 952 953 if (HostTransaction == null) 954 { 955 this.executionContext = new InstancePersistenceContext(this.handle, this.timeoutHelper.RemainingTime()); 956 } 957 else 958 { 959 this.executionContext = new InstancePersistenceContext(this.handle, HostTransaction); 960 } 961 962 this.handle.AcquirePending = null; 963 this.handle.CurrentExecutionContext = this.executionContext; 964 this.handle.TooLateToEnlist = false; 965 } 966 967 if (HostTransaction != null) 968 { 969 WaitForHostTransaction = new AsyncWaitHandle(EventResetMode.ManualReset); 970 HostTransaction.EnlistVolatile(this, EnlistmentOptions.None); 971 setWaitTo = this; 972 } 973 } 974 finally 975 { 976 this.handle.CurrentTransactionalAsyncResult = setWaitTo; 977 } 978 979 return true; 980 } 981 ReuseContext()982 InstancePersistenceContext ReuseContext() 983 { 984 Fx.Assert(this.executionContext != null, "ReuseContext called but there is no context."); 985 986 this.executionContext.PrepareForReuse(); 987 return this.executionContext; 988 } 989 IEnlistmentNotification.Commit(Enlistment enlistment)990 void IEnlistmentNotification.Commit(Enlistment enlistment) 991 { 992 Fx.AssertAndThrow(this.handle.CurrentExecutionContext == null, "Prepare should have been called first and waited until after command processing."); 993 994 bool commitSuccessful = this.handle.Commit(this.executionContext.InstanceView) != null; 995 enlistment.Done(); 996 if (commitSuccessful) 997 { 998 WaitForHostTransaction.Set(); 999 } 1000 else 1001 { 1002 this.handle.Free(); 1003 } 1004 } 1005 IEnlistmentNotification.InDoubt(Enlistment enlistment)1006 void IEnlistmentNotification.InDoubt(Enlistment enlistment) 1007 { 1008 enlistment.Done(); 1009 this.handle.Free(); 1010 } 1011 IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)1012 void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment) 1013 { 1014 this.handle.OnPrepare(preparingEnlistment); 1015 } 1016 IEnlistmentNotification.Rollback(Enlistment enlistment)1017 void IEnlistmentNotification.Rollback(Enlistment enlistment) 1018 { 1019 enlistment.Done(); 1020 this.handle.OnRollBack(this); 1021 } 1022 } 1023 1024 class WaitForEventsAsyncResult : AsyncResult 1025 { 1026 static readonly Action<object> timeoutCallback = new Action<object>(OnTimeout); 1027 1028 readonly InstanceHandle handle; 1029 readonly TimeSpan timeout; 1030 1031 IOThreadTimer timer; 1032 1033 List<InstancePersistenceEvent> readyEvents; 1034 WaitForEventsAsyncResult(InstanceHandle handle, TimeSpan timeout, AsyncCallback callback, object state)1035 internal WaitForEventsAsyncResult(InstanceHandle handle, TimeSpan timeout, AsyncCallback callback, object state) 1036 : base(callback, state) 1037 { 1038 this.handle = handle; 1039 this.timeout = timeout; 1040 1041 if (this.timeout != TimeSpan.Zero && this.timeout != TimeSpan.MaxValue) 1042 { 1043 this.timer = new IOThreadTimer(WaitForEventsAsyncResult.timeoutCallback, this, false); 1044 } 1045 1046 List<InstancePersistenceEvent> existingReadyEvents = this.handle.StartWaiting(this, this.timer, this.timeout); 1047 if (existingReadyEvents == null) 1048 { 1049 if (this.timeout == TimeSpan.Zero) 1050 { 1051 this.handle.CancelWaiting(this); 1052 throw Fx.Exception.AsError(new TimeoutException(SRCore.WaitForEventsTimedOut(TimeSpan.Zero))); 1053 } 1054 } 1055 else 1056 { 1057 this.readyEvents = existingReadyEvents; 1058 Complete(true); 1059 } 1060 } 1061 Signaled(InstancePersistenceEvent persistenceEvent)1062 internal void Signaled(InstancePersistenceEvent persistenceEvent) 1063 { 1064 Signaled(new List<InstancePersistenceEvent>(1) { persistenceEvent }); 1065 } 1066 Signaled(List<InstancePersistenceEvent> persistenceEvents)1067 internal void Signaled(List<InstancePersistenceEvent> persistenceEvents) 1068 { 1069 if (this.timer != null) 1070 { 1071 this.timer.Cancel(); 1072 } 1073 this.readyEvents = persistenceEvents; 1074 Complete(false); 1075 } 1076 Canceled()1077 internal void Canceled() 1078 { 1079 if (this.timer != null) 1080 { 1081 this.timer.Cancel(); 1082 } 1083 Complete(false, new OperationCanceledException(SRCore.HandleFreed)); 1084 } 1085 OnTimeout(object state)1086 static void OnTimeout(object state) 1087 { 1088 WaitForEventsAsyncResult thisPtr = (WaitForEventsAsyncResult)state; 1089 if (thisPtr.handle.CancelWaiting(thisPtr)) 1090 { 1091 thisPtr.Complete(false, new TimeoutException(SRCore.WaitForEventsTimedOut(thisPtr.timeout))); 1092 } 1093 } 1094 End(IAsyncResult result)1095 internal static List<InstancePersistenceEvent> End(IAsyncResult result) 1096 { 1097 return AsyncResult.End<WaitForEventsAsyncResult>(result).readyEvents; 1098 } 1099 } 1100 } 1101 } 1102