96a92a8fed856a98323a9bf2306b5fac2201c62c
[mono.git] / mcs / class / referencesource / System.ServiceModel.Activities / System / ServiceModel / Activities / Dispatcher / PersistenceContext.cs
1 //-----------------------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //-----------------------------------------------------------------------------
4
5 namespace System.ServiceModel.Activities.Dispatcher
6 {
7     using System.Activities;
8     using System.Activities.DurableInstancing;
9     using System.Activities.Hosting;
10     using System.Collections;
11     using System.Collections.ObjectModel;
12     using System.Collections.Generic;
13     using System.Diagnostics.CodeAnalysis;
14     using System.Runtime;
15     using System.Runtime.DurableInstancing;
16     using System.ServiceModel.Channels;
17     using System.ServiceModel.Activities.Description;
18     using System.Transactions;
19     using System.Xml.Linq;    
20
21     sealed class PersistenceContext : CommunicationObject
22     {
23         // Dictionary keyed by Transaction HashCode. The value is the enlistment for that transaction.
24         internal static Dictionary<int, PersistenceContextEnlistment> Enlistments = new Dictionary<int, PersistenceContextEnlistment>();
25
26         readonly PersistenceProviderDirectory directory;
27
28         readonly InstanceStore store;
29         readonly InstanceHandle handle;
30
31         readonly HashSet<InstanceKey> keysToAssociate;
32         readonly HashSet<InstanceKey> keysToDisassociate;
33
34         static TimeSpan defaultOpenTimeout = TimeSpan.FromSeconds(90);
35         static TimeSpan defaultCloseTimeout = TimeSpan.FromSeconds(90);
36
37
38         bool operationInProgress;
39
40         WorkflowServiceInstance workflowInstance;
41
42         // The hash code of the transaction that has this particular context "locked".
43         // If the value of this property is 0, then no transaction is working on this context
44         // and it is available to be "locked" by a transaction. Locking for transactions is done
45         // with QueueForTransactionLock. This method returns a TransactionWaitAsyncResult. If the
46         // lock was obtained by the call, the resulting AsyncResult will be marked as "Completed"
47         // upon return from QueueForTransactionLock. If not, the caller should wait on the 
48         // AsyncResult.AsyncWaitHandle before proceeding to update any of the fields of the context.
49         int lockingTransaction;
50         //We are keeping a reference to both the transaction object and the hash code to avoid calling the GetHashCode multiple times
51         Transaction lockingTransactionObject;
52
53         // This is the queue of TransactionWaitAsyncResult objects that are waiting for the
54         // context to become "unlocked" with respect to a transaction. DequeueTransactionWaiter
55         // removes the first element from this queue and returns it. If there is no element on the
56         // queue, null is returned indicating that there was no outstanding waiter.
57         Queue<TransactionWaitAsyncResult> transactionWaiterQueue;
58
59         // Used by PPD when there is no store.
60         internal PersistenceContext(PersistenceProviderDirectory directory,
61             Guid instanceId, InstanceKey key, IEnumerable<InstanceKey> associatedKeys)
62         {
63             Fx.Assert(directory != null, "Directory is null in PersistenceContext.");
64             Fx.Assert(instanceId != Guid.Empty, "Cannot provide an empty instance ID.");
65
66             this.directory = directory;
67
68             InstanceId = instanceId;
69
70             AssociatedKeys = associatedKeys != null ? new HashSet<InstanceKey>(associatedKeys) :
71                 new HashSet<InstanceKey>();
72             if (key != null && !AssociatedKeys.Contains(key))
73             {
74                 AssociatedKeys.Add(key);
75             }
76
77             this.keysToAssociate = new HashSet<InstanceKey>(AssociatedKeys);
78             this.keysToDisassociate = new HashSet<InstanceKey>();
79
80             this.lockingTransaction = 0;
81             this.Detaching = false;
82             this.transactionWaiterQueue = new Queue<TransactionWaitAsyncResult>();
83         }
84
85         // Used by PPD when there is a store.
86         internal PersistenceContext(PersistenceProviderDirectory directory, InstanceStore store,
87             InstanceHandle handle, Guid instanceId, IEnumerable<InstanceKey> associatedKeys,
88             bool newInstance, bool locked, InstanceView view, WorkflowIdentityKey updatedIdentity) 
89             : this(directory, instanceId, null, associatedKeys)
90         {
91             Fx.Assert(store != null, "Null store passed to PersistenceContext.");
92             Fx.Assert(handle != null, "Null handle passed to PersistenceContext.");
93
94             this.store = store;
95             this.handle = handle;
96
97             IsInitialized = !newInstance;
98             IsLocked = locked;
99
100             if (view != null)
101             {
102                 ReadSuspendedInfo(view);
103             }
104
105             // If we were loaded or we locked the instance, the keys will have been sync'd.
106             if (IsInitialized || IsLocked)
107             {
108                 RationalizeSavedKeys(false);
109             }
110
111             if (IsInitialized)
112             {
113                 Fx.Assert(view != null, "View must be specified on an initialized instance.");
114                 WorkflowIdentity definitionIdentity;                
115
116                 if (!TryGetValue<WorkflowIdentity>(view.InstanceMetadata, Workflow45Namespace.DefinitionIdentity, out definitionIdentity))
117                 {
118                     definitionIdentity = null;
119                 }
120
121                 this.workflowInstance = this.directory.InitializeInstance(InstanceId, this, definitionIdentity, updatedIdentity, view.InstanceData, null);
122             }
123         }
124
125         public Guid InstanceId { get; private set; }
126
127         public bool IsLocked { get; private set; }
128         public bool IsInitialized { get; private set; }
129         public bool IsCompleted { get; private set; }
130         public bool IsVisible { get; internal set; }
131
132         public bool IsSuspended { get; set; }
133         public string SuspendedReason { get; set; }
134
135         // Set to true when we detach from the PPD under a transaction. When the transaction completes,
136         // either commit or abort, we will finish the removal from the PPD.
137         internal bool Detaching
138         {
139             get; set;
140         }
141
142         public bool CanPersist
143         {
144             get
145             {
146                 return (this.store != null);
147             }
148         }
149
150         public bool IsHandleValid
151         {
152             get
153             {
154                 return this.handle == null || this.handle.IsValid;
155             }
156         }
157
158         internal Transaction LockingTransaction
159         {
160             get
161             {
162                 lock (ThisLock)
163                 {
164                     ThrowIfDisposedOrNotOpen();
165                     return this.lockingTransactionObject;
166                 }
167             }
168         }
169
170         // Used only by PPD.
171         internal bool IsPermanentlyRemoved { get; set; }
172
173         // If there's a directory, only it can write to this collection as long as the isntance is locked.  Otherwise,
174         // only this class can.
175         internal HashSet<InstanceKey> AssociatedKeys { get; private set; }
176         internal ReadOnlyCollection<BookmarkInfo> Bookmarks { get; set; }
177
178         protected override TimeSpan DefaultCloseTimeout { get { return defaultCloseTimeout; } }
179         protected override TimeSpan DefaultOpenTimeout { get { return defaultOpenTimeout; } }
180
181         // Remove key associations.  These are never immediately propagated to the store / cache.  Succeeds
182         // if the keys don't exist or are associated with a different instance (in which case they are
183         // not disassociated).
184         public void DisassociateKeys(ICollection<InstanceKey> expiredKeys)
185         {
186             ThrowIfDisposedOrNotOpen();
187             Fx.Assert(expiredKeys != null, "'expiredKeys' parameter to DisassociateKeys cannot be null.");
188
189             try
190             {
191                 StartOperation();
192                 ThrowIfCompleted();
193                 ThrowIfNotVisible();
194                 Fx.Assert(!IsInitialized || IsLocked, "Should not be visible if initialized and not locked.");
195
196                 foreach (InstanceKey key in expiredKeys)
197                 {
198                     if (AssociatedKeys.Contains(key) && !this.keysToDisassociate.Contains(key))
199                     {
200                         this.keysToDisassociate.Add(key);
201                         this.keysToAssociate.Remove(key);
202                     }
203                     else
204                     {
205                         Fx.Assert(!this.keysToAssociate.Contains(key), "Cannot be planning to associate this key.");
206                     }
207                 }
208             }
209             finally
210             {
211                 FinishOperation();
212             }
213         }
214
215         public IAsyncResult BeginSave(
216             IDictionary<XName, InstanceValue> instance,
217             SaveStatus saveStatus,
218             TimeSpan timeout,
219             AsyncCallback callback,
220             object state)
221         {
222             ThrowIfDisposedOrNotOpen();
223             Fx.AssertAndThrow(instance != null, "'instance' parameter to BeginSave cannot be null.");
224
225             return new SaveAsyncResult(this, instance, saveStatus, timeout, callback, state);
226         }
227
228         public void EndSave(IAsyncResult result)
229         {
230             SaveAsyncResult.End(result);
231         }
232
233         public IAsyncResult BeginRelease(TimeSpan timeout, AsyncCallback callback, object state)
234         {
235             ThrowIfDisposedOrNotOpen();
236
237             return new ReleaseAsyncResult(this, timeout, callback, state);
238         }
239
240         public void EndRelease(IAsyncResult result)
241         {
242             ReleaseAsyncResult.End(result);
243         }
244
245         public IAsyncResult BeginAssociateKeys(
246             ICollection<InstanceKey> associatedKeys, TimeSpan timeout, AsyncCallback callback, object state)
247         {
248             return BeginAssociateKeysHelper(associatedKeys, timeout, true, callback, state);
249         }
250
251         internal IAsyncResult BeginAssociateInfrastructureKeys(
252             ICollection<InstanceKey> associatedKeys, TimeSpan timeout, AsyncCallback callback, object state)
253         {
254             return BeginAssociateKeysHelper(associatedKeys, timeout, true, callback, state);
255         }
256
257         IAsyncResult BeginAssociateKeysHelper(ICollection<InstanceKey> associatedKeys,
258             TimeSpan timeout, bool applicationKeys, AsyncCallback callback, object state)
259         {
260             ThrowIfDisposedOrNotOpen();
261             Fx.Assert(associatedKeys != null, "'associatedKeys' parameter to BeginAssociateKeys cannot be null.");
262
263             return new AssociateKeysAsyncResult(this, associatedKeys, timeout, applicationKeys, callback, state);
264         }
265
266         public void EndAssociateKeys(IAsyncResult result)
267         {
268             AssociateKeysAsyncResult.End(result);
269         }
270
271         internal void EndAssociateInfrastructureKeys(IAsyncResult result)
272         {
273             AssociateKeysAsyncResult.End(result);
274         }
275
276         // UpdateSuspendMetadata and Unlock instance
277         public IAsyncResult BeginUpdateSuspendMetadata(Exception reason, TimeSpan timeout, AsyncCallback callback, object state)
278         {
279             ThrowIfDisposedOrNotOpen();
280
281             return new UpdateSuspendMetadataAsyncResult(this, reason, timeout, callback, state);
282         }
283
284         public void EndUpdateSuspendMetadata(IAsyncResult result)
285         {
286             UpdateSuspendMetadataAsyncResult.End(result);
287         }
288
289         public WorkflowServiceInstance GetInstance(WorkflowGetInstanceContext parameters)
290         {
291             if (this.workflowInstance == null && parameters != null)
292             {
293                 lock (ThisLock)
294                 {
295                     ThrowIfDisposedOrNotOpen();
296
297                     if (this.workflowInstance == null)
298                     {
299                         try
300                         {
301                             WorkflowServiceInstance result;
302                             if (parameters.WorkflowHostingEndpoint != null)
303                             {
304                                 WorkflowHostingResponseContext responseContext = new WorkflowHostingResponseContext();
305                                 WorkflowCreationContext creationContext = parameters.WorkflowHostingEndpoint.OnGetCreationContext(parameters.Inputs, parameters.OperationContext, InstanceId, responseContext);
306                                 if (creationContext == null)
307                                 {
308                                     throw FxTrace.Exception.AsError(WorkflowHostingEndpoint.CreateDispatchFaultException());
309                                 }
310                                 result = this.directory.InitializeInstance(InstanceId, this, null, creationContext);
311
312                                 // Return args
313                                 parameters.WorkflowCreationContext = creationContext;
314                                 parameters.WorkflowHostingResponseContext = responseContext;
315                             }
316                             else
317                             {
318                                 result = this.directory.InitializeInstance(InstanceId, this, null, null);
319                             }
320                             this.workflowInstance = result;
321                         }
322                         finally
323                         {
324                             if (this.workflowInstance == null)
325                             {
326                                 Fault();
327                             }
328                         }
329                     }
330                 }
331             }
332             return this.workflowInstance;
333         }
334
335         protected override void OnAbort()
336         {
337             if (this.handle != null)
338             {
339                 this.handle.Free();
340             }
341         }
342
343         protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state)
344         {
345             return new CloseAsyncResult(this, callback, state);
346         }
347
348         protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
349         {
350             return new CompletedAsyncResult(callback, state);
351         }
352
353         protected override void OnClose(TimeSpan timeout)
354         {
355             try
356             {
357                 StartOperation();
358
359                 if (this.store != null)
360                 {
361                     this.handle.Free();
362                 }
363             }
364             finally
365             {
366                 FinishOperation();
367             }
368         }
369
370         protected override void OnEndClose(IAsyncResult result)
371         {
372             CloseAsyncResult.End(result);
373         }
374
375         protected override void OnEndOpen(IAsyncResult result)
376         {
377             CompletedAsyncResult.End(result);
378         }
379
380         // PersistenceProviderDirectory calls Open in an async path.  Do not introduce blocking work to this method
381         // without changing PersistenceProviderDirectory to call BeginOpen instead.
382         protected override void OnOpen(TimeSpan timeout)
383         {
384         }
385
386         protected override void OnClosing()
387         {
388             base.OnClosing();
389             this.directory.RemoveInstance(this, true);
390         }
391
392         protected override void OnFaulted()
393         {
394             base.OnFaulted();
395             this.directory.RemoveInstance(this, true);
396         }
397
398         void RationalizeSavedKeys(bool updateDirectory)
399         {
400             if (updateDirectory)
401             {
402                 this.directory.RemoveAssociations(this, this.keysToDisassociate);
403             }
404             else
405             {
406                 foreach (InstanceKey key in this.keysToDisassociate)
407                 {
408                     AssociatedKeys.Remove(key);
409                 }
410             }
411
412             this.keysToAssociate.Clear();
413             this.keysToDisassociate.Clear();
414         }
415
416         void ReadSuspendedInfo(InstanceView view)
417         {
418             string suspendedReason = null;
419             if (TryGetValue<string>(view.InstanceMetadata, WorkflowServiceNamespace.SuspendReason, out suspendedReason))
420             {
421                 IsSuspended = true;
422                 SuspendedReason = suspendedReason;
423             }
424             else
425             {
426                 IsSuspended = false;
427                 SuspendedReason = null;
428             }
429         }
430
431         void StartOperation()
432         {
433             Fx.AssertAndThrow(!this.operationInProgress, "PersistenceContext doesn't support multiple operations.");
434             this.operationInProgress = true;
435         }
436
437         void FinishOperation()
438         {
439             this.operationInProgress = false;
440         }
441
442         void OnFinishOperationHelper(Exception exception, bool ownsThrottle)
443         {
444             try
445             {
446                 if (exception is OperationCanceledException)
447                 {
448                     throw FxTrace.Exception.AsError(new CommunicationObjectAbortedException(SR.HandleFreedInDirectory, exception)); 
449                 }
450                 else if (exception is TimeoutException)
451                 {
452                     Fault();
453                 }
454             }
455             finally
456             {
457                 if (ownsThrottle)
458                 {
459                     this.directory.ReleaseThrottle();
460                 }
461                 FinishOperation();
462             }
463         }
464
465         void ThrowIfCompleted()
466         {
467             Fx.AssertAndThrow(!IsCompleted, "PersistenceContext operation invalid: instance already completed.");
468         }
469
470         void ThrowIfNotVisible()
471         {
472             // Be charitable to racy aborts.
473             if (!IsVisible)
474             {
475                 lock (ThisLock)
476                 {
477                     Fx.AssertAndThrow(State != CommunicationState.Opened,
478                         "PersistenceContext operation invalid: instance must be visible.");
479                 }
480             }
481         }
482
483         internal static bool TryGetValue<T>(IDictionary<XName, InstanceValue> data, XName key, out T value)
484         {
485             InstanceValue instanceValue;
486             value = default(T);
487             if (data.TryGetValue(key, out instanceValue) && !instanceValue.IsDeletedValue)
488             {
489                 if (instanceValue.Value is T)
490                 {
491                     value = (T)instanceValue.Value;
492                     return true;
493                 }
494                 else if (instanceValue.Value == null && !(value is ValueType))
495                 {
496                     // need to check for null assignments to value types
497                     return true;
498                 }
499                 else
500                 {
501                     if (instanceValue.Value == null)
502                     {
503                         throw FxTrace.Exception.AsError(new InstancePersistenceException(SRCore.NullAssignedToValueType(typeof(T))));
504                     }
505                     else
506                     {
507                         throw FxTrace.Exception.AsError(new InstancePersistenceException(SRCore.IncorrectValueType(typeof(T), instanceValue.Value.GetType())));
508                     }
509                 }
510             }
511             else
512             {
513                 return false;
514             }
515         }
516
517         internal TransactionWaitAsyncResult BeginEnlist(TimeSpan timeout, AsyncCallback callback, object state)
518         {
519             ThrowIfDisposedOrNotOpen();
520             // The transaction to enlist on is in Transaction.Current. The actual enlistment, if needed, will be made in 
521             // TransactionWaitAsyncResult when it is notified that it has the transaction lock.
522             return new TransactionWaitAsyncResult(Transaction.Current, this, timeout, callback, state);
523         }
524
525         [SuppressMessage(FxCop.Category.ReliabilityBasic, FxCop.Rule.CommunicationObjectThrowIf,
526             Justification = "We are intentionally re-validating the state of the instance after having acquired the transaction lock")]
527         internal void EndEnlist(IAsyncResult result)
528         {
529             TransactionWaitAsyncResult.End(result);
530             // The PersistenceContext may have been aborted while we were waiting for the transaction lock.
531             ThrowIfDisposedOrNotOpen();
532         }
533
534
535         // Returns true if the call was able to obtain the transaction lock; false if we had
536         // to queue the request for the lock.
537         internal bool QueueForTransactionLock(Transaction requestingTransaction, TransactionWaitAsyncResult txWaitAsyncResult)
538         {
539             lock (ThisLock)
540             {
541                 // If the transaction "lock" is not already held, give it to this requester.
542                 if (0 == this.lockingTransaction)
543                 {
544                     // It's possible that this particular request is not transacted.
545                     if (null != requestingTransaction)
546                     {
547                         this.lockingTransaction = requestingTransaction.GetHashCode();
548                         this.lockingTransactionObject = requestingTransaction.Clone();
549                     }
550                     // No queuing because we weren't already locked by a transaction.
551                     return true;
552                 }
553                 else if ((null != requestingTransaction) && (this.lockingTransaction == requestingTransaction.GetHashCode()))
554                 {
555                     // Same transaction as the locking transaction - no queuing.
556                     return true;
557                 }
558                 else
559                 {
560                     // Some other transaction has the lock, so add the AsyncResult to the queue.
561                     this.transactionWaiterQueue.Enqueue(txWaitAsyncResult);
562                     return false;
563                 }
564             }
565         }
566
567         // Dequeue and schedule the top element on queue of waiting TransactionWaitAsyncResult objects.
568         // Before returning this also makes the transaction represented by the dequeued TransactionWaitAsyncResult
569         // the owner of the transaction "lock" for this context.
570         internal void ScheduleNextTransactionWaiter()
571         {
572             TransactionWaitAsyncResult dequeuedWaiter = null;
573             bool detachThis = false;
574
575             lock (ThisLock)
576             {
577                 // Only try Dequeue if we have entries on the queue.
578                 bool atLeastOneSuccessfullyCompleted = false;
579                 if (0 < this.transactionWaiterQueue.Count)
580                 {
581                     while ((0 < this.transactionWaiterQueue.Count) && !atLeastOneSuccessfullyCompleted)
582                     {
583                         dequeuedWaiter = this.transactionWaiterQueue.Dequeue();
584
585                         // It's possible that the waiter didn't have a transaction.
586                         // If that is the case, we don't have a transaction to "lock" the context.
587                         if (null != dequeuedWaiter.Transaction)
588                         {
589                             this.lockingTransactionObject = dequeuedWaiter.Transaction;
590                             this.lockingTransaction = lockingTransactionObject.GetHashCode();
591                         }
592                         else
593                         {
594                             this.lockingTransaction = 0;
595                             this.lockingTransactionObject = null;
596                         }
597
598                         atLeastOneSuccessfullyCompleted = dequeuedWaiter.Complete() || atLeastOneSuccessfullyCompleted;
599
600                         if (this.Detaching)
601                         {
602                             detachThis = true;
603                             this.Detaching = false;
604                         }
605
606                         // If we are doing a permanent detach, we must have received an OnClosing or
607                         // OnFaulted while the PersistenceContext was locked for a transaction. In that
608                         // case, we want to wake up ALL waiters.
609                         if (this.IsPermanentlyRemoved)
610                         {
611                             this.lockingTransaction = 0;
612                             this.lockingTransactionObject = null;
613                             while (0 < this.transactionWaiterQueue.Count)
614                             {
615                                 dequeuedWaiter = this.transactionWaiterQueue.Dequeue();
616                                 atLeastOneSuccessfullyCompleted = dequeuedWaiter.Complete() || atLeastOneSuccessfullyCompleted;
617                             }
618                         }
619
620                         // Now we need to look for any adjacent waiters in the queue that are
621                         // waiting for the same transaction. If we still have entries on the queue,
622                         // we must have a waiterToComplete. Note that if we were doing a permanent detach,
623                         // there won't be any waiters left in the queue at this point.
624                         while (0 < this.transactionWaiterQueue.Count)
625                         {
626                             TransactionWaitAsyncResult nextWaiter = this.transactionWaiterQueue.Peek();
627                             if (0 == this.lockingTransaction)
628                             {
629                                 // We dequeue this waiter because we shouldn't block transactional waiters
630                                 // behind non-transactional waiters because there is nothing to wake up the
631                                 // transactional waiters in that case. Also set this.LockingTransaction
632                                 // to that of the next waiter.
633                                 if (null != nextWaiter.Transaction)
634                                 {
635                                     this.lockingTransactionObject = nextWaiter.Transaction;
636                                     this.lockingTransaction = this.lockingTransactionObject.GetHashCode();
637                                 }
638                             }
639                             else if (null != nextWaiter.Transaction)
640                             {
641                                 // Stop looking if the new lockingTransaction is different than
642                                 // the nextWaiter's transaction. 
643                                 if (this.lockingTransaction != nextWaiter.Transaction.GetHashCode())
644                                 {
645                                     break;  // out of the inner-while
646                                 }
647                             }
648                             else
649                             {
650                                 // The nextWaiter is non-transational, so it doesn't match the current
651                                 // lock holder, so we are done.
652                                 break;  // out of the inner-while
653                             }
654
655                             dequeuedWaiter = this.transactionWaiterQueue.Dequeue();
656                             atLeastOneSuccessfullyCompleted = dequeuedWaiter.Complete() || atLeastOneSuccessfullyCompleted;
657                         }
658                     }
659                 }
660                 if (!atLeastOneSuccessfullyCompleted)
661                 {
662                     // There are no more waiters, so the context is no longer "locked" by a transaction.
663                     this.lockingTransaction = 0;
664                     this.lockingTransactionObject = null;
665                 }
666             }
667
668             // If we are detaching and it is NOT permanently removed, finish the detach by calling RemoveInstance non-transactionally.
669             // It will be marked as permanently removed in OnClosing and OnFaulted and it will have already been removed, so we don't
670             // want to try to remove it again.
671             if (detachThis)
672             {
673                 this.directory.RemoveInstance(this, false);
674             }
675         }
676
677         bool ScheduleDetach()
678         {
679             lock (ThisLock)
680             {
681                 if (this.lockingTransaction != 0)
682                 {
683                     Detaching = true;
684                     return true;
685                 }
686             }
687             return false;
688         }
689
690         void PopulateActivationMetadata(SaveWorkflowCommand saveCommand)
691         {
692             bool saveIdentity;
693             if (!IsInitialized)
694             {
695                 Fx.Assert(this.directory.InstanceMetadataChanges != null, "We should always be non-null here.");
696                 foreach (KeyValuePair<XName, InstanceValue> pair in this.directory.InstanceMetadataChanges)
697                 {
698                     saveCommand.InstanceMetadataChanges.Add(pair.Key, pair.Value);
699                 }
700                 saveIdentity = this.workflowInstance.DefinitionIdentity != null;
701             }
702             else 
703             {
704                 saveIdentity = this.workflowInstance.HasBeenUpdated;
705             }
706
707             if (saveIdentity)
708             {
709                 if (this.workflowInstance.DefinitionIdentity != null)
710                 {
711                     saveCommand.InstanceMetadataChanges.Add(Workflow45Namespace.DefinitionIdentity, new InstanceValue(this.workflowInstance.DefinitionIdentity, InstanceValueOptions.None));
712                 }
713                 else
714                 {
715                     saveCommand.InstanceMetadataChanges.Add(Workflow45Namespace.DefinitionIdentity, InstanceValue.DeletedValue);
716                 }
717             }
718         }
719
720         class CloseAsyncResult : AsyncResult
721         {
722             PersistenceContext persistenceContext;
723
724             public CloseAsyncResult(PersistenceContext persistenceContext, AsyncCallback callback, object state)
725                 : base(callback, state)
726             {
727                 this.persistenceContext = persistenceContext;
728                 OnCompleting = new Action<AsyncResult, Exception>(OnFinishOperation);
729
730                 bool success = false;
731                 bool completeSelf = false;
732                 try
733                 {
734                     this.persistenceContext.StartOperation();
735
736                     if (this.persistenceContext.store != null)
737                     {
738                         Fx.Assert(this.persistenceContext.handle != null, "WorkflowInstance failed to call SetHandle - from OnBeginClose.");
739                         this.persistenceContext.handle.Free();
740                     }
741                     completeSelf = true;
742                     success = true;
743                 }
744                 finally
745                 {
746                     if (!success)
747                     {
748                         this.persistenceContext.FinishOperation();
749                     }
750                 }
751
752                 if (completeSelf)
753                 {
754                     base.Complete(true);
755                 }
756             }
757
758             public static void End(IAsyncResult result)
759             {
760                 AsyncResult.End<CloseAsyncResult>(result);
761             }
762
763             void OnFinishOperation(AsyncResult result, Exception exception)
764             {
765                 this.persistenceContext.FinishOperation();
766             }
767         }
768
769         class SaveAsyncResult : TransactedAsyncResult
770         {
771             static readonly AsyncCompletion handleEndExecute = new AsyncCompletion(HandleEndExecute);
772             static readonly AsyncCompletion handleEndEnlist = new AsyncCompletion(HandleEndEnlist);
773
774             readonly PersistenceContext persistenceContext;
775             readonly SaveStatus saveStatus;
776             readonly TimeoutHelper timeoutHelper;
777             readonly DependentTransaction transaction;
778
779             public SaveAsyncResult(PersistenceContext persistenceContext, IDictionary<XName, InstanceValue> instance, SaveStatus saveStatus, TimeSpan timeout,
780                 AsyncCallback callback, object state)
781                 : base(callback, state)
782             {
783                 this.persistenceContext = persistenceContext;
784                 OnCompleting = new Action<AsyncResult, Exception>(OnFinishOperation);
785
786                 this.saveStatus = saveStatus;
787
788                 bool success = false;
789                 try
790                 {
791                     this.persistenceContext.StartOperation();
792
793                     this.persistenceContext.ThrowIfCompleted();
794                     this.persistenceContext.ThrowIfNotVisible();
795                     Fx.Assert(!this.persistenceContext.IsInitialized || this.persistenceContext.IsLocked,
796                         "Should not be visible if initialized and not locked.");
797
798                     this.timeoutHelper = new TimeoutHelper(timeout);
799
800                     Transaction currentTransaction = Transaction.Current;
801                     if (currentTransaction != null)
802                     {
803                         this.transaction = currentTransaction.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
804                     }
805
806                     if (this.persistenceContext.store != null)
807                     {
808                         SaveWorkflowCommand saveCommand = new SaveWorkflowCommand();                        
809                         foreach (KeyValuePair<XName, InstanceValue> value in instance)
810                         {
811                             saveCommand.InstanceData.Add(value);
812                         }
813                         this.persistenceContext.PopulateActivationMetadata(saveCommand);
814                         if (this.persistenceContext.IsSuspended)
815                         {
816                             saveCommand.InstanceMetadataChanges.Add(WorkflowServiceNamespace.SuspendReason, new InstanceValue(this.persistenceContext.SuspendedReason));
817                         }
818                         else
819                         {
820                             saveCommand.InstanceMetadataChanges.Add(WorkflowServiceNamespace.SuspendReason, InstanceValue.DeletedValue);
821                             saveCommand.InstanceMetadataChanges.Add(WorkflowServiceNamespace.SuspendException, InstanceValue.DeletedValue);
822                         }
823                         foreach (InstanceKey key in this.persistenceContext.keysToAssociate)
824                         {
825                             saveCommand.InstanceKeysToAssociate.Add(key.Value, key.Metadata);
826                         }
827                         foreach (InstanceKey key in this.persistenceContext.keysToDisassociate)
828                         {
829                             // We are going to Complete and Disassociate with the same Save command.
830                             saveCommand.InstanceKeysToComplete.Add(key.Value);
831                             saveCommand.InstanceKeysToFree.Add(key.Value);
832                         }
833
834                         if (this.saveStatus == SaveStatus.Completed)
835                         {
836                             saveCommand.CompleteInstance = true;
837                             saveCommand.UnlockInstance = true;
838                         }
839                         else
840                         {
841                             saveCommand.UnlockInstance = this.saveStatus == SaveStatus.Unlocked;
842                         }
843
844                         IAsyncResult result = this.persistenceContext.store.BeginExecute(
845                             this.persistenceContext.handle,
846                             saveCommand,
847                             this.timeoutHelper.RemainingTime(),
848                             PrepareAsyncCompletion(SaveAsyncResult.handleEndExecute),
849                             this);
850                         if (SyncContinue(result))
851                         {
852                             Complete(true);
853                         }
854                     }
855                     else
856                     {
857                         if (this.saveStatus == SaveStatus.Completed)
858                         {
859                             this.persistenceContext.IsCompleted = true;
860                             this.persistenceContext.IsLocked = false;
861                         }
862                         else
863                         {
864                             this.persistenceContext.IsLocked = this.saveStatus != SaveStatus.Unlocked;
865                         }
866                         if (AfterSave())
867                         {
868                             Complete(true);
869                         }
870                     }
871                     success = true;
872                 }
873                 catch (OperationCanceledException exception)
874                 {
875                     throw FxTrace.Exception.AsError(new CommunicationObjectAbortedException(SR.HandleFreedInDirectory, exception)); 
876                 }
877                 catch (TimeoutException)
878                 {
879                     this.persistenceContext.Fault();
880                     throw;
881                 }
882                 finally
883                 {
884                     if (!success)
885                     {
886                         try
887                         {
888                             if (this.transaction != null)
889                             {
890                                 this.transaction.Complete();
891                             }
892                         }
893                         finally
894                         {
895                             this.persistenceContext.FinishOperation();
896                         }
897                     }
898                 }
899             }
900
901             public static void End(IAsyncResult result)
902             {
903                 AsyncResult.End<SaveAsyncResult>(result);
904             }
905
906             static bool HandleEndExecute(IAsyncResult result)
907             {
908                 SaveAsyncResult thisPtr = (SaveAsyncResult)result.AsyncState;
909                 thisPtr.persistenceContext.store.EndExecute(result);
910                 thisPtr.persistenceContext.IsCompleted = thisPtr.saveStatus == SaveStatus.Completed;
911                 thisPtr.persistenceContext.IsLocked = thisPtr.saveStatus == SaveStatus.Locked;
912                 return thisPtr.AfterSave();
913             }
914
915             bool AfterSave()
916             {
917                 this.persistenceContext.IsInitialized = true;
918
919                 if (this.saveStatus != SaveStatus.Locked)
920                 {
921                     IAsyncResult result;
922                     using (PrepareTransactionalCall(this.transaction))
923                     {
924                         result = this.persistenceContext.BeginEnlist(this.timeoutHelper.RemainingTime(), PrepareAsyncCompletion(SaveAsyncResult.handleEndEnlist), this);
925                     }
926                     return SyncContinue(result);
927                 }
928
929                 return AfterEnlist();
930             }
931
932             bool AfterEnlist()
933             {
934                 this.persistenceContext.RationalizeSavedKeys(this.saveStatus == SaveStatus.Locked);
935                 return true;
936             }
937
938             static bool HandleEndEnlist(IAsyncResult result)
939             {
940                 SaveAsyncResult thisPtr = (SaveAsyncResult)result.AsyncState;
941                 thisPtr.persistenceContext.EndEnlist(result);
942
943                 if (!thisPtr.persistenceContext.ScheduleDetach())
944                 {
945                     thisPtr.persistenceContext.directory.RemoveInstance(thisPtr.persistenceContext);
946                 }
947                 return thisPtr.AfterEnlist();
948             }
949
950             void OnFinishOperation(AsyncResult result, Exception exception)
951             {
952                 try
953                 {
954                     this.persistenceContext.OnFinishOperationHelper(exception, false);
955                 }
956                 finally
957                 {
958                     if (this.transaction != null)
959                     {
960                         this.transaction.Complete();
961                     }
962                 }
963             }
964         }
965
966         class ReleaseAsyncResult : TransactedAsyncResult
967         {
968             static readonly AsyncCompletion handleEndExecute = new AsyncCompletion(HandleEndExecute);
969             static readonly AsyncCompletion handleEndEnlist = new AsyncCompletion(HandleEndEnlist);
970
971             readonly PersistenceContext persistenceContext;
972             readonly TimeoutHelper timeoutHelper;
973             readonly DependentTransaction transaction;
974
975             public ReleaseAsyncResult(PersistenceContext persistenceContext, TimeSpan timeout, AsyncCallback callback, object state)
976                 : base(callback, state)
977             {
978                 this.persistenceContext = persistenceContext;
979                 OnCompleting = new Action<AsyncResult, Exception>(OnFinishOperation);
980
981                 bool success = false;
982                 try
983                 {
984                     this.persistenceContext.StartOperation();
985
986                     this.timeoutHelper = new TimeoutHelper(timeout);
987
988                     Transaction currentTransaction = Transaction.Current;
989                     if (currentTransaction != null)
990                     {
991                         this.transaction = currentTransaction.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
992                     }
993
994                     if (this.persistenceContext.IsVisible)
995                     {
996                         if (this.persistenceContext.store != null && this.persistenceContext.IsLocked)
997                         {
998                             SaveWorkflowCommand saveCommand = new SaveWorkflowCommand() { UnlockInstance = true };
999                             this.persistenceContext.PopulateActivationMetadata(saveCommand);
1000                             IAsyncResult result = this.persistenceContext.store.BeginExecute(
1001                                 this.persistenceContext.handle,
1002                                 saveCommand,
1003                                 this.timeoutHelper.RemainingTime(),
1004                                 PrepareAsyncCompletion(ReleaseAsyncResult.handleEndExecute),
1005                                 this);
1006                             if (SyncContinue(result))
1007                             {
1008                                 Complete(true);
1009                             }
1010                         }
1011                         else
1012                         {
1013                             if (AfterUnlock())
1014                             {
1015                                 Complete(true);
1016                             }
1017                         }
1018                     }
1019                     else
1020                     {
1021                         // If we're not visible because we were aborted in a ----, the caller needs to know.
1022                         lock (this.persistenceContext.ThisLock)
1023                         {
1024                             this.persistenceContext.ThrowIfDisposedOrNotOpen();
1025                         }
1026                         Complete(true);
1027                     }
1028                     success = true;
1029                 }
1030                 catch (OperationCanceledException exception)
1031                 {
1032                     throw FxTrace.Exception.AsError(new CommunicationObjectAbortedException(SR.HandleFreedInDirectory, exception)); 
1033                 }
1034                 catch (TimeoutException)
1035                 {
1036                     this.persistenceContext.Fault();
1037                     throw;
1038                 }
1039                 finally
1040                 {
1041                     if (!success)
1042                     {
1043                         try
1044                         {
1045                             if (this.transaction != null)
1046                             {
1047                                 this.transaction.Complete();
1048                             }
1049                         }
1050                         finally
1051                         {
1052                             this.persistenceContext.FinishOperation();
1053                         }
1054                     }
1055                 }
1056             }
1057
1058             public static void End(IAsyncResult result)
1059             {
1060                 AsyncResult.End<ReleaseAsyncResult>(result);
1061             }
1062
1063             static bool HandleEndExecute(IAsyncResult result)
1064             {
1065                 ReleaseAsyncResult thisPtr = (ReleaseAsyncResult)result.AsyncState;
1066                 thisPtr.persistenceContext.store.EndExecute(result);
1067                 return thisPtr.AfterUnlock();
1068             }
1069
1070             bool AfterUnlock()
1071             {
1072                 this.persistenceContext.IsLocked = false;
1073
1074                 IAsyncResult result;
1075                 using (PrepareTransactionalCall(this.transaction))
1076                 {
1077                     result = this.persistenceContext.BeginEnlist(this.timeoutHelper.RemainingTime(), PrepareAsyncCompletion(ReleaseAsyncResult.handleEndEnlist), this);
1078                 }
1079                 return SyncContinue(result);
1080             }
1081
1082             static bool HandleEndEnlist(IAsyncResult result)
1083             {
1084                 ReleaseAsyncResult thisPtr = (ReleaseAsyncResult)result.AsyncState;
1085                 thisPtr.persistenceContext.EndEnlist(result);
1086
1087                 if (!thisPtr.persistenceContext.ScheduleDetach())
1088                 {
1089                     thisPtr.persistenceContext.directory.RemoveInstance(thisPtr.persistenceContext);
1090                 }
1091
1092                 foreach (InstanceKey key in thisPtr.persistenceContext.keysToAssociate)
1093                 {
1094                     thisPtr.persistenceContext.AssociatedKeys.Remove(key);
1095                 }
1096                 thisPtr.persistenceContext.keysToAssociate.Clear();
1097                 thisPtr.persistenceContext.keysToDisassociate.Clear();
1098
1099                 return true;
1100             }
1101
1102             void OnFinishOperation(AsyncResult result, Exception exception)
1103             {
1104                 try
1105                 {
1106                     this.persistenceContext.OnFinishOperationHelper(exception, false);
1107                 }
1108                 finally
1109                 {
1110                     if (this.transaction != null)
1111                     {
1112                         this.transaction.Complete();
1113                     }
1114                 }
1115             }
1116         }
1117
1118         class AssociateKeysAsyncResult : TransactedAsyncResult
1119         {
1120             static readonly AsyncCompletion handleEndExecute = new AsyncCompletion(HandleEndExecute);
1121             static readonly AsyncCompletion handleEndEnlist = new AsyncCompletion(HandleEndEnlist);
1122
1123             readonly PersistenceContext persistenceContext;
1124             readonly bool applicationKeys;
1125             readonly ICollection<InstanceKey> keysToAssociate;
1126             readonly TimeoutHelper timeoutHelper;
1127             readonly DependentTransaction transaction;
1128
1129             public AssociateKeysAsyncResult(PersistenceContext persistenceContext, ICollection<InstanceKey> associatedKeys, TimeSpan timeout,
1130                 bool applicationKeys, AsyncCallback callback, object state)
1131                 : base(callback, state)
1132             {
1133                 this.persistenceContext = persistenceContext;
1134                 this.applicationKeys = applicationKeys;
1135                 this.keysToAssociate = associatedKeys;
1136                 this.timeoutHelper = new TimeoutHelper(timeout);
1137
1138                 OnCompleting = new Action<AsyncResult, Exception>(OnFinishOperation);
1139
1140                 bool success = false;
1141                 try
1142                 {
1143                     this.persistenceContext.StartOperation();
1144
1145                     this.persistenceContext.ThrowIfCompleted();
1146                     this.persistenceContext.ThrowIfNotVisible();
1147                     Fx.Assert(!this.persistenceContext.IsInitialized || this.persistenceContext.IsLocked,
1148                         "Should not be visible if initialized and not locked.");
1149
1150                     Transaction currentTransaction = Transaction.Current;
1151                     if (currentTransaction != null)
1152                     {
1153                         this.transaction = currentTransaction.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
1154                     }
1155
1156                     // We need to get the transaction lock and enlist on the transaction, if there is one.
1157                     IAsyncResult enlistResult = persistenceContext.BeginEnlist(this.timeoutHelper.RemainingTime(), 
1158                                       this.PrepareAsyncCompletion(handleEndEnlist), this);
1159                     if (SyncContinue(enlistResult))
1160                     {
1161                         Complete(true);
1162                     }
1163                     success = true;
1164                 }
1165                 catch (InstancePersistenceException)
1166                 {
1167                     this.persistenceContext.Fault();
1168                     throw;
1169                 }
1170                 catch (OperationCanceledException exception)
1171                 {
1172                     throw FxTrace.Exception.AsError(new CommunicationObjectAbortedException(SR.HandleFreedInDirectory, exception)); 
1173                 }
1174                 catch (TimeoutException)
1175                 {
1176                     this.persistenceContext.Fault();
1177                     throw;
1178                 }
1179                 finally
1180                 {
1181                     if (!success)
1182                     {
1183                         try
1184                         {
1185                             // We need to complete our dependent clone because OnFinishOperation will not
1186                             // get called in this case.
1187                             if (this.transaction != null)
1188                             {
1189                                 this.transaction.Complete();
1190                             }
1191                         }
1192                         finally
1193                         {
1194                             this.persistenceContext.FinishOperation();
1195                         }
1196                     }
1197                 }
1198             }
1199
1200             public static void End(IAsyncResult result)
1201             {
1202                 AsyncResult.End<AssociateKeysAsyncResult>(result);
1203             }
1204
1205             static bool HandleEndExecute(IAsyncResult result)
1206             {
1207                 AssociateKeysAsyncResult thisPtr = (AssociateKeysAsyncResult)result.AsyncState;
1208                 thisPtr.persistenceContext.store.EndExecute(result);
1209                 return thisPtr.AfterUpdate();
1210             }
1211
1212             static bool HandleEndEnlist(IAsyncResult result)
1213             {
1214                 AssociateKeysAsyncResult thisPtr = (AssociateKeysAsyncResult)result.AsyncState;
1215                 bool returnValue = false;
1216
1217                 if (!thisPtr.persistenceContext.directory.TryAddAssociations(
1218                     thisPtr.persistenceContext,
1219                     thisPtr.keysToAssociate,
1220                     thisPtr.persistenceContext.keysToAssociate,
1221                     thisPtr.applicationKeys ? thisPtr.persistenceContext.keysToDisassociate : null))
1222                 {
1223                     lock (thisPtr.persistenceContext.ThisLock)
1224                     {
1225                         thisPtr.persistenceContext.ThrowIfDisposedOrNotOpen();
1226                     }
1227                     throw Fx.AssertAndThrow("Should only fail to add keys in a ---- with abort.");
1228                 }
1229
1230                 if (thisPtr.persistenceContext.directory.ConsistencyScope == DurableConsistencyScope.Global)
1231                 {
1232                     // Only do a SetKeysToPersist or Save command if we have keys to associate or disassociate.
1233                     // It's possible that we got invoked with a key that was already in the
1234                     // AssociatedKeys collection.
1235                     if ((thisPtr.persistenceContext.keysToAssociate.Count != 0) ||
1236                         ((thisPtr.persistenceContext.keysToDisassociate.Count != 0) &&
1237                          (thisPtr.applicationKeys)))
1238                     {
1239                         if (thisPtr.persistenceContext.store != null)
1240                         {
1241                             SaveWorkflowCommand saveCommand = new SaveWorkflowCommand();
1242                             foreach (InstanceKey key in thisPtr.persistenceContext.keysToAssociate)
1243                             {                                
1244                                 saveCommand.InstanceKeysToAssociate.Add(key.Value, key.Metadata);
1245                             }
1246                             if (thisPtr.applicationKeys)
1247                             {
1248                                 foreach (InstanceKey key in thisPtr.persistenceContext.keysToDisassociate)
1249                                 {
1250                                     // We are going to Complete and Disassociate with the same Save command.
1251                                     saveCommand.InstanceKeysToComplete.Add(key.Value);
1252                                     saveCommand.InstanceKeysToFree.Add(key.Value);
1253                                 }
1254                             }
1255                             IAsyncResult beginExecuteResult = null;
1256                             using (thisPtr.PrepareTransactionalCall(thisPtr.transaction))
1257                             {
1258                                 beginExecuteResult = thisPtr.persistenceContext.store.BeginExecute(
1259                                     thisPtr.persistenceContext.handle,
1260                                     saveCommand,
1261                                     thisPtr.timeoutHelper.RemainingTime(),
1262                                     thisPtr.PrepareAsyncCompletion(AssociateKeysAsyncResult.handleEndExecute),
1263                                     thisPtr);
1264                             }
1265                             returnValue = thisPtr.SyncContinue(beginExecuteResult);
1266                         }
1267                     }
1268                     else
1269                     {
1270                         returnValue = thisPtr.AfterUpdate();
1271                     }
1272                 }
1273                 else
1274                 {
1275                     returnValue = thisPtr.AfterUpdate();
1276                 }
1277
1278                 return returnValue;
1279             }
1280
1281             bool AfterUpdate()
1282             {
1283                 if (this.applicationKeys)
1284                 {
1285                     this.persistenceContext.RationalizeSavedKeys(true);
1286                 }
1287                 else
1288                 {
1289                     this.persistenceContext.keysToAssociate.Clear();
1290                 }
1291
1292                 return true;
1293             }
1294
1295             void OnFinishOperation(AsyncResult result, Exception exception)
1296             {
1297                 if (exception is InstancePersistenceException)
1298                 {
1299                     this.persistenceContext.Fault();
1300                 }
1301                 try
1302                 {
1303                     this.persistenceContext.OnFinishOperationHelper(exception, false);
1304                 }
1305                 finally
1306                 {
1307                     // We are all done. If we have a savedTransaction, we need to complete it now.
1308                     if (this.transaction != null)
1309                     {
1310                         this.transaction.Complete();
1311                     }
1312                 }
1313             }
1314         }
1315
1316         class UpdateSuspendMetadataAsyncResult : AsyncResult
1317         {
1318             static readonly AsyncCompletion handleEndExecute = new AsyncCompletion(HandleEndExecute);
1319
1320             readonly PersistenceContext persistenceContext;
1321             readonly TimeoutHelper timeoutHelper;
1322             readonly DependentTransaction transaction;
1323
1324             public UpdateSuspendMetadataAsyncResult(PersistenceContext persistenceContext, Exception reason, TimeSpan timeout, AsyncCallback callback, object state)
1325                 : base(callback, state)
1326             {
1327                 this.persistenceContext = persistenceContext;
1328                 OnCompleting = new Action<AsyncResult, Exception>(OnFinishOperation);
1329
1330                 bool success = false;
1331                 try
1332                 {
1333                     this.persistenceContext.StartOperation();
1334
1335                     this.timeoutHelper = new TimeoutHelper(timeout);
1336
1337                     Transaction currentTransaction = Transaction.Current;
1338                     if (currentTransaction != null)
1339                     {
1340                         this.transaction = currentTransaction.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
1341                     }
1342
1343                     if (this.persistenceContext.store != null)
1344                     {
1345                         SaveWorkflowCommand saveCommand = new SaveWorkflowCommand();
1346                         this.persistenceContext.PopulateActivationMetadata(saveCommand);
1347                         saveCommand.InstanceMetadataChanges[WorkflowServiceNamespace.SuspendReason] = new InstanceValue(reason.Message);
1348                         saveCommand.InstanceMetadataChanges[WorkflowServiceNamespace.SuspendException] = new InstanceValue(reason, InstanceValueOptions.WriteOnly | InstanceValueOptions.Optional);
1349                         saveCommand.UnlockInstance = true;
1350
1351                         IAsyncResult result = this.persistenceContext.store.BeginExecute(
1352                             this.persistenceContext.handle,
1353                             saveCommand,
1354                             this.timeoutHelper.RemainingTime(),
1355                             PrepareAsyncCompletion(handleEndExecute),
1356                             this);
1357                         if (SyncContinue(result))
1358                         {
1359                             Complete(true);
1360                         }
1361                     }
1362                     else
1363                     {
1364                         Complete(true);
1365                     }
1366                     success = true;
1367                 }
1368                 catch (OperationCanceledException exception)
1369                 {
1370                     throw FxTrace.Exception.AsError(new CommunicationObjectAbortedException(SR.HandleFreedInDirectory, exception));
1371                 }
1372                 catch (TimeoutException)
1373                 {
1374                     this.persistenceContext.Fault();
1375                     throw;
1376                 }
1377                 finally
1378                 {
1379                     if (!success)
1380                     {
1381                         try
1382                         {
1383                             if (this.transaction != null)
1384                             {
1385                                 this.transaction.Complete();
1386                             }
1387                         }
1388                         finally
1389                         {
1390                             this.persistenceContext.FinishOperation();
1391                         }
1392                     }
1393                 }
1394             }
1395
1396             public static void End(IAsyncResult result)
1397             {
1398                 AsyncResult.End<UpdateSuspendMetadataAsyncResult>(result);
1399             }
1400
1401             static bool HandleEndExecute(IAsyncResult result)
1402             {
1403                 UpdateSuspendMetadataAsyncResult thisPtr = (UpdateSuspendMetadataAsyncResult)result.AsyncState;
1404                 thisPtr.persistenceContext.store.EndExecute(result);
1405                 return true;
1406             }
1407
1408             void OnFinishOperation(AsyncResult result, Exception exception)
1409             {
1410                 try
1411                 {
1412                     this.persistenceContext.OnFinishOperationHelper(exception, false);
1413                 }
1414                 finally
1415                 {
1416                     if (this.transaction != null)
1417                     {
1418                         this.transaction.Complete();
1419                     }
1420                 }
1421             }
1422         }
1423     }
1424 }