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