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.Collections.ObjectModel;
9     using System.Runtime.Serialization;
10     using System.Security;
11     using System.Threading;
12     using System.Transactions;
13     using System.Xml.Linq;
14     using System.Runtime.Diagnostics;
15 
16     [Fx.Tag.XamlVisible(false)]
17     public sealed class InstancePersistenceContext
18     {
19         readonly TimeSpan timeout;
20 
21         Transaction transaction;
22         bool freezeTransaction;
23         CommittableTransaction myTransaction;
24         int cancellationHandlerCalled;
25         EventTraceActivity eventTraceActivity;
26 
InstancePersistenceContext(InstanceHandle handle, Transaction transaction)27         internal InstancePersistenceContext(InstanceHandle handle, Transaction transaction)
28             : this(handle)
29         {
30             Fx.Assert(transaction != null, "Null Transaction passed to InstancePersistenceContext.");
31 
32             // Let's take our own clone of the transaction. We need to do this because we might need to
33             // create a TransactionScope using the transaction and in cases where we are dealing with a
34             // transaction that is flowed into the workflow on a message, the DependentTransaction that the
35             // dispatcher creates and sets to Transaction.Current may already be Completed by the time a
36             // Save operation is done. And since TransactionScope creates a DependentTransaction, it won't
37             // be able to.
38             // We don't create another DependentClone because we are going to do a EnlistVolatile on the
39             // transaction ourselves.
40             this.transaction = transaction.Clone();
41             IsHostTransaction = true;
42             this.eventTraceActivity = handle.EventTraceActivity;
43         }
44 
InstancePersistenceContext(InstanceHandle handle, TimeSpan timeout)45         internal InstancePersistenceContext(InstanceHandle handle, TimeSpan timeout)
46             : this(handle)
47         {
48             this.timeout = timeout;
49         }
50 
InstancePersistenceContext(InstanceHandle handle)51         InstancePersistenceContext(InstanceHandle handle)
52         {
53             Fx.Assert(handle != null, "Null handle passed to InstancePersistenceContext.");
54 
55             InstanceHandle = handle;
56 
57             // Fork a copy of the current view to be the new working view. It starts with no query results.
58             InstanceView newView = handle.View.Clone();
59             newView.InstanceStoreQueryResults = null;
60             InstanceView = newView;
61 
62             this.cancellationHandlerCalled = 0;
63         }
64 
65         public InstanceHandle InstanceHandle { get; private set; }
66         public InstanceView InstanceView { get; private set; }
67 
68         public long InstanceVersion
69         {
70             get
71             {
72                 return InstanceHandle.Version;
73             }
74         }
75 
76         internal EventTraceActivity EventTraceActivity
77         {
78             get
79             {
80                 return this.eventTraceActivity;
81             }
82         }
83 
84         public Guid LockToken
85         {
86             get
87             {
88                 Fx.Assert(InstanceHandle.Owner == null || InstanceHandle.Owner.OwnerToken == InstanceView.InstanceOwner.OwnerToken, "Mismatched lock tokens.");
89 
90                 // If the handle doesn't own the lock yet, return the owner LockToken, which is needed to check whether this owner already owns locks.
91                 return InstanceHandle.Owner == null ? Guid.Empty : InstanceHandle.Owner.OwnerToken;
92             }
93         }
94 
95         public object UserContext
96         {
97             get
98             {
99                 return InstanceHandle.ProviderObject;
100             }
101         }
102 
103         bool CancelRequested { get; set; }
104 
105         ExecuteAsyncResult RootAsyncResult { get; set; }
106         ExecuteAsyncResult LastAsyncResult { get; set; }
107         bool IsHostTransaction { get; set; }
108 
109         bool Active
110         {
111             get
112             {
113                 return RootAsyncResult != null;
114             }
115         }
116 
SetCancellationHandler(Action<InstancePersistenceContext> cancellationHandler)117         public void SetCancellationHandler(Action<InstancePersistenceContext> cancellationHandler)
118         {
119             ThrowIfNotActive("SetCancellationHandler");
120             LastAsyncResult.CancellationHandler = cancellationHandler;
121             if (CancelRequested && (cancellationHandler != null))
122             {
123                 try
124                 {
125                     if (Interlocked.CompareExchange(ref this.cancellationHandlerCalled, 0, 1) == 0)
126                     {
127                         cancellationHandler(this);
128                     }
129                 }
130                 catch (Exception exception)
131                 {
132                     if (Fx.IsFatal(exception))
133                     {
134                         throw;
135                     }
136                     throw Fx.Exception.AsError(new CallbackException(SRCore.OnCancelRequestedThrew, exception));
137                 }
138             }
139         }
140 
BindInstanceOwner(Guid instanceOwnerId, Guid lockToken)141         public void BindInstanceOwner(Guid instanceOwnerId, Guid lockToken)
142         {
143             if (instanceOwnerId == Guid.Empty)
144             {
145                 throw Fx.Exception.Argument("instanceOwnerId", SRCore.GuidCannotBeEmpty);
146             }
147             if (lockToken == Guid.Empty)
148             {
149                 throw Fx.Exception.Argument("lockToken", SRCore.GuidCannotBeEmpty);
150             }
151             ThrowIfNotActive("BindInstanceOwner");
152 
153             InstanceOwner owner = InstanceHandle.Store.GetOrCreateOwner(instanceOwnerId, lockToken);
154 
155             InstanceView.BindOwner(owner);
156             IsHandleDoomedByRollback = true;
157 
158             InstanceHandle.BindOwner(owner);
159         }
160 
BindInstance(Guid instanceId)161         public void BindInstance(Guid instanceId)
162         {
163             if (instanceId == Guid.Empty)
164             {
165                 throw Fx.Exception.Argument("instanceId", SRCore.GuidCannotBeEmpty);
166             }
167             ThrowIfNotActive("BindInstance");
168 
169             InstanceView.BindInstance(instanceId);
170             IsHandleDoomedByRollback = true;
171 
172             InstanceHandle.BindInstance(instanceId);
173         }
174 
BindEvent(InstancePersistenceEvent persistenceEvent)175         public void BindEvent(InstancePersistenceEvent persistenceEvent)
176         {
177             if (persistenceEvent == null)
178             {
179                 throw Fx.Exception.ArgumentNull("persistenceEvent");
180             }
181             ThrowIfNotActive("BindEvent");
182 
183             if (!InstanceView.IsBoundToInstanceOwner)
184             {
185                 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.ContextMustBeBoundToOwner));
186             }
187             IsHandleDoomedByRollback = true;
188 
189             InstanceHandle.BindOwnerEvent(persistenceEvent);
190         }
191 
BindAcquiredLock(long instanceVersion)192         public void BindAcquiredLock(long instanceVersion)
193         {
194             if (instanceVersion < 0)
195             {
196                 throw Fx.Exception.ArgumentOutOfRange("instanceVersion", instanceVersion, SRCore.InvalidLockToken);
197             }
198             ThrowIfNotActive("BindAcquiredLock");
199 
200             // This call has a synchronization, so we are guaranteed it is only successful once.
201             InstanceView.BindLock(instanceVersion);
202             IsHandleDoomedByRollback = true;
203 
204             InstanceHandle.Bind(instanceVersion);
205         }
206 
BindReclaimedLock(long instanceVersion, TimeSpan timeout)207         public void BindReclaimedLock(long instanceVersion, TimeSpan timeout)
208         {
209             AsyncWaitHandle wait = InitiateBindReclaimedLockHelper("BindReclaimedLock", instanceVersion, timeout);
210             if (!wait.Wait(timeout))
211             {
212                 InstanceHandle.CancelReclaim(new TimeoutException(SRCore.TimedOutWaitingForLockResolution));
213             }
214             ConcludeBindReclaimedLockHelper();
215         }
216 
BeginBindReclaimedLock(long instanceVersion, TimeSpan timeout, AsyncCallback callback, object state)217         public IAsyncResult BeginBindReclaimedLock(long instanceVersion, TimeSpan timeout, AsyncCallback callback, object state)
218         {
219             AsyncWaitHandle wait = InitiateBindReclaimedLockHelper("BeginBindReclaimedLock", instanceVersion, timeout);
220             return new BindReclaimedLockAsyncResult(this, wait, timeout, callback, state);
221         }
222 
EndBindReclaimedLock(IAsyncResult result)223         public void EndBindReclaimedLock(IAsyncResult result)
224         {
225             BindReclaimedLockAsyncResult.End(result);
226         }
227 
CreateBindReclaimedLockException(long instanceVersion)228         public Exception CreateBindReclaimedLockException(long instanceVersion)
229         {
230             AsyncWaitHandle wait = InitiateBindReclaimedLockHelper("CreateBindReclaimedLockException", instanceVersion, TimeSpan.MaxValue);
231             return new BindReclaimedLockException(wait);
232         }
233 
InitiateBindReclaimedLockHelper(string methodName, long instanceVersion, TimeSpan timeout)234         AsyncWaitHandle InitiateBindReclaimedLockHelper(string methodName, long instanceVersion, TimeSpan timeout)
235         {
236             if (instanceVersion < 0)
237             {
238                 throw Fx.Exception.ArgumentOutOfRange("instanceVersion", instanceVersion, SRCore.InvalidLockToken);
239             }
240             TimeoutHelper.ThrowIfNegativeArgument(timeout);
241             ThrowIfNotActive(methodName);
242 
243             // This call has a synchronization, so we are guaranteed it is only successful once.
244             InstanceView.StartBindLock(instanceVersion);
245             IsHandleDoomedByRollback = true;
246 
247             AsyncWaitHandle wait = InstanceHandle.StartReclaim(instanceVersion);
248             if (wait == null)
249             {
250                 InstanceHandle.Free();
251                 throw Fx.Exception.AsError(new InstanceHandleConflictException(LastAsyncResult.CurrentCommand.Name, InstanceView.InstanceId));
252             }
253             return wait;
254         }
255 
ConcludeBindReclaimedLockHelper()256         void ConcludeBindReclaimedLockHelper()
257         {
258             // If FinishReclaim doesn't throw an exception, we are done - the reclaim was successful.
259             // The Try / Finally makes up for the reverse order of setting the handle, then the view.
260             long instanceVersion = -1;
261             try
262             {
263                 if (!InstanceHandle.FinishReclaim(ref instanceVersion))
264                 {
265                     InstanceHandle.Free();
266                     throw Fx.Exception.AsError(new InstanceHandleConflictException(LastAsyncResult.CurrentCommand.Name, InstanceView.InstanceId));
267                 }
268                 Fx.Assert(instanceVersion >= 0, "Where did the instance version go?");
269             }
270             finally
271             {
272                 if (instanceVersion >= 0)
273                 {
274                     InstanceView.FinishBindLock(instanceVersion);
275                 }
276             }
277         }
278 
PersistedInstance(IDictionary<XName, InstanceValue> data)279         public void PersistedInstance(IDictionary<XName, InstanceValue> data)
280         {
281             ThrowIfNotLocked();
282             ThrowIfCompleted();
283             ThrowIfNotTransactional("PersistedInstance");
284 
285             InstanceView.InstanceData = data.ReadOnlyCopy(true);
286             InstanceView.InstanceDataConsistency = InstanceValueConsistency.None;
287             InstanceView.InstanceState = InstanceState.Initialized;
288         }
289 
LoadedInstance(InstanceState state, IDictionary<XName, InstanceValue> instanceData, IDictionary<XName, InstanceValue> instanceMetadata, IDictionary<Guid, IDictionary<XName, InstanceValue>> associatedInstanceKeyMetadata, IDictionary<Guid, IDictionary<XName, InstanceValue>> completedInstanceKeyMetadata)290         public void LoadedInstance(InstanceState state, IDictionary<XName, InstanceValue> instanceData, IDictionary<XName, InstanceValue> instanceMetadata, IDictionary<Guid, IDictionary<XName, InstanceValue>> associatedInstanceKeyMetadata, IDictionary<Guid, IDictionary<XName, InstanceValue>> completedInstanceKeyMetadata)
291         {
292             if (state == InstanceState.Uninitialized)
293             {
294                 if (instanceData != null && instanceData.Count > 0)
295                 {
296                     throw Fx.Exception.AsError(new InvalidOperationException(SRCore.UninitializedCannotHaveData));
297                 }
298             }
299             else if (state == InstanceState.Completed)
300             {
301                 if (associatedInstanceKeyMetadata != null && associatedInstanceKeyMetadata.Count > 0)
302                 {
303                     throw Fx.Exception.AsError(new InvalidOperationException(SRCore.CompletedMustNotHaveAssociatedKeys));
304                 }
305             }
306             else if (state != InstanceState.Initialized)
307             {
308                 throw Fx.Exception.Argument("state", SRCore.InvalidInstanceState);
309             }
310             ThrowIfNoInstance();
311             ThrowIfNotActive("PersistedInstance");
312 
313             InstanceValueConsistency consistency = InstanceView.IsBoundToLock || state == InstanceState.Completed ? InstanceValueConsistency.None : InstanceValueConsistency.InDoubt;
314 
315             ReadOnlyDictionaryInternal<XName, InstanceValue> instanceDataCopy = instanceData.ReadOnlyCopy(false);
316             ReadOnlyDictionaryInternal<XName, InstanceValue> instanceMetadataCopy = instanceMetadata.ReadOnlyCopy(false);
317 
318             Dictionary<Guid, InstanceKeyView> keysCopy = null;
319             int totalKeys = (associatedInstanceKeyMetadata != null ? associatedInstanceKeyMetadata.Count : 0) + (completedInstanceKeyMetadata != null ? completedInstanceKeyMetadata.Count : 0);
320             if (totalKeys > 0)
321             {
322                 keysCopy = new Dictionary<Guid, InstanceKeyView>(totalKeys);
323             }
324             if (associatedInstanceKeyMetadata != null && associatedInstanceKeyMetadata.Count > 0)
325             {
326                 foreach (KeyValuePair<Guid, IDictionary<XName, InstanceValue>> keyMetadata in associatedInstanceKeyMetadata)
327                 {
328                     InstanceKeyView view = new InstanceKeyView(keyMetadata.Key);
329                     view.InstanceKeyState = InstanceKeyState.Associated;
330                     view.InstanceKeyMetadata = keyMetadata.Value.ReadOnlyCopy(false);
331                     view.InstanceKeyMetadataConsistency = InstanceView.IsBoundToLock ? InstanceValueConsistency.None : InstanceValueConsistency.InDoubt;
332                     keysCopy.Add(view.InstanceKey, view);
333                 }
334             }
335 
336             if (completedInstanceKeyMetadata != null && completedInstanceKeyMetadata.Count > 0)
337             {
338                 foreach (KeyValuePair<Guid, IDictionary<XName, InstanceValue>> keyMetadata in completedInstanceKeyMetadata)
339                 {
340                     InstanceKeyView view = new InstanceKeyView(keyMetadata.Key);
341                     view.InstanceKeyState = InstanceKeyState.Completed;
342                     view.InstanceKeyMetadata = keyMetadata.Value.ReadOnlyCopy(false);
343                     view.InstanceKeyMetadataConsistency = consistency;
344                     keysCopy.Add(view.InstanceKey, view);
345                 }
346             }
347 
348             InstanceView.InstanceState = state;
349 
350             InstanceView.InstanceData = instanceDataCopy;
351             InstanceView.InstanceDataConsistency = consistency;
352 
353             InstanceView.InstanceMetadata = instanceMetadataCopy;
354             InstanceView.InstanceMetadataConsistency = consistency;
355 
356             InstanceView.InstanceKeys = keysCopy == null ? null : new ReadOnlyDictionaryInternal<Guid, InstanceKeyView>(keysCopy);
357             InstanceView.InstanceKeysConsistency = consistency;
358         }
359 
CompletedInstance()360         public void CompletedInstance()
361         {
362             ThrowIfNotLocked();
363             ThrowIfUninitialized();
364             ThrowIfCompleted();
365             if ((InstanceView.InstanceKeysConsistency & InstanceValueConsistency.InDoubt) == 0)
366             {
367                 foreach (KeyValuePair<Guid, InstanceKeyView> key in InstanceView.InstanceKeys)
368                 {
369                     if (key.Value.InstanceKeyState == InstanceKeyState.Associated)
370                     {
371                         throw Fx.Exception.AsError(new InvalidOperationException(SRCore.CannotCompleteWithKeys));
372                     }
373                 }
374             }
375             ThrowIfNotTransactional("CompletedInstance");
376 
377             InstanceView.InstanceState = InstanceState.Completed;
378         }
379 
ReadInstanceMetadata(IDictionary<XName, InstanceValue> metadata, bool complete)380         public void ReadInstanceMetadata(IDictionary<XName, InstanceValue> metadata, bool complete)
381         {
382             ThrowIfNoInstance();
383             ThrowIfNotActive("ReadInstanceMetadata");
384 
385             if (InstanceView.InstanceMetadataConsistency == InstanceValueConsistency.None)
386             {
387                 return;
388             }
389 
390             if (complete)
391             {
392                 InstanceView.InstanceMetadata = metadata.ReadOnlyCopy(false);
393                 InstanceView.InstanceMetadataConsistency = InstanceView.IsBoundToLock || InstanceView.InstanceState == InstanceState.Completed ? InstanceValueConsistency.None : InstanceValueConsistency.InDoubt;
394             }
395             else
396             {
397                 if ((InstanceView.IsBoundToLock || InstanceView.InstanceState == InstanceState.Completed) && (InstanceView.InstanceMetadataConsistency & InstanceValueConsistency.InDoubt) != 0)
398                 {
399                     // In this case, prefer throwing out old data and keeping only authoritative data.
400                     InstanceView.InstanceMetadata = metadata.ReadOnlyMergeInto(null, false);
401                     InstanceView.InstanceMetadataConsistency = InstanceValueConsistency.Partial;
402                 }
403                 else
404                 {
405                     InstanceView.InstanceMetadata = metadata.ReadOnlyMergeInto(InstanceView.InstanceMetadata, false);
406                     InstanceView.InstanceMetadataConsistency |= InstanceValueConsistency.Partial;
407                 }
408             }
409         }
410 
WroteInstanceMetadataValue(XName name, InstanceValue value)411         public void WroteInstanceMetadataValue(XName name, InstanceValue value)
412         {
413             if (name == null)
414             {
415                 throw Fx.Exception.ArgumentNull("name");
416             }
417             if (value == null)
418             {
419                 throw Fx.Exception.ArgumentNull("value");
420             }
421             ThrowIfNotLocked();
422             ThrowIfCompleted();
423             ThrowIfNotTransactional("WroteInstanceMetadataValue");
424 
425             InstanceView.AccumulatedMetadataWrites[name] = value;
426         }
427 
AssociatedInstanceKey(Guid key)428         public void AssociatedInstanceKey(Guid key)
429         {
430             if (key == Guid.Empty)
431             {
432                 throw Fx.Exception.Argument("key", SRCore.InvalidKeyArgument);
433             }
434             ThrowIfNotLocked();
435             ThrowIfCompleted();
436             ThrowIfNotTransactional("AssociatedInstanceKey");
437 
438             Dictionary<Guid, InstanceKeyView> copy = new Dictionary<Guid, InstanceKeyView>(InstanceView.InstanceKeys);
439             if ((InstanceView.InstanceKeysConsistency & InstanceValueConsistency.InDoubt) == 0 && copy.ContainsKey(key))
440             {
441                 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.KeyAlreadyAssociated));
442             }
443             InstanceKeyView keyView = new InstanceKeyView(key);
444             keyView.InstanceKeyState = InstanceKeyState.Associated;
445             keyView.InstanceKeyMetadataConsistency = InstanceValueConsistency.None;
446             copy[keyView.InstanceKey] = keyView;
447             InstanceView.InstanceKeys = new ReadOnlyDictionaryInternal<Guid, InstanceKeyView>(copy);
448         }
449 
CompletedInstanceKey(Guid key)450         public void CompletedInstanceKey(Guid key)
451         {
452             if (key == Guid.Empty)
453             {
454                 throw Fx.Exception.Argument("key", SRCore.InvalidKeyArgument);
455             }
456             ThrowIfNotLocked();
457             ThrowIfCompleted();
458             ThrowIfNotTransactional("CompletedInstanceKey");
459 
460             InstanceKeyView existingKeyView;
461             InstanceView.InstanceKeys.TryGetValue(key, out existingKeyView);
462             if ((InstanceView.InstanceKeysConsistency & InstanceValueConsistency.InDoubt) == 0)
463             {
464                 if (existingKeyView != null)
465                 {
466                     if (existingKeyView.InstanceKeyState == InstanceKeyState.Completed)
467                     {
468                         throw Fx.Exception.AsError(new InvalidOperationException(SRCore.KeyAlreadyCompleted));
469                     }
470                 }
471                 else if ((InstanceView.InstanceKeysConsistency & InstanceValueConsistency.Partial) == 0)
472                 {
473                     throw Fx.Exception.AsError(new InvalidOperationException(SRCore.KeyNotAssociated));
474                 }
475             }
476 
477             if (existingKeyView != null)
478             {
479                 existingKeyView.InstanceKeyState = InstanceKeyState.Completed;
480             }
481             else
482             {
483                 Dictionary<Guid, InstanceKeyView> copy = new Dictionary<Guid, InstanceKeyView>(InstanceView.InstanceKeys);
484                 InstanceKeyView keyView = new InstanceKeyView(key);
485                 keyView.InstanceKeyState = InstanceKeyState.Completed;
486                 keyView.InstanceKeyMetadataConsistency = InstanceValueConsistency.Partial;
487                 copy[keyView.InstanceKey] = keyView;
488                 InstanceView.InstanceKeys = new ReadOnlyDictionaryInternal<Guid, InstanceKeyView>(copy);
489             }
490         }
491 
UnassociatedInstanceKey(Guid key)492         public void UnassociatedInstanceKey(Guid key)
493         {
494             if (key == Guid.Empty)
495             {
496                 throw Fx.Exception.Argument("key", SRCore.InvalidKeyArgument);
497             }
498             ThrowIfNotLocked();
499             ThrowIfCompleted();
500             ThrowIfNotTransactional("UnassociatedInstanceKey");
501 
502             InstanceKeyView existingKeyView;
503             InstanceView.InstanceKeys.TryGetValue(key, out existingKeyView);
504             if ((InstanceView.InstanceKeysConsistency & InstanceValueConsistency.InDoubt) == 0)
505             {
506                 if (existingKeyView != null)
507                 {
508                     if (existingKeyView.InstanceKeyState == InstanceKeyState.Associated)
509                     {
510                         throw Fx.Exception.AsError(new InvalidOperationException(SRCore.KeyNotCompleted));
511                     }
512                 }
513                 else if ((InstanceView.InstanceKeysConsistency & InstanceValueConsistency.Partial) == 0)
514                 {
515                     throw Fx.Exception.AsError(new InvalidOperationException(SRCore.KeyAlreadyUnassociated));
516                 }
517             }
518 
519             if (existingKeyView != null)
520             {
521                 Dictionary<Guid, InstanceKeyView> copy = new Dictionary<Guid, InstanceKeyView>(InstanceView.InstanceKeys);
522                 copy.Remove(key);
523                 InstanceView.InstanceKeys = new ReadOnlyDictionaryInternal<Guid, InstanceKeyView>(copy);
524             }
525         }
526 
ReadInstanceKeyMetadata(Guid key, IDictionary<XName, InstanceValue> metadata, bool complete)527         public void ReadInstanceKeyMetadata(Guid key, IDictionary<XName, InstanceValue> metadata, bool complete)
528         {
529             if (key == Guid.Empty)
530             {
531                 throw Fx.Exception.Argument("key", SRCore.InvalidKeyArgument);
532             }
533             ThrowIfNoInstance();
534             ThrowIfNotActive("ReadInstanceKeyMetadata");
535 
536             InstanceKeyView keyView;
537             if (!InstanceView.InstanceKeys.TryGetValue(key, out keyView))
538             {
539                 if (InstanceView.InstanceKeysConsistency == InstanceValueConsistency.None)
540                 {
541                     throw Fx.Exception.AsError(new InvalidOperationException(SRCore.KeyNotAssociated));
542                 }
543 
544                 Dictionary<Guid, InstanceKeyView> copy = new Dictionary<Guid, InstanceKeyView>(InstanceView.InstanceKeys);
545                 keyView = new InstanceKeyView(key);
546                 if (complete)
547                 {
548                     keyView.InstanceKeyMetadata = metadata.ReadOnlyCopy(false);
549                     keyView.InstanceKeyMetadataConsistency = InstanceValueConsistency.None;
550                 }
551                 else
552                 {
553                     keyView.InstanceKeyMetadata = metadata.ReadOnlyMergeInto(null, false);
554                     keyView.InstanceKeyMetadataConsistency = InstanceValueConsistency.Partial;
555                 }
556                 if (!InstanceView.IsBoundToLock && InstanceView.InstanceState != InstanceState.Completed)
557                 {
558                     keyView.InstanceKeyMetadataConsistency |= InstanceValueConsistency.InDoubt;
559                 }
560                 copy[keyView.InstanceKey] = keyView;
561                 InstanceView.InstanceKeys = new ReadOnlyDictionaryInternal<Guid, InstanceKeyView>(copy);
562             }
563             else
564             {
565                 if (keyView.InstanceKeyMetadataConsistency == InstanceValueConsistency.None)
566                 {
567                     return;
568                 }
569 
570                 if (complete)
571                 {
572                     keyView.InstanceKeyMetadata = metadata.ReadOnlyCopy(false);
573                     keyView.InstanceKeyMetadataConsistency = InstanceView.IsBoundToLock || InstanceView.InstanceState == InstanceState.Completed ? InstanceValueConsistency.None : InstanceValueConsistency.InDoubt;
574                 }
575                 else
576                 {
577                     if ((InstanceView.IsBoundToLock || InstanceView.InstanceState == InstanceState.Completed) && (keyView.InstanceKeyMetadataConsistency & InstanceValueConsistency.InDoubt) != 0)
578                     {
579                         // In this case, prefer throwing out old data and keeping only authoritative data.
580                         keyView.InstanceKeyMetadata = metadata.ReadOnlyMergeInto(null, false);
581                         keyView.InstanceKeyMetadataConsistency = InstanceValueConsistency.Partial;
582                     }
583                     else
584                     {
585                         keyView.InstanceKeyMetadata = metadata.ReadOnlyMergeInto(keyView.InstanceKeyMetadata, false);
586                         keyView.InstanceKeyMetadataConsistency |= InstanceValueConsistency.Partial;
587                     }
588                 }
589             }
590         }
591 
WroteInstanceKeyMetadataValue(Guid key, XName name, InstanceValue value)592         public void WroteInstanceKeyMetadataValue(Guid key, XName name, InstanceValue value)
593         {
594             if (key == Guid.Empty)
595             {
596                 throw Fx.Exception.Argument("key", SRCore.InvalidKeyArgument);
597             }
598             if (name == null)
599             {
600                 throw Fx.Exception.ArgumentNull("name");
601             }
602             if (value == null)
603             {
604                 throw Fx.Exception.ArgumentNull("value");
605             }
606             ThrowIfNotLocked();
607             ThrowIfCompleted();
608             ThrowIfNotTransactional("WroteInstanceKeyMetadataValue");
609 
610             InstanceKeyView keyView;
611             if (!InstanceView.InstanceKeys.TryGetValue(key, out keyView))
612             {
613                 if (InstanceView.InstanceKeysConsistency == InstanceValueConsistency.None)
614                 {
615                     throw Fx.Exception.AsError(new InvalidOperationException(SRCore.KeyNotAssociated));
616                 }
617 
618                 if (!value.IsWriteOnly() && !value.IsDeletedValue)
619                 {
620                     Dictionary<Guid, InstanceKeyView> copy = new Dictionary<Guid, InstanceKeyView>(InstanceView.InstanceKeys);
621                     keyView = new InstanceKeyView(key);
622                     keyView.AccumulatedMetadataWrites.Add(name, value);
623                     keyView.InstanceKeyMetadataConsistency = InstanceValueConsistency.Partial;
624                     copy[keyView.InstanceKey] = keyView;
625                     InstanceView.InstanceKeys = new ReadOnlyDictionaryInternal<Guid, InstanceKeyView>(copy);
626                     InstanceView.InstanceKeysConsistency |= InstanceValueConsistency.Partial;
627                 }
628             }
629             else
630             {
631                 keyView.AccumulatedMetadataWrites.Add(name, value);
632             }
633         }
634 
ReadInstanceOwnerMetadata(IDictionary<XName, InstanceValue> metadata, bool complete)635         public void ReadInstanceOwnerMetadata(IDictionary<XName, InstanceValue> metadata, bool complete)
636         {
637             ThrowIfNoOwner();
638             ThrowIfNotActive("ReadInstanceOwnerMetadata");
639 
640             if (InstanceView.InstanceOwnerMetadataConsistency == InstanceValueConsistency.None)
641             {
642                 return;
643             }
644 
645             if (complete)
646             {
647                 InstanceView.InstanceOwnerMetadata = metadata.ReadOnlyCopy(false);
648                 InstanceView.InstanceOwnerMetadataConsistency = InstanceValueConsistency.InDoubt;
649             }
650             else
651             {
652                 InstanceView.InstanceOwnerMetadata = metadata.ReadOnlyMergeInto(InstanceView.InstanceOwnerMetadata, false);
653                 InstanceView.InstanceOwnerMetadataConsistency |= InstanceValueConsistency.Partial;
654             }
655         }
656 
WroteInstanceOwnerMetadataValue(XName name, InstanceValue value)657         public void WroteInstanceOwnerMetadataValue(XName name, InstanceValue value)
658         {
659             if (name == null)
660             {
661                 throw Fx.Exception.ArgumentNull("name");
662             }
663             if (value == null)
664             {
665                 throw Fx.Exception.ArgumentNull("value");
666             }
667             ThrowIfNoOwner();
668             ThrowIfNotTransactional("WroteInstanceOwnerMetadataValue");
669 
670             InstanceView.AccumulatedOwnerMetadataWrites.Add(name, value);
671         }
672 
QueriedInstanceStore(InstanceStoreQueryResult queryResult)673         public void QueriedInstanceStore(InstanceStoreQueryResult queryResult)
674         {
675             if (queryResult == null)
676             {
677                 throw Fx.Exception.ArgumentNull("queryResult");
678             }
679             ThrowIfNotActive("QueriedInstanceStore");
680 
681             InstanceView.QueryResultsBacking.Add(queryResult);
682         }
683 
684         [Fx.Tag.Throws.Timeout("The operation timed out.")]
685         [Fx.Tag.Throws(typeof(OperationCanceledException), "The operation was canceled because the InstanceHandle has been freed.")]
686         [Fx.Tag.Throws(typeof(InstancePersistenceException), "A command failed.")]
687         [Fx.Tag.Blocking(CancelMethod = "NotifyHandleFree")]
Execute(InstancePersistenceCommand command, TimeSpan timeout)688         public void Execute(InstancePersistenceCommand command, TimeSpan timeout)
689         {
690             if (command == null)
691             {
692                 throw Fx.Exception.ArgumentNull("command");
693             }
694             ThrowIfNotActive("Execute");
695 
696             try
697             {
698                 ReconcileTransaction();
699                 ExecuteAsyncResult.End(new ExecuteAsyncResult(this, command, timeout));
700             }
701             catch (TimeoutException)
702             {
703                 InstanceHandle.Free();
704                 throw;
705             }
706             catch (OperationCanceledException)
707             {
708                 InstanceHandle.Free();
709                 throw;
710             }
711         }
712 
713         // For each level of hierarchy of command execution, only one BeginExecute may be pending at a time.
714         [Fx.Tag.InheritThrows(From = "Execute")]
BeginExecute(InstancePersistenceCommand command, TimeSpan timeout, AsyncCallback callback, object state)715         public IAsyncResult BeginExecute(InstancePersistenceCommand command, TimeSpan timeout, AsyncCallback callback, object state)
716         {
717             if (command == null)
718             {
719                 throw Fx.Exception.ArgumentNull("command");
720             }
721             ThrowIfNotActive("BeginExecute");
722 
723             try
724             {
725                 ReconcileTransaction();
726                 return new ExecuteAsyncResult(this, command, timeout, callback, state);
727             }
728             catch (TimeoutException)
729             {
730                 InstanceHandle.Free();
731                 throw;
732             }
733             catch (OperationCanceledException)
734             {
735                 InstanceHandle.Free();
736                 throw;
737             }
738         }
739 
740         [Fx.Tag.InheritThrows(From = "Execute")]
741         [Fx.Tag.Blocking(CancelMethod = "NotifyHandleFree", Conditional = "!result.IsCompleted")]
EndExecute(IAsyncResult result)742         public void EndExecute(IAsyncResult result)
743         {
744             ExecuteAsyncResult.End(result);
745         }
746 
747         internal Transaction Transaction
748         {
749             get
750             {
751                 return this.transaction;
752             }
753         }
754 
755         internal bool IsHandleDoomedByRollback { get; private set; }
756 
RequireTransaction()757         internal void RequireTransaction()
758         {
759             if (this.transaction != null)
760             {
761                 return;
762             }
763             Fx.AssertAndThrow(!this.freezeTransaction, "RequireTransaction called when transaction is frozen.");
764             Fx.AssertAndThrow(Active, "RequireTransaction called when no command is active.");
765 
766             // It's ok if some time has passed since the timeout value was acquired, it is ok to run long.  This transaction is not generally responsible
767             // for timing out the Execute operation. The exception to this rule is during Commit.
768             this.myTransaction = new CommittableTransaction(new TransactionOptions() { IsolationLevel = IsolationLevel.ReadCommitted, Timeout = this.timeout });
769             Transaction clone = this.myTransaction.Clone();
770             RootAsyncResult.SetInteriorTransaction(this.myTransaction, true);
771             this.transaction = clone;
772         }
773 
PrepareForReuse()774         internal void PrepareForReuse()
775         {
776             Fx.AssertAndThrow(!Active, "Prior use not yet complete!");
777             Fx.AssertAndThrow(IsHostTransaction, "Can only reuse contexts with host transactions.");
778         }
779 
NotifyHandleFree()780         internal void NotifyHandleFree()
781         {
782             CancelRequested = true;
783             ExecuteAsyncResult lastAsyncResult = LastAsyncResult;
784             Action<InstancePersistenceContext> onCancel = lastAsyncResult == null ? null : lastAsyncResult.CancellationHandler;
785             if (onCancel != null)
786             {
787                 try
788                 {
789                     if (Interlocked.CompareExchange(ref this.cancellationHandlerCalled, 0, 1) == 0)
790                     {
791                         onCancel(this);
792                     }
793                 }
794                 catch (Exception exception)
795                 {
796                     if (Fx.IsFatal(exception))
797                     {
798                         throw;
799                     }
800                     throw Fx.Exception.AsError(new CallbackException(SRCore.OnCancelRequestedThrew, exception));
801                 }
802             }
803         }
804 
805         [Fx.Tag.Blocking(CancelMethod = "NotifyHandleFree")]
OuterExecute(InstanceHandle initialInstanceHandle, InstancePersistenceCommand command, Transaction transaction, TimeSpan timeout)806         internal static InstanceView OuterExecute(InstanceHandle initialInstanceHandle, InstancePersistenceCommand command, Transaction transaction, TimeSpan timeout)
807         {
808             try
809             {
810                 return ExecuteAsyncResult.End(new ExecuteAsyncResult(initialInstanceHandle, command, transaction, timeout));
811             }
812             catch (TimeoutException)
813             {
814                 initialInstanceHandle.Free();
815                 throw;
816             }
817             catch (OperationCanceledException)
818             {
819                 initialInstanceHandle.Free();
820                 throw;
821             }
822         }
823 
BeginOuterExecute(InstanceHandle initialInstanceHandle, InstancePersistenceCommand command, Transaction transaction, TimeSpan timeout, AsyncCallback callback, object state)824         internal static IAsyncResult BeginOuterExecute(InstanceHandle initialInstanceHandle, InstancePersistenceCommand command, Transaction transaction, TimeSpan timeout, AsyncCallback callback, object state)
825         {
826             try
827             {
828                 return new ExecuteAsyncResult(initialInstanceHandle, command, transaction, timeout, callback, state);
829             }
830             catch (TimeoutException)
831             {
832                 initialInstanceHandle.Free();
833                 throw;
834             }
835             catch (OperationCanceledException)
836             {
837                 initialInstanceHandle.Free();
838                 throw;
839             }
840         }
841 
842         [Fx.Tag.Blocking(CancelMethod = "NotifyHandleFree", Conditional = "!result.IsCompleted")]
EndOuterExecute(IAsyncResult result)843         internal static InstanceView EndOuterExecute(IAsyncResult result)
844         {
845             InstanceView finalState = ExecuteAsyncResult.End(result);
846             if (finalState == null)
847             {
848                 throw Fx.Exception.Argument("result", InternalSR.InvalidAsyncResult);
849             }
850             return finalState;
851         }
852 
ThrowIfNotLocked()853         void ThrowIfNotLocked()
854         {
855             if (!InstanceView.IsBoundToLock)
856             {
857                 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.InstanceOperationRequiresLock));
858             }
859         }
860 
ThrowIfNoInstance()861         void ThrowIfNoInstance()
862         {
863             if (!InstanceView.IsBoundToInstance)
864             {
865                 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.InstanceOperationRequiresInstance));
866             }
867         }
868 
ThrowIfNoOwner()869         void ThrowIfNoOwner()
870         {
871             if (!InstanceView.IsBoundToInstanceOwner)
872             {
873                 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.InstanceOperationRequiresOwner));
874             }
875         }
876 
ThrowIfCompleted()877         void ThrowIfCompleted()
878         {
879             if (InstanceView.IsBoundToLock && InstanceView.InstanceState == InstanceState.Completed)
880             {
881                 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.InstanceOperationRequiresNotCompleted));
882             }
883         }
884 
ThrowIfUninitialized()885         void ThrowIfUninitialized()
886         {
887             if (InstanceView.IsBoundToLock && InstanceView.InstanceState == InstanceState.Uninitialized)
888             {
889                 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.InstanceOperationRequiresNotUninitialized));
890             }
891         }
892 
ThrowIfNotActive(string methodName)893         void ThrowIfNotActive(string methodName)
894         {
895             if (!Active)
896             {
897                 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.OutsideInstanceExecutionScope(methodName)));
898             }
899         }
900 
ThrowIfNotTransactional(string methodName)901         void ThrowIfNotTransactional(string methodName)
902         {
903             ThrowIfNotActive(methodName);
904             if (RootAsyncResult.CurrentCommand.IsTransactionEnlistmentOptional)
905             {
906                 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.OutsideTransactionalCommand(methodName)));
907             }
908         }
909 
ReconcileTransaction()910         void ReconcileTransaction()
911         {
912             // If the provider fails to flow the transaction, that's fine, we don't consider that a request
913             // not to use one.
914             Transaction transaction = Transaction.Current;
915             if (transaction != null)
916             {
917                 if (this.transaction == null)
918                 {
919                     if (this.freezeTransaction)
920                     {
921                         throw Fx.Exception.AsError(new InvalidOperationException(SRCore.MustSetTransactionOnFirstCall));
922                     }
923                     RootAsyncResult.SetInteriorTransaction(transaction, false);
924                     this.transaction = transaction;
925                 }
926                 else if (!transaction.Equals(this.transaction))
927                 {
928                     throw Fx.Exception.AsError(new InvalidOperationException(SRCore.CannotReplaceTransaction));
929                 }
930             }
931             this.freezeTransaction = true;
932         }
933 
934         class ExecuteAsyncResult : TransactedAsyncResult, ISinglePhaseNotification
935         {
936             static AsyncCompletion onAcquireContext = new AsyncCompletion(OnAcquireContext);
937             static AsyncCompletion onTryCommand = new AsyncCompletion(OnTryCommand);
938             static AsyncCompletion onCommit = new AsyncCompletion(OnCommit);
939             static Action<object, TimeoutException> onBindReclaimed = new Action<object, TimeoutException>(OnBindReclaimed);
940             static Action<object, TimeoutException> onCommitWait = new Action<object, TimeoutException>(OnCommitWait);
941 
942             readonly InstanceHandle initialInstanceHandle;
943             readonly Stack<IEnumerator<InstancePersistenceCommand>> executionStack;
944             readonly TimeoutHelper timeoutHelper;
945             readonly ExecuteAsyncResult priorAsyncResult;
946 
947             InstancePersistenceContext context;
948             CommittableTransaction transactionToCommit;
949             IEnumerator<InstancePersistenceCommand> currentExecution;
950             AsyncWaitHandle waitForTransaction;
951             Action<InstancePersistenceContext> cancellationHandler;
952             bool executeCalledByCurrentCommand;
953             bool rolledBack;
954             bool inDoubt;
955 
956             InstanceView finalState;
957 
ExecuteAsyncResult(InstanceHandle initialInstanceHandle, InstancePersistenceCommand command, Transaction transaction, TimeSpan timeout, AsyncCallback callback, object state)958             public ExecuteAsyncResult(InstanceHandle initialInstanceHandle, InstancePersistenceCommand command, Transaction transaction, TimeSpan timeout, AsyncCallback callback, object state)
959                 : this(command, timeout, callback, state)
960             {
961                 this.initialInstanceHandle = initialInstanceHandle;
962 
963                 OnCompleting = new Action<AsyncResult, Exception>(SimpleCleanup);
964 
965                 IAsyncResult result = this.initialInstanceHandle.BeginAcquireExecutionContext(transaction, this.timeoutHelper.RemainingTime(), PrepareAsyncCompletion(ExecuteAsyncResult.onAcquireContext), this);
966                 if (result.CompletedSynchronously)
967                 {
968                     // After this stage, must complete explicitly in order to get Cleanup to run correctly.
969                     bool completeSelf = false;
970                     Exception completionException = null;
971                     try
972                     {
973                         completeSelf = OnAcquireContext(result);
974                     }
975                     catch (Exception exception)
976                     {
977                         if (Fx.IsFatal(exception))
978                         {
979                             throw;
980                         }
981                         completeSelf = true;
982                         completionException = exception;
983                     }
984                     if (completeSelf)
985                     {
986                         Complete(true, completionException);
987                     }
988                 }
989             }
990 
ExecuteAsyncResult(InstancePersistenceContext context, InstancePersistenceCommand command, TimeSpan timeout, AsyncCallback callback, object state)991             public ExecuteAsyncResult(InstancePersistenceContext context, InstancePersistenceCommand command, TimeSpan timeout, AsyncCallback callback, object state)
992                 : this(command, timeout, callback, state)
993             {
994                 this.context = context;
995 
996                 this.priorAsyncResult = this.context.LastAsyncResult;
997                 Fx.Assert(this.priorAsyncResult != null, "The LastAsyncResult should already have been checked.");
998                 this.priorAsyncResult.executeCalledByCurrentCommand = true;
999 
1000                 OnCompleting = new Action<AsyncResult, Exception>(SimpleCleanup);
1001 
1002                 bool completeSelf = false;
1003                 bool success = false;
1004                 try
1005                 {
1006                     this.context.LastAsyncResult = this;
1007                     if (RunLoop())
1008                     {
1009                         completeSelf = true;
1010                     }
1011                     success = true;
1012                 }
1013                 finally
1014                 {
1015                     if (!success)
1016                     {
1017                         this.context.LastAsyncResult = this.priorAsyncResult;
1018                     }
1019                 }
1020                 if (completeSelf)
1021                 {
1022                     Complete(true);
1023                 }
1024             }
1025 
1026             [Fx.Tag.Blocking(CancelMethod = "NotifyHandleFree", CancelDeclaringType = typeof(InstancePersistenceContext))]
ExecuteAsyncResult(InstanceHandle initialInstanceHandle, InstancePersistenceCommand command, Transaction transaction, TimeSpan timeout)1027             public ExecuteAsyncResult(InstanceHandle initialInstanceHandle, InstancePersistenceCommand command, Transaction transaction, TimeSpan timeout)
1028                 : this(command, timeout, null, null)
1029             {
1030                 this.initialInstanceHandle = initialInstanceHandle;
1031                 this.context = this.initialInstanceHandle.AcquireExecutionContext(transaction, this.timeoutHelper.RemainingTime());
1032 
1033                 Exception completionException = null;
1034                 try
1035                 {
1036                     // After this stage, must complete explicitly in order to get Cleanup to run correctly.
1037                     this.context.RootAsyncResult = this;
1038                     this.context.LastAsyncResult = this;
1039                     OnCompleting = new Action<AsyncResult, Exception>(Cleanup);
1040 
1041                     RunLoopCore(true);
1042 
1043                     if (this.transactionToCommit != null)
1044                     {
1045                         try
1046                         {
1047                             this.transactionToCommit.Commit();
1048                         }
1049                         catch (TransactionException)
1050                         {
1051                             // Since we are enlisted in this transaction, we can ignore exceptions from Commit.
1052                         }
1053                         this.transactionToCommit = null;
1054                     }
1055 
1056                     DoWaitForTransaction(true);
1057                 }
1058                 catch (Exception exception)
1059                 {
1060                     if (Fx.IsFatal(exception))
1061                     {
1062                         throw;
1063                     }
1064                     completionException = exception;
1065                 }
1066                 Complete(true, completionException);
1067             }
1068 
1069             [Fx.Tag.Blocking(CancelMethod = "NotifyHandleFree", CancelDeclaringType = typeof(InstancePersistenceContext))]
ExecuteAsyncResult(InstancePersistenceContext context, InstancePersistenceCommand command, TimeSpan timeout)1070             public ExecuteAsyncResult(InstancePersistenceContext context, InstancePersistenceCommand command, TimeSpan timeout)
1071                 : this(command, timeout, null, null)
1072             {
1073                 this.context = context;
1074 
1075                 this.priorAsyncResult = this.context.LastAsyncResult;
1076                 Fx.Assert(this.priorAsyncResult != null, "The LastAsyncResult should already have been checked.");
1077                 this.priorAsyncResult.executeCalledByCurrentCommand = true;
1078 
1079                 bool success = false;
1080                 try
1081                 {
1082                     this.context.LastAsyncResult = this;
1083                     RunLoopCore(true);
1084                     success = true;
1085                 }
1086                 finally
1087                 {
1088                     this.context.LastAsyncResult = this.priorAsyncResult;
1089                     if (!success && this.context.IsHandleDoomedByRollback)
1090                     {
1091                         this.context.InstanceHandle.Free();
1092                     }
1093                 }
1094                 Complete(true);
1095             }
1096 
ExecuteAsyncResult(InstancePersistenceCommand command, TimeSpan timeout, AsyncCallback callback, object state)1097             ExecuteAsyncResult(InstancePersistenceCommand command, TimeSpan timeout, AsyncCallback callback, object state)
1098                 : base(callback, state)
1099             {
1100                 this.executionStack = new Stack<IEnumerator<InstancePersistenceCommand>>(2);
1101                 this.timeoutHelper = new TimeoutHelper(timeout);
1102 
1103                 this.currentExecution = (new List<InstancePersistenceCommand> { command }).GetEnumerator();
1104             }
1105 
1106             internal InstancePersistenceCommand CurrentCommand { get; private set; }
1107 
1108             internal Action<InstancePersistenceContext> CancellationHandler
1109             {
1110                 get
1111                 {
1112                     Action<InstancePersistenceContext> handler = this.cancellationHandler;
1113                     ExecuteAsyncResult current = this;
1114                     while (handler == null)
1115                     {
1116                         current = current.priorAsyncResult;
1117                         if (current == null)
1118                         {
1119                             break;
1120                         }
1121                         handler = current.cancellationHandler;
1122                     }
1123                     return handler;
1124                 }
1125 
1126                 set
1127                 {
1128                     this.cancellationHandler = value;
1129                 }
1130             }
1131 
SetInteriorTransaction(Transaction interiorTransaction, bool needsCommit)1132             public void SetInteriorTransaction(Transaction interiorTransaction, bool needsCommit)
1133             {
1134                 Fx.Assert(!this.context.IsHostTransaction, "SetInteriorTransaction called for a host transaction.");
1135 
1136                 if (this.waitForTransaction != null)
1137                 {
1138                     throw Fx.Exception.AsError(new InvalidOperationException(SRCore.ExecuteMustBeNested));
1139                 }
1140 
1141                 bool success = false;
1142                 try
1143                 {
1144                     this.waitForTransaction = new AsyncWaitHandle(EventResetMode.ManualReset);
1145                     interiorTransaction.EnlistVolatile(this, EnlistmentOptions.None);
1146                     success = true;
1147                 }
1148                 finally
1149                 {
1150                     if (!success)
1151                     {
1152                         if (this.waitForTransaction != null)
1153                         {
1154                             this.waitForTransaction.Set();
1155                         }
1156                     }
1157                     else if (needsCommit)
1158                     {
1159                         this.transactionToCommit = (CommittableTransaction)interiorTransaction;
1160                     }
1161                 }
1162             }
1163 
1164             [Fx.Tag.Blocking(CancelMethod = "NotifyHandleFree", CancelDeclaringType = typeof(InstancePersistenceContext), Conditional = "!result.IsCOmpleted")]
End(IAsyncResult result)1165             public static InstanceView End(IAsyncResult result)
1166             {
1167                 ExecuteAsyncResult thisPtr = AsyncResult.End<ExecuteAsyncResult>(result);
1168                 Fx.Assert((thisPtr.finalState == null) == (thisPtr.initialInstanceHandle == null), "Should have thrown an exception if this is null on the outer result.");
1169                 return thisPtr.finalState;
1170             }
1171 
OnAcquireContext(IAsyncResult result)1172             static bool OnAcquireContext(IAsyncResult result)
1173             {
1174                 ExecuteAsyncResult thisPtr = (ExecuteAsyncResult)result.AsyncState;
1175                 thisPtr.context = thisPtr.initialInstanceHandle.EndAcquireExecutionContext(result);
1176                 thisPtr.context.RootAsyncResult = thisPtr;
1177                 thisPtr.context.LastAsyncResult = thisPtr;
1178                 thisPtr.OnCompleting = new Action<AsyncResult, Exception>(thisPtr.Cleanup);
1179                 return thisPtr.RunLoop();
1180             }
1181 
1182             [Fx.Tag.Blocking(CancelMethod = "NotifyHandleFree", CancelDeclaringType = typeof(InstancePersistenceContext), Conditional = "synchronous")]
RunLoopCore(bool synchronous)1183             bool RunLoopCore(bool synchronous)
1184             {
1185                 while (this.currentExecution != null)
1186                 {
1187                     if (this.currentExecution.MoveNext())
1188                     {
1189                         bool isFirstCommand = CurrentCommand == null;
1190                         this.executeCalledByCurrentCommand = false;
1191                         CurrentCommand = this.currentExecution.Current;
1192 
1193                         Fx.Assert(isFirstCommand || this.executionStack.Count > 0, "The first command should always remain at the top of the stack.");
1194 
1195                         if (isFirstCommand)
1196                         {
1197                             if (this.priorAsyncResult != null)
1198                             {
1199                                 if (this.priorAsyncResult.CurrentCommand.IsTransactionEnlistmentOptional && !CurrentCommand.IsTransactionEnlistmentOptional)
1200                                 {
1201                                     throw Fx.Exception.AsError(new InvalidOperationException(SRCore.CannotInvokeTransactionalFromNonTransactional));
1202                                 }
1203                             }
1204                         }
1205                         else if (this.executionStack.Peek().Current.IsTransactionEnlistmentOptional)
1206                         {
1207                             if (!CurrentCommand.IsTransactionEnlistmentOptional)
1208                             {
1209                                 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.CannotInvokeTransactionalFromNonTransactional));
1210                             }
1211                         }
1212                         else if (this.priorAsyncResult == null)
1213                         {
1214                             // This is not the first command. Since the whole thing wasn't done at once by the
1215                             // provider, force a transaction if the first command required one.
1216                             this.context.RequireTransaction();
1217                         }
1218 
1219                         // Intentionally calling MayBindLockToInstanceHandle prior to Validate.  This is a publically visible order.
1220                         bool mayBindLockToInstanceHandle = CurrentCommand.AutomaticallyAcquiringLock;
1221                         CurrentCommand.Validate(this.context.InstanceView);
1222 
1223                         if (mayBindLockToInstanceHandle)
1224                         {
1225                             if (isFirstCommand)
1226                             {
1227                                 if (this.priorAsyncResult != null)
1228                                 {
1229                                     if (!this.priorAsyncResult.CurrentCommand.AutomaticallyAcquiringLock)
1230                                     {
1231                                         throw Fx.Exception.AsError(new InvalidOperationException(SRCore.CannotInvokeBindingFromNonBinding));
1232                                     }
1233                                 }
1234                                 else if (!this.context.InstanceView.IsBoundToInstanceOwner)
1235                                 {
1236                                     throw Fx.Exception.AsError(new InvalidOperationException(SRCore.MayBindLockCommandShouldValidateOwner));
1237                                 }
1238                                 else if (!this.context.InstanceView.IsBoundToLock)
1239                                 {
1240                                     // This is the first command in the set and it may lock, so we must start the bind.
1241                                     this.context.InstanceHandle.StartPotentialBind();
1242                                 }
1243                             }
1244                             else if (!this.executionStack.Peek().Current.AutomaticallyAcquiringLock)
1245                             {
1246                                 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.CannotInvokeBindingFromNonBinding));
1247                             }
1248                         }
1249 
1250                         if (this.context.CancelRequested)
1251                         {
1252                             throw Fx.Exception.AsError(new OperationCanceledException(SRCore.HandleFreed));
1253                         }
1254 
1255                         BindReclaimedLockException bindReclaimedLockException = null;
1256                         if (synchronous)
1257                         {
1258                             bool commandProcessed;
1259                             TransactionScope txScope = null;
1260                             try
1261                             {
1262                                 txScope = TransactionHelper.CreateTransactionScope(this.context.Transaction);
1263                                 commandProcessed = this.context.InstanceHandle.Store.TryCommand(this.context, CurrentCommand, this.timeoutHelper.RemainingTime());
1264                             }
1265                             catch (BindReclaimedLockException exception)
1266                             {
1267                                 bindReclaimedLockException = exception;
1268                                 commandProcessed = true;
1269                             }
1270                             finally
1271                             {
1272                                 TransactionHelper.CompleteTransactionScope(ref txScope);
1273                             }
1274                             AfterCommand(commandProcessed);
1275                             if (bindReclaimedLockException != null)
1276                             {
1277                                 BindReclaimed(!bindReclaimedLockException.MarkerWaitHandle.Wait(this.timeoutHelper.RemainingTime()));
1278                             }
1279                         }
1280                         else
1281                         {
1282                             IAsyncResult result;
1283                             using (PrepareTransactionalCall(this.context.Transaction))
1284                             {
1285                                 try
1286                                 {
1287                                     result = this.context.InstanceHandle.Store.BeginTryCommand(this.context, CurrentCommand, this.timeoutHelper.RemainingTime(), PrepareAsyncCompletion(ExecuteAsyncResult.onTryCommand), this);
1288                                 }
1289                                 catch (BindReclaimedLockException exception)
1290                                 {
1291                                     bindReclaimedLockException = exception;
1292                                     result = null;
1293                                 }
1294                             }
1295                             if (result == null)
1296                             {
1297                                 AfterCommand(true);
1298                                 if (!bindReclaimedLockException.MarkerWaitHandle.WaitAsync(ExecuteAsyncResult.onBindReclaimed, this, this.timeoutHelper.RemainingTime()))
1299                                 {
1300                                     return false;
1301                                 }
1302                                 BindReclaimed(false);
1303                             }
1304                             else
1305                             {
1306                                 if (!CheckSyncContinue(result) || !DoEndCommand(result))
1307                                 {
1308                                     return false;
1309                                 }
1310                             }
1311                         }
1312                     }
1313                     else if (this.executionStack.Count > 0)
1314                     {
1315                         this.currentExecution = this.executionStack.Pop();
1316                     }
1317                     else
1318                     {
1319                         this.currentExecution = null;
1320                     }
1321                 }
1322 
1323                 CurrentCommand = null;
1324                 return true;
1325             }
1326 
RunLoop()1327             bool RunLoop()
1328             {
1329                 if (!RunLoopCore(false))
1330                 {
1331                     return false;
1332                 }
1333 
1334                 // If this is an inner command, return true right away to continue this execution episode in a different async result.
1335                 if (this.initialInstanceHandle == null)
1336                 {
1337                     return true;
1338                 }
1339 
1340                 // This is is an outer scope.  We need to commit and/or wait for commit if necessary.
1341                 if (this.transactionToCommit != null)
1342                 {
1343                     IAsyncResult result = null;
1344                     try
1345                     {
1346                         result = this.transactionToCommit.BeginCommit(PrepareAsyncCompletion(ExecuteAsyncResult.onCommit), this);
1347                     }
1348                     catch (TransactionException)
1349                     {
1350                         // Since we are enlisted in the transaction, we can ignore exceptions from Commit.
1351                         this.transactionToCommit = null;
1352                     }
1353                     if (result != null)
1354                     {
1355                         return result.CompletedSynchronously ? OnCommit(result) : false;
1356                     }
1357                 }
1358 
1359                 return DoWaitForTransaction(false);
1360             }
1361 
OnTryCommand(IAsyncResult result)1362             static bool OnTryCommand(IAsyncResult result)
1363             {
1364                 ExecuteAsyncResult thisPtr = (ExecuteAsyncResult)result.AsyncState;
1365                 return thisPtr.DoEndCommand(result) && thisPtr.RunLoop();
1366             }
1367 
1368             [Fx.Tag.GuaranteeNonBlocking]
DoEndCommand(IAsyncResult result)1369             bool DoEndCommand(IAsyncResult result)
1370             {
1371                 bool commandProcessed;
1372                 BindReclaimedLockException bindReclaimedLockException = null;
1373                 try
1374                 {
1375                     commandProcessed = this.context.InstanceHandle.Store.EndTryCommand(result);
1376                 }
1377                 catch (BindReclaimedLockException exception)
1378                 {
1379                     bindReclaimedLockException = exception;
1380                     commandProcessed = true;
1381                 }
1382                 AfterCommand(commandProcessed);
1383                 if (bindReclaimedLockException != null)
1384                 {
1385                     if (!bindReclaimedLockException.MarkerWaitHandle.WaitAsync(ExecuteAsyncResult.onBindReclaimed, this, this.timeoutHelper.RemainingTime()))
1386                     {
1387                         return false;
1388                     }
1389                     BindReclaimed(false);
1390                 }
1391                 return true;
1392             }
1393 
AfterCommand(bool commandProcessed)1394             void AfterCommand(bool commandProcessed)
1395             {
1396                 if (!object.ReferenceEquals(this.context.LastAsyncResult, this))
1397                 {
1398                     throw Fx.Exception.AsError(new InvalidOperationException(SRCore.ExecuteMustBeNested));
1399                 }
1400                 if (!commandProcessed)
1401                 {
1402                     if (this.executeCalledByCurrentCommand)
1403                     {
1404                         throw Fx.Exception.AsError(new InvalidOperationException(SRCore.TryCommandCannotExecuteSubCommandsAndReduce));
1405                     }
1406                     IEnumerable<InstancePersistenceCommand> reduction = CurrentCommand.Reduce(this.context.InstanceView);
1407                     if (reduction == null)
1408                     {
1409                         throw Fx.Exception.AsError(new NotSupportedException(SRCore.ProviderDoesNotSupportCommand(CurrentCommand.Name)));
1410                     }
1411                     this.executionStack.Push(this.currentExecution);
1412                     this.currentExecution = reduction.GetEnumerator();
1413                 }
1414             }
1415 
OnBindReclaimed(object state, TimeoutException timeoutException)1416             static void OnBindReclaimed(object state, TimeoutException timeoutException)
1417             {
1418                 ExecuteAsyncResult thisPtr = (ExecuteAsyncResult)state;
1419 
1420                 bool completeSelf;
1421                 Exception completionException = null;
1422                 try
1423                 {
1424                     thisPtr.BindReclaimed(timeoutException != null);
1425                     completeSelf = thisPtr.RunLoop();
1426                 }
1427                 catch (Exception exception)
1428                 {
1429                     if (Fx.IsFatal(exception))
1430                     {
1431                         throw;
1432                     }
1433                     completionException = exception;
1434                     completeSelf = true;
1435                 }
1436                 if (completeSelf)
1437                 {
1438                     thisPtr.Complete(false, completionException);
1439                 }
1440             }
1441 
BindReclaimed(bool timedOut)1442             void BindReclaimed(bool timedOut)
1443             {
1444                 if (timedOut)
1445                 {
1446                     this.context.InstanceHandle.CancelReclaim(new TimeoutException(SRCore.TimedOutWaitingForLockResolution));
1447                 }
1448                 this.context.ConcludeBindReclaimedLockHelper();
1449 
1450                 // If we get here, the reclaim attempt succeeded and we own the lock - but we are in the
1451                 // CreateBindReclaimedLockException path, which auto-cancels on success.
1452                 this.context.InstanceHandle.Free();
1453                 throw Fx.Exception.AsError(new OperationCanceledException(SRCore.BindReclaimSucceeded));
1454             }
1455 
1456             [Fx.Tag.GuaranteeNonBlocking]
OnCommit(IAsyncResult result)1457             static bool OnCommit(IAsyncResult result)
1458             {
1459                 ExecuteAsyncResult thisPtr = (ExecuteAsyncResult)result.AsyncState;
1460                 try
1461                 {
1462                     thisPtr.transactionToCommit.EndCommit(result);
1463                 }
1464                 catch (TransactionException)
1465                 {
1466                     // Since we are enlisted in the transaction, we can ignore exceptions from Commit.
1467                 }
1468                 thisPtr.transactionToCommit = null;
1469                 return thisPtr.DoWaitForTransaction(false);
1470             }
1471 
1472             [Fx.Tag.Blocking(CancelMethod = "NotifyHandleFree", CancelDeclaringType = typeof(InstancePersistenceContext), Conditional = "synchronous")]
DoWaitForTransaction(bool synchronous)1473             bool DoWaitForTransaction(bool synchronous)
1474             {
1475                 if (this.waitForTransaction != null)
1476                 {
1477                     if (synchronous)
1478                     {
1479                         TimeSpan waitTimeout = this.timeoutHelper.RemainingTime();
1480                         if (!this.waitForTransaction.Wait(waitTimeout))
1481                         {
1482                             throw Fx.Exception.AsError(new TimeoutException(InternalSR.TimeoutOnOperation(waitTimeout)));
1483                         }
1484                     }
1485                     else
1486                     {
1487                         if (!this.waitForTransaction.WaitAsync(ExecuteAsyncResult.onCommitWait, this, this.timeoutHelper.RemainingTime()))
1488                         {
1489                             return false;
1490                         }
1491                     }
1492                     Exception exception = AfterCommitWait();
1493                     if (exception != null)
1494                     {
1495                         throw Fx.Exception.AsError(exception);
1496                     }
1497                 }
1498                 else if (this.context.IsHostTransaction)
1499                 {
1500                     // For host transactions, we need to provide a clone of the intermediate state as the final state.
1501                     this.finalState = this.context.InstanceView.Clone();
1502                     this.finalState.MakeReadOnly();
1503 
1504                     // The intermediate state should have the query results cleared - they are per-call of Execute.
1505                     this.context.InstanceView.InstanceStoreQueryResults = null;
1506                 }
1507                 else
1508                 {
1509                     // If we get here, there's no transaction at all.  Need to "commit" the intermediate state.
1510                     CommitHelper();
1511                     if (this.finalState == null)
1512                     {
1513                         this.context.InstanceHandle.Free();
1514                         throw Fx.Exception.AsError(new InstanceHandleConflictException(null, this.context.InstanceView.InstanceId));
1515                     }
1516                 }
1517                 return true;
1518             }
1519 
OnCommitWait(object state, TimeoutException exception)1520             static void OnCommitWait(object state, TimeoutException exception)
1521             {
1522                 ExecuteAsyncResult thisPtr = (ExecuteAsyncResult)state;
1523                 thisPtr.Complete(false, exception ?? thisPtr.AfterCommitWait());
1524             }
1525 
AfterCommitWait()1526             Exception AfterCommitWait()
1527             {
1528                 if (this.inDoubt)
1529                 {
1530                     this.context.InstanceHandle.Free();
1531                     return new TransactionInDoubtException(SRCore.TransactionInDoubtNonHost);
1532                 }
1533                 if (this.rolledBack)
1534                 {
1535                     if (this.context.IsHandleDoomedByRollback)
1536                     {
1537                         this.context.InstanceHandle.Free();
1538                     }
1539                     return new TransactionAbortedException(SRCore.TransactionRolledBackNonHost);
1540                 }
1541                 if (this.finalState == null)
1542                 {
1543                     this.context.InstanceHandle.Free();
1544                     return new InstanceHandleConflictException(null, this.context.InstanceView.InstanceId);
1545                 }
1546                 return null;
1547             }
1548 
CommitHelper()1549             void CommitHelper()
1550             {
1551                 this.finalState = this.context.InstanceHandle.Commit(this.context.InstanceView);
1552             }
1553 
SimpleCleanup(AsyncResult result, Exception exception)1554             void SimpleCleanup(AsyncResult result, Exception exception)
1555             {
1556                 if (this.initialInstanceHandle == null)
1557                 {
1558                     Fx.Assert(this.priorAsyncResult != null, "In the non-outer case, we should always have a priorAsyncResult here, since we set it before ----igining OnComplete.");
1559                     this.context.LastAsyncResult = this.priorAsyncResult;
1560                 }
1561                 if (exception != null)
1562                 {
1563                     if (this.context != null && this.context.IsHandleDoomedByRollback)
1564                     {
1565                         this.context.InstanceHandle.Free();
1566                     }
1567                     else if (exception is TimeoutException || exception is OperationCanceledException)
1568                     {
1569                         if (this.context == null)
1570                         {
1571                             this.initialInstanceHandle.Free();
1572                         }
1573                         else
1574                         {
1575                             this.context.InstanceHandle.Free();
1576                         }
1577                     }
1578                 }
1579             }
1580 
Cleanup(AsyncResult result, Exception exception)1581             void Cleanup(AsyncResult result, Exception exception)
1582             {
1583                 try
1584                 {
1585                     SimpleCleanup(result, exception);
1586                     if (this.transactionToCommit != null)
1587                     {
1588                         try
1589                         {
1590                             this.transactionToCommit.Rollback(exception);
1591                         }
1592                         catch (TransactionException)
1593                         {
1594                         }
1595                     }
1596                 }
1597                 finally
1598                 {
1599                     Fx.AssertAndThrowFatal(this.context.Active, "Out-of-sync between InstanceExecutionContext and ExecutionAsyncResult.");
1600 
1601                     this.context.LastAsyncResult = null;
1602                     this.context.RootAsyncResult = null;
1603                     this.context.InstanceHandle.ReleaseExecutionContext();
1604                 }
1605             }
1606 
ISinglePhaseNotification.SinglePhaseCommit(SinglePhaseEnlistment singlePhaseEnlistment)1607             void ISinglePhaseNotification.SinglePhaseCommit(SinglePhaseEnlistment singlePhaseEnlistment)
1608             {
1609                 CommitHelper();
1610                 singlePhaseEnlistment.Committed();
1611                 this.waitForTransaction.Set();
1612             }
1613 
IEnlistmentNotification.Commit(Enlistment enlistment)1614             void IEnlistmentNotification.Commit(Enlistment enlistment)
1615             {
1616                 CommitHelper();
1617                 enlistment.Done();
1618                 this.waitForTransaction.Set();
1619             }
1620 
IEnlistmentNotification.InDoubt(Enlistment enlistment)1621             void IEnlistmentNotification.InDoubt(Enlistment enlistment)
1622             {
1623                 enlistment.Done();
1624                 this.inDoubt = true;
1625                 this.waitForTransaction.Set();
1626             }
1627 
IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)1628             void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)
1629             {
1630                 preparingEnlistment.Prepared();
1631             }
1632 
IEnlistmentNotification.Rollback(Enlistment enlistment)1633             void IEnlistmentNotification.Rollback(Enlistment enlistment)
1634             {
1635                 enlistment.Done();
1636                 this.rolledBack = true;
1637                 this.waitForTransaction.Set();
1638             }
1639         }
1640 
1641         class BindReclaimedLockAsyncResult : AsyncResult
1642         {
1643             static Action<object, TimeoutException> waitComplete = new Action<object, TimeoutException>(OnWaitComplete);
1644 
1645             readonly InstancePersistenceContext context;
1646 
BindReclaimedLockAsyncResult(InstancePersistenceContext context, AsyncWaitHandle wait, TimeSpan timeout, AsyncCallback callback, object state)1647             public BindReclaimedLockAsyncResult(InstancePersistenceContext context, AsyncWaitHandle wait, TimeSpan timeout, AsyncCallback callback, object state)
1648                 : base(callback, state)
1649             {
1650                 this.context = context;
1651 
1652                 if (wait.WaitAsync(BindReclaimedLockAsyncResult.waitComplete, this, timeout))
1653                 {
1654                     this.context.ConcludeBindReclaimedLockHelper();
1655                     Complete(true);
1656                 }
1657             }
1658 
OnWaitComplete(object state, TimeoutException timeoutException)1659             static void OnWaitComplete(object state, TimeoutException timeoutException)
1660             {
1661                 BindReclaimedLockAsyncResult thisPtr = (BindReclaimedLockAsyncResult)state;
1662 
1663                 Exception completionException = null;
1664                 try
1665                 {
1666                     if (timeoutException != null)
1667                     {
1668                         thisPtr.context.InstanceHandle.CancelReclaim(new TimeoutException(SRCore.TimedOutWaitingForLockResolution));
1669                     }
1670                     thisPtr.context.ConcludeBindReclaimedLockHelper();
1671                 }
1672                 catch (Exception exception)
1673                 {
1674                     if (Fx.IsFatal(exception))
1675                     {
1676                         throw;
1677                     }
1678                     completionException = exception;
1679                 }
1680                 thisPtr.Complete(false, completionException);
1681             }
1682 
End(IAsyncResult result)1683             public static void End(IAsyncResult result)
1684             {
1685                 AsyncResult.End<BindReclaimedLockAsyncResult>(result);
1686             }
1687         }
1688 
1689         [Serializable]
1690         class BindReclaimedLockException : Exception
1691         {
BindReclaimedLockException()1692             public BindReclaimedLockException()
1693             {
1694             }
1695 
BindReclaimedLockException(AsyncWaitHandle markerWaitHandle)1696             internal BindReclaimedLockException(AsyncWaitHandle markerWaitHandle)
1697                 : base(SRCore.BindReclaimedLockException)
1698             {
1699                 MarkerWaitHandle = markerWaitHandle;
1700             }
1701 
1702             internal AsyncWaitHandle MarkerWaitHandle { get; private set; }
1703 
1704             [SecurityCritical]
BindReclaimedLockException(SerializationInfo info, StreamingContext context)1705             protected BindReclaimedLockException(SerializationInfo info, StreamingContext context)
1706                 : base(info, context)
1707             {
1708             }
1709         }
1710     }
1711 }
1712