Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Runtime.DurableInstancing / System / Runtime / DurableInstancing / InstanceHandle.cs
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
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
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
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
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
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
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
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
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.
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).
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.
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")]
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
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")]
491         internal InstancePersistenceContext EndAcquireExecutionContext(IAsyncResult result)
492         {
493             return AcquireContextAsyncResult.End(result);
494         }
495
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.
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
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
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
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
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
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
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
771         internal static IAsyncResult BeginWaitForEvents(InstanceHandle handle, TimeSpan timeout, AsyncCallback callback, object state)
772         {
773             return new WaitForEventsAsyncResult(handle, timeout, callback, state);
774         }
775
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
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))]
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")]
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
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
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
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
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
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
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
1006             void IEnlistmentNotification.InDoubt(Enlistment enlistment)
1007             {
1008                 enlistment.Done();
1009                 this.handle.Free();
1010             }
1011
1012             void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)
1013             {
1014                 this.handle.OnPrepare(preparingEnlistment);
1015             }
1016
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
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
1062             internal void Signaled(InstancePersistenceEvent persistenceEvent)
1063             {
1064                 Signaled(new List<InstancePersistenceEvent>(1) { persistenceEvent });
1065             }
1066
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
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
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
1095             internal static List<InstancePersistenceEvent> End(IAsyncResult result)
1096             {
1097                 return AsyncResult.End<WaitForEventsAsyncResult>(result).readyEvents;
1098             }
1099         }
1100     }
1101 }