1 //-----------------------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 //-----------------------------------------------------------------------------
5 namespace System.ServiceModel.Activities.Dispatcher
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;
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;
21 sealed class PersistenceContext : CommunicationObject
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>();
26 readonly PersistenceProviderDirectory directory;
28 readonly InstanceStore store;
29 readonly InstanceHandle handle;
31 readonly HashSet<InstanceKey> keysToAssociate;
32 readonly HashSet<InstanceKey> keysToDisassociate;
34 static TimeSpan defaultOpenTimeout = TimeSpan.FromSeconds(90);
35 static TimeSpan defaultCloseTimeout = TimeSpan.FromSeconds(90);
38 bool operationInProgress;
40 WorkflowServiceInstance workflowInstance;
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;
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;
59 // Used by PPD when there is no store.
60 internal PersistenceContext(PersistenceProviderDirectory directory,
61 Guid instanceId, InstanceKey key, IEnumerable<InstanceKey> associatedKeys)
63 Fx.Assert(directory != null, "Directory is null in PersistenceContext.");
64 Fx.Assert(instanceId != Guid.Empty, "Cannot provide an empty instance ID.");
66 this.directory = directory;
68 InstanceId = instanceId;
70 AssociatedKeys = associatedKeys != null ? new HashSet<InstanceKey>(associatedKeys) :
71 new HashSet<InstanceKey>();
72 if (key != null && !AssociatedKeys.Contains(key))
74 AssociatedKeys.Add(key);
77 this.keysToAssociate = new HashSet<InstanceKey>(AssociatedKeys);
78 this.keysToDisassociate = new HashSet<InstanceKey>();
80 this.lockingTransaction = 0;
81 this.Detaching = false;
82 this.transactionWaiterQueue = new Queue<TransactionWaitAsyncResult>();
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)
91 Fx.Assert(store != null, "Null store passed to PersistenceContext.");
92 Fx.Assert(handle != null, "Null handle passed to PersistenceContext.");
97 IsInitialized = !newInstance;
102 ReadSuspendedInfo(view);
105 // If we were loaded or we locked the instance, the keys will have been sync'd.
106 if (IsInitialized || IsLocked)
108 RationalizeSavedKeys(false);
113 Fx.Assert(view != null, "View must be specified on an initialized instance.");
114 WorkflowIdentity definitionIdentity;
116 if (!TryGetValue<WorkflowIdentity>(view.InstanceMetadata, Workflow45Namespace.DefinitionIdentity, out definitionIdentity))
118 definitionIdentity = null;
121 this.workflowInstance = this.directory.InitializeInstance(InstanceId, this, definitionIdentity, updatedIdentity, view.InstanceData, null);
125 public Guid InstanceId { get; private set; }
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; }
132 public bool IsSuspended { get; set; }
133 public string SuspendedReason { get; set; }
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
142 public bool CanPersist
146 return (this.store != null);
150 public bool IsHandleValid
154 return this.handle == null || this.handle.IsValid;
158 internal Transaction LockingTransaction
164 ThrowIfDisposedOrNotOpen();
165 return this.lockingTransactionObject;
171 internal bool IsPermanentlyRemoved { get; set; }
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; }
178 protected override TimeSpan DefaultCloseTimeout { get { return defaultCloseTimeout; } }
179 protected override TimeSpan DefaultOpenTimeout { get { return defaultOpenTimeout; } }
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)
186 ThrowIfDisposedOrNotOpen();
187 Fx.Assert(expiredKeys != null, "'expiredKeys' parameter to DisassociateKeys cannot be null.");
194 Fx.Assert(!IsInitialized || IsLocked, "Should not be visible if initialized and not locked.");
196 foreach (InstanceKey key in expiredKeys)
198 if (AssociatedKeys.Contains(key) && !this.keysToDisassociate.Contains(key))
200 this.keysToDisassociate.Add(key);
201 this.keysToAssociate.Remove(key);
205 Fx.Assert(!this.keysToAssociate.Contains(key), "Cannot be planning to associate this key.");
215 public IAsyncResult BeginSave(
216 IDictionary<XName, InstanceValue> instance,
217 SaveStatus saveStatus,
219 AsyncCallback callback,
222 ThrowIfDisposedOrNotOpen();
223 Fx.AssertAndThrow(instance != null, "'instance' parameter to BeginSave cannot be null.");
225 return new SaveAsyncResult(this, instance, saveStatus, timeout, callback, state);
228 public void EndSave(IAsyncResult result)
230 SaveAsyncResult.End(result);
233 public IAsyncResult BeginRelease(TimeSpan timeout, AsyncCallback callback, object state)
235 ThrowIfDisposedOrNotOpen();
237 return new ReleaseAsyncResult(this, timeout, callback, state);
240 public void EndRelease(IAsyncResult result)
242 ReleaseAsyncResult.End(result);
245 public IAsyncResult BeginAssociateKeys(
246 ICollection<InstanceKey> associatedKeys, TimeSpan timeout, AsyncCallback callback, object state)
248 return BeginAssociateKeysHelper(associatedKeys, timeout, true, callback, state);
251 internal IAsyncResult BeginAssociateInfrastructureKeys(
252 ICollection<InstanceKey> associatedKeys, TimeSpan timeout, AsyncCallback callback, object state)
254 return BeginAssociateKeysHelper(associatedKeys, timeout, true, callback, state);
257 IAsyncResult BeginAssociateKeysHelper(ICollection<InstanceKey> associatedKeys,
258 TimeSpan timeout, bool applicationKeys, AsyncCallback callback, object state)
260 ThrowIfDisposedOrNotOpen();
261 Fx.Assert(associatedKeys != null, "'associatedKeys' parameter to BeginAssociateKeys cannot be null.");
263 return new AssociateKeysAsyncResult(this, associatedKeys, timeout, applicationKeys, callback, state);
266 public void EndAssociateKeys(IAsyncResult result)
268 AssociateKeysAsyncResult.End(result);
271 internal void EndAssociateInfrastructureKeys(IAsyncResult result)
273 AssociateKeysAsyncResult.End(result);
276 // UpdateSuspendMetadata and Unlock instance
277 public IAsyncResult BeginUpdateSuspendMetadata(Exception reason, TimeSpan timeout, AsyncCallback callback, object state)
279 ThrowIfDisposedOrNotOpen();
281 return new UpdateSuspendMetadataAsyncResult(this, reason, timeout, callback, state);
284 public void EndUpdateSuspendMetadata(IAsyncResult result)
286 UpdateSuspendMetadataAsyncResult.End(result);
289 public WorkflowServiceInstance GetInstance(WorkflowGetInstanceContext parameters)
291 if (this.workflowInstance == null && parameters != null)
295 ThrowIfDisposedOrNotOpen();
297 if (this.workflowInstance == null)
301 WorkflowServiceInstance result;
302 if (parameters.WorkflowHostingEndpoint != null)
304 WorkflowHostingResponseContext responseContext = new WorkflowHostingResponseContext();
305 WorkflowCreationContext creationContext = parameters.WorkflowHostingEndpoint.OnGetCreationContext(parameters.Inputs, parameters.OperationContext, InstanceId, responseContext);
306 if (creationContext == null)
308 throw FxTrace.Exception.AsError(WorkflowHostingEndpoint.CreateDispatchFaultException());
310 result = this.directory.InitializeInstance(InstanceId, this, null, creationContext);
313 parameters.WorkflowCreationContext = creationContext;
314 parameters.WorkflowHostingResponseContext = responseContext;
318 result = this.directory.InitializeInstance(InstanceId, this, null, null);
320 this.workflowInstance = result;
324 if (this.workflowInstance == null)
332 return this.workflowInstance;
335 protected override void OnAbort()
337 if (this.handle != null)
343 protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state)
345 return new CloseAsyncResult(this, callback, state);
348 protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
350 return new CompletedAsyncResult(callback, state);
353 protected override void OnClose(TimeSpan timeout)
359 if (this.store != null)
370 protected override void OnEndClose(IAsyncResult result)
372 CloseAsyncResult.End(result);
375 protected override void OnEndOpen(IAsyncResult result)
377 CompletedAsyncResult.End(result);
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)
386 protected override void OnClosing()
389 this.directory.RemoveInstance(this, true);
392 protected override void OnFaulted()
395 this.directory.RemoveInstance(this, true);
398 void RationalizeSavedKeys(bool updateDirectory)
402 this.directory.RemoveAssociations(this, this.keysToDisassociate);
406 foreach (InstanceKey key in this.keysToDisassociate)
408 AssociatedKeys.Remove(key);
412 this.keysToAssociate.Clear();
413 this.keysToDisassociate.Clear();
416 void ReadSuspendedInfo(InstanceView view)
418 string suspendedReason = null;
419 if (TryGetValue<string>(view.InstanceMetadata, WorkflowServiceNamespace.SuspendReason, out suspendedReason))
422 SuspendedReason = suspendedReason;
427 SuspendedReason = null;
431 void StartOperation()
433 Fx.AssertAndThrow(!this.operationInProgress, "PersistenceContext doesn't support multiple operations.");
434 this.operationInProgress = true;
437 void FinishOperation()
439 this.operationInProgress = false;
442 void OnFinishOperationHelper(Exception exception, bool ownsThrottle)
446 if (exception is OperationCanceledException)
448 throw FxTrace.Exception.AsError(new CommunicationObjectAbortedException(SR.HandleFreedInDirectory, exception));
450 else if (exception is TimeoutException)
459 this.directory.ReleaseThrottle();
465 void ThrowIfCompleted()
467 Fx.AssertAndThrow(!IsCompleted, "PersistenceContext operation invalid: instance already completed.");
470 void ThrowIfNotVisible()
472 // Be charitable to racy aborts.
477 Fx.AssertAndThrow(State != CommunicationState.Opened,
478 "PersistenceContext operation invalid: instance must be visible.");
483 internal static bool TryGetValue<T>(IDictionary<XName, InstanceValue> data, XName key, out T value)
485 InstanceValue instanceValue;
487 if (data.TryGetValue(key, out instanceValue) && !instanceValue.IsDeletedValue)
489 if (instanceValue.Value is T)
491 value = (T)instanceValue.Value;
494 else if (instanceValue.Value == null && !(value is ValueType))
496 // need to check for null assignments to value types
501 if (instanceValue.Value == null)
503 throw FxTrace.Exception.AsError(new InstancePersistenceException(SRCore.NullAssignedToValueType(typeof(T))));
507 throw FxTrace.Exception.AsError(new InstancePersistenceException(SRCore.IncorrectValueType(typeof(T), instanceValue.Value.GetType())));
517 internal TransactionWaitAsyncResult BeginEnlist(TimeSpan timeout, AsyncCallback callback, object state)
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);
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)
529 TransactionWaitAsyncResult.End(result);
530 // The PersistenceContext may have been aborted while we were waiting for the transaction lock.
531 ThrowIfDisposedOrNotOpen();
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)
541 // If the transaction "lock" is not already held, give it to this requester.
542 if (0 == this.lockingTransaction)
544 // It's possible that this particular request is not transacted.
545 if (null != requestingTransaction)
547 this.lockingTransaction = requestingTransaction.GetHashCode();
548 this.lockingTransactionObject = requestingTransaction.Clone();
550 // No queuing because we weren't already locked by a transaction.
553 else if ((null != requestingTransaction) && (this.lockingTransaction == requestingTransaction.GetHashCode()))
555 // Same transaction as the locking transaction - no queuing.
560 // Some other transaction has the lock, so add the AsyncResult to the queue.
561 this.transactionWaiterQueue.Enqueue(txWaitAsyncResult);
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()
572 TransactionWaitAsyncResult dequeuedWaiter = null;
573 bool detachThis = false;
577 // Only try Dequeue if we have entries on the queue.
578 bool atLeastOneSuccessfullyCompleted = false;
579 if (0 < this.transactionWaiterQueue.Count)
581 while ((0 < this.transactionWaiterQueue.Count) && !atLeastOneSuccessfullyCompleted)
583 dequeuedWaiter = this.transactionWaiterQueue.Dequeue();
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)
589 this.lockingTransactionObject = dequeuedWaiter.Transaction;
590 this.lockingTransaction = lockingTransactionObject.GetHashCode();
594 this.lockingTransaction = 0;
595 this.lockingTransactionObject = null;
598 atLeastOneSuccessfullyCompleted = dequeuedWaiter.Complete() || atLeastOneSuccessfullyCompleted;
603 this.Detaching = false;
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)
611 this.lockingTransaction = 0;
612 this.lockingTransactionObject = null;
613 while (0 < this.transactionWaiterQueue.Count)
615 dequeuedWaiter = this.transactionWaiterQueue.Dequeue();
616 atLeastOneSuccessfullyCompleted = dequeuedWaiter.Complete() || atLeastOneSuccessfullyCompleted;
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)
626 TransactionWaitAsyncResult nextWaiter = this.transactionWaiterQueue.Peek();
627 if (0 == this.lockingTransaction)
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)
635 this.lockingTransactionObject = nextWaiter.Transaction;
636 this.lockingTransaction = this.lockingTransactionObject.GetHashCode();
639 else if (null != nextWaiter.Transaction)
641 // Stop looking if the new lockingTransaction is different than
642 // the nextWaiter's transaction.
643 if (this.lockingTransaction != nextWaiter.Transaction.GetHashCode())
645 break; // out of the inner-while
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
655 dequeuedWaiter = this.transactionWaiterQueue.Dequeue();
656 atLeastOneSuccessfullyCompleted = dequeuedWaiter.Complete() || atLeastOneSuccessfullyCompleted;
660 if (!atLeastOneSuccessfullyCompleted)
662 // There are no more waiters, so the context is no longer "locked" by a transaction.
663 this.lockingTransaction = 0;
664 this.lockingTransactionObject = null;
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.
673 this.directory.RemoveInstance(this, false);
677 bool ScheduleDetach()
681 if (this.lockingTransaction != 0)
690 void PopulateActivationMetadata(SaveWorkflowCommand saveCommand)
695 Fx.Assert(this.directory.InstanceMetadataChanges != null, "We should always be non-null here.");
696 foreach (KeyValuePair<XName, InstanceValue> pair in this.directory.InstanceMetadataChanges)
698 saveCommand.InstanceMetadataChanges.Add(pair.Key, pair.Value);
700 saveIdentity = this.workflowInstance.DefinitionIdentity != null;
704 saveIdentity = this.workflowInstance.HasBeenUpdated;
709 if (this.workflowInstance.DefinitionIdentity != null)
711 saveCommand.InstanceMetadataChanges.Add(Workflow45Namespace.DefinitionIdentity, new InstanceValue(this.workflowInstance.DefinitionIdentity, InstanceValueOptions.None));
715 saveCommand.InstanceMetadataChanges.Add(Workflow45Namespace.DefinitionIdentity, InstanceValue.DeletedValue);
720 class CloseAsyncResult : AsyncResult
722 PersistenceContext persistenceContext;
724 public CloseAsyncResult(PersistenceContext persistenceContext, AsyncCallback callback, object state)
725 : base(callback, state)
727 this.persistenceContext = persistenceContext;
728 OnCompleting = new Action<AsyncResult, Exception>(OnFinishOperation);
730 bool success = false;
731 bool completeSelf = false;
734 this.persistenceContext.StartOperation();
736 if (this.persistenceContext.store != null)
738 Fx.Assert(this.persistenceContext.handle != null, "WorkflowInstance failed to call SetHandle - from OnBeginClose.");
739 this.persistenceContext.handle.Free();
748 this.persistenceContext.FinishOperation();
758 public static void End(IAsyncResult result)
760 AsyncResult.End<CloseAsyncResult>(result);
763 void OnFinishOperation(AsyncResult result, Exception exception)
765 this.persistenceContext.FinishOperation();
769 class SaveAsyncResult : TransactedAsyncResult
771 static readonly AsyncCompletion handleEndExecute = new AsyncCompletion(HandleEndExecute);
772 static readonly AsyncCompletion handleEndEnlist = new AsyncCompletion(HandleEndEnlist);
774 readonly PersistenceContext persistenceContext;
775 readonly SaveStatus saveStatus;
776 readonly TimeoutHelper timeoutHelper;
777 readonly DependentTransaction transaction;
779 public SaveAsyncResult(PersistenceContext persistenceContext, IDictionary<XName, InstanceValue> instance, SaveStatus saveStatus, TimeSpan timeout,
780 AsyncCallback callback, object state)
781 : base(callback, state)
783 this.persistenceContext = persistenceContext;
784 OnCompleting = new Action<AsyncResult, Exception>(OnFinishOperation);
786 this.saveStatus = saveStatus;
788 bool success = false;
791 this.persistenceContext.StartOperation();
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.");
798 this.timeoutHelper = new TimeoutHelper(timeout);
800 Transaction currentTransaction = Transaction.Current;
801 if (currentTransaction != null)
803 this.transaction = currentTransaction.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
806 if (this.persistenceContext.store != null)
808 SaveWorkflowCommand saveCommand = new SaveWorkflowCommand();
809 foreach (KeyValuePair<XName, InstanceValue> value in instance)
811 saveCommand.InstanceData.Add(value);
813 this.persistenceContext.PopulateActivationMetadata(saveCommand);
814 if (this.persistenceContext.IsSuspended)
816 saveCommand.InstanceMetadataChanges.Add(WorkflowServiceNamespace.SuspendReason, new InstanceValue(this.persistenceContext.SuspendedReason));
820 saveCommand.InstanceMetadataChanges.Add(WorkflowServiceNamespace.SuspendReason, InstanceValue.DeletedValue);
821 saveCommand.InstanceMetadataChanges.Add(WorkflowServiceNamespace.SuspendException, InstanceValue.DeletedValue);
823 foreach (InstanceKey key in this.persistenceContext.keysToAssociate)
825 saveCommand.InstanceKeysToAssociate.Add(key.Value, key.Metadata);
827 foreach (InstanceKey key in this.persistenceContext.keysToDisassociate)
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);
834 if (this.saveStatus == SaveStatus.Completed)
836 saveCommand.CompleteInstance = true;
837 saveCommand.UnlockInstance = true;
841 saveCommand.UnlockInstance = this.saveStatus == SaveStatus.Unlocked;
844 IAsyncResult result = this.persistenceContext.store.BeginExecute(
845 this.persistenceContext.handle,
847 this.timeoutHelper.RemainingTime(),
848 PrepareAsyncCompletion(SaveAsyncResult.handleEndExecute),
850 if (SyncContinue(result))
857 if (this.saveStatus == SaveStatus.Completed)
859 this.persistenceContext.IsCompleted = true;
860 this.persistenceContext.IsLocked = false;
864 this.persistenceContext.IsLocked = this.saveStatus != SaveStatus.Unlocked;
873 catch (OperationCanceledException exception)
875 throw FxTrace.Exception.AsError(new CommunicationObjectAbortedException(SR.HandleFreedInDirectory, exception));
877 catch (TimeoutException)
879 this.persistenceContext.Fault();
888 if (this.transaction != null)
890 this.transaction.Complete();
895 this.persistenceContext.FinishOperation();
901 public static void End(IAsyncResult result)
903 AsyncResult.End<SaveAsyncResult>(result);
906 static bool HandleEndExecute(IAsyncResult result)
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();
917 this.persistenceContext.IsInitialized = true;
919 if (this.saveStatus != SaveStatus.Locked)
922 using (PrepareTransactionalCall(this.transaction))
924 result = this.persistenceContext.BeginEnlist(this.timeoutHelper.RemainingTime(), PrepareAsyncCompletion(SaveAsyncResult.handleEndEnlist), this);
926 return SyncContinue(result);
929 return AfterEnlist();
934 this.persistenceContext.RationalizeSavedKeys(this.saveStatus == SaveStatus.Locked);
938 static bool HandleEndEnlist(IAsyncResult result)
940 SaveAsyncResult thisPtr = (SaveAsyncResult)result.AsyncState;
941 thisPtr.persistenceContext.EndEnlist(result);
943 if (!thisPtr.persistenceContext.ScheduleDetach())
945 thisPtr.persistenceContext.directory.RemoveInstance(thisPtr.persistenceContext);
947 return thisPtr.AfterEnlist();
950 void OnFinishOperation(AsyncResult result, Exception exception)
954 this.persistenceContext.OnFinishOperationHelper(exception, false);
958 if (this.transaction != null)
960 this.transaction.Complete();
966 class ReleaseAsyncResult : TransactedAsyncResult
968 static readonly AsyncCompletion handleEndExecute = new AsyncCompletion(HandleEndExecute);
969 static readonly AsyncCompletion handleEndEnlist = new AsyncCompletion(HandleEndEnlist);
971 readonly PersistenceContext persistenceContext;
972 readonly TimeoutHelper timeoutHelper;
973 readonly DependentTransaction transaction;
975 public ReleaseAsyncResult(PersistenceContext persistenceContext, TimeSpan timeout, AsyncCallback callback, object state)
976 : base(callback, state)
978 this.persistenceContext = persistenceContext;
979 OnCompleting = new Action<AsyncResult, Exception>(OnFinishOperation);
981 bool success = false;
984 this.persistenceContext.StartOperation();
986 this.timeoutHelper = new TimeoutHelper(timeout);
988 Transaction currentTransaction = Transaction.Current;
989 if (currentTransaction != null)
991 this.transaction = currentTransaction.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
994 if (this.persistenceContext.IsVisible)
996 if (this.persistenceContext.store != null && this.persistenceContext.IsLocked)
998 SaveWorkflowCommand saveCommand = new SaveWorkflowCommand() { UnlockInstance = true };
999 this.persistenceContext.PopulateActivationMetadata(saveCommand);
1000 IAsyncResult result = this.persistenceContext.store.BeginExecute(
1001 this.persistenceContext.handle,
1003 this.timeoutHelper.RemainingTime(),
1004 PrepareAsyncCompletion(ReleaseAsyncResult.handleEndExecute),
1006 if (SyncContinue(result))
1021 // If we're not visible because we were aborted in a ----, the caller needs to know.
1022 lock (this.persistenceContext.ThisLock)
1024 this.persistenceContext.ThrowIfDisposedOrNotOpen();
1030 catch (OperationCanceledException exception)
1032 throw FxTrace.Exception.AsError(new CommunicationObjectAbortedException(SR.HandleFreedInDirectory, exception));
1034 catch (TimeoutException)
1036 this.persistenceContext.Fault();
1045 if (this.transaction != null)
1047 this.transaction.Complete();
1052 this.persistenceContext.FinishOperation();
1058 public static void End(IAsyncResult result)
1060 AsyncResult.End<ReleaseAsyncResult>(result);
1063 static bool HandleEndExecute(IAsyncResult result)
1065 ReleaseAsyncResult thisPtr = (ReleaseAsyncResult)result.AsyncState;
1066 thisPtr.persistenceContext.store.EndExecute(result);
1067 return thisPtr.AfterUnlock();
1072 this.persistenceContext.IsLocked = false;
1074 IAsyncResult result;
1075 using (PrepareTransactionalCall(this.transaction))
1077 result = this.persistenceContext.BeginEnlist(this.timeoutHelper.RemainingTime(), PrepareAsyncCompletion(ReleaseAsyncResult.handleEndEnlist), this);
1079 return SyncContinue(result);
1082 static bool HandleEndEnlist(IAsyncResult result)
1084 ReleaseAsyncResult thisPtr = (ReleaseAsyncResult)result.AsyncState;
1085 thisPtr.persistenceContext.EndEnlist(result);
1087 if (!thisPtr.persistenceContext.ScheduleDetach())
1089 thisPtr.persistenceContext.directory.RemoveInstance(thisPtr.persistenceContext);
1092 foreach (InstanceKey key in thisPtr.persistenceContext.keysToAssociate)
1094 thisPtr.persistenceContext.AssociatedKeys.Remove(key);
1096 thisPtr.persistenceContext.keysToAssociate.Clear();
1097 thisPtr.persistenceContext.keysToDisassociate.Clear();
1102 void OnFinishOperation(AsyncResult result, Exception exception)
1106 this.persistenceContext.OnFinishOperationHelper(exception, false);
1110 if (this.transaction != null)
1112 this.transaction.Complete();
1118 class AssociateKeysAsyncResult : TransactedAsyncResult
1120 static readonly AsyncCompletion handleEndExecute = new AsyncCompletion(HandleEndExecute);
1121 static readonly AsyncCompletion handleEndEnlist = new AsyncCompletion(HandleEndEnlist);
1123 readonly PersistenceContext persistenceContext;
1124 readonly bool applicationKeys;
1125 readonly ICollection<InstanceKey> keysToAssociate;
1126 readonly TimeoutHelper timeoutHelper;
1127 readonly DependentTransaction transaction;
1129 public AssociateKeysAsyncResult(PersistenceContext persistenceContext, ICollection<InstanceKey> associatedKeys, TimeSpan timeout,
1130 bool applicationKeys, AsyncCallback callback, object state)
1131 : base(callback, state)
1133 this.persistenceContext = persistenceContext;
1134 this.applicationKeys = applicationKeys;
1135 this.keysToAssociate = associatedKeys;
1136 this.timeoutHelper = new TimeoutHelper(timeout);
1138 OnCompleting = new Action<AsyncResult, Exception>(OnFinishOperation);
1140 bool success = false;
1143 this.persistenceContext.StartOperation();
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.");
1150 Transaction currentTransaction = Transaction.Current;
1151 if (currentTransaction != null)
1153 this.transaction = currentTransaction.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
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))
1165 catch (InstancePersistenceException)
1167 this.persistenceContext.Fault();
1170 catch (OperationCanceledException exception)
1172 throw FxTrace.Exception.AsError(new CommunicationObjectAbortedException(SR.HandleFreedInDirectory, exception));
1174 catch (TimeoutException)
1176 this.persistenceContext.Fault();
1185 // We need to complete our dependent clone because OnFinishOperation will not
1186 // get called in this case.
1187 if (this.transaction != null)
1189 this.transaction.Complete();
1194 this.persistenceContext.FinishOperation();
1200 public static void End(IAsyncResult result)
1202 AsyncResult.End<AssociateKeysAsyncResult>(result);
1205 static bool HandleEndExecute(IAsyncResult result)
1207 AssociateKeysAsyncResult thisPtr = (AssociateKeysAsyncResult)result.AsyncState;
1208 thisPtr.persistenceContext.store.EndExecute(result);
1209 return thisPtr.AfterUpdate();
1212 static bool HandleEndEnlist(IAsyncResult result)
1214 AssociateKeysAsyncResult thisPtr = (AssociateKeysAsyncResult)result.AsyncState;
1215 bool returnValue = false;
1217 if (!thisPtr.persistenceContext.directory.TryAddAssociations(
1218 thisPtr.persistenceContext,
1219 thisPtr.keysToAssociate,
1220 thisPtr.persistenceContext.keysToAssociate,
1221 thisPtr.applicationKeys ? thisPtr.persistenceContext.keysToDisassociate : null))
1223 lock (thisPtr.persistenceContext.ThisLock)
1225 thisPtr.persistenceContext.ThrowIfDisposedOrNotOpen();
1227 throw Fx.AssertAndThrow("Should only fail to add keys in a ---- with abort.");
1230 if (thisPtr.persistenceContext.directory.ConsistencyScope == DurableConsistencyScope.Global)
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)))
1239 if (thisPtr.persistenceContext.store != null)
1241 SaveWorkflowCommand saveCommand = new SaveWorkflowCommand();
1242 foreach (InstanceKey key in thisPtr.persistenceContext.keysToAssociate)
1244 saveCommand.InstanceKeysToAssociate.Add(key.Value, key.Metadata);
1246 if (thisPtr.applicationKeys)
1248 foreach (InstanceKey key in thisPtr.persistenceContext.keysToDisassociate)
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);
1255 IAsyncResult beginExecuteResult = null;
1256 using (thisPtr.PrepareTransactionalCall(thisPtr.transaction))
1258 beginExecuteResult = thisPtr.persistenceContext.store.BeginExecute(
1259 thisPtr.persistenceContext.handle,
1261 thisPtr.timeoutHelper.RemainingTime(),
1262 thisPtr.PrepareAsyncCompletion(AssociateKeysAsyncResult.handleEndExecute),
1265 returnValue = thisPtr.SyncContinue(beginExecuteResult);
1270 returnValue = thisPtr.AfterUpdate();
1275 returnValue = thisPtr.AfterUpdate();
1283 if (this.applicationKeys)
1285 this.persistenceContext.RationalizeSavedKeys(true);
1289 this.persistenceContext.keysToAssociate.Clear();
1295 void OnFinishOperation(AsyncResult result, Exception exception)
1297 if (exception is InstancePersistenceException)
1299 this.persistenceContext.Fault();
1303 this.persistenceContext.OnFinishOperationHelper(exception, false);
1307 // We are all done. If we have a savedTransaction, we need to complete it now.
1308 if (this.transaction != null)
1310 this.transaction.Complete();
1316 class UpdateSuspendMetadataAsyncResult : AsyncResult
1318 static readonly AsyncCompletion handleEndExecute = new AsyncCompletion(HandleEndExecute);
1320 readonly PersistenceContext persistenceContext;
1321 readonly TimeoutHelper timeoutHelper;
1322 readonly DependentTransaction transaction;
1324 public UpdateSuspendMetadataAsyncResult(PersistenceContext persistenceContext, Exception reason, TimeSpan timeout, AsyncCallback callback, object state)
1325 : base(callback, state)
1327 this.persistenceContext = persistenceContext;
1328 OnCompleting = new Action<AsyncResult, Exception>(OnFinishOperation);
1330 bool success = false;
1333 this.persistenceContext.StartOperation();
1335 this.timeoutHelper = new TimeoutHelper(timeout);
1337 Transaction currentTransaction = Transaction.Current;
1338 if (currentTransaction != null)
1340 this.transaction = currentTransaction.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
1343 if (this.persistenceContext.store != null)
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;
1351 IAsyncResult result = this.persistenceContext.store.BeginExecute(
1352 this.persistenceContext.handle,
1354 this.timeoutHelper.RemainingTime(),
1355 PrepareAsyncCompletion(handleEndExecute),
1357 if (SyncContinue(result))
1368 catch (OperationCanceledException exception)
1370 throw FxTrace.Exception.AsError(new CommunicationObjectAbortedException(SR.HandleFreedInDirectory, exception));
1372 catch (TimeoutException)
1374 this.persistenceContext.Fault();
1383 if (this.transaction != null)
1385 this.transaction.Complete();
1390 this.persistenceContext.FinishOperation();
1396 public static void End(IAsyncResult result)
1398 AsyncResult.End<UpdateSuspendMetadataAsyncResult>(result);
1401 static bool HandleEndExecute(IAsyncResult result)
1403 UpdateSuspendMetadataAsyncResult thisPtr = (UpdateSuspendMetadataAsyncResult)result.AsyncState;
1404 thisPtr.persistenceContext.store.EndExecute(result);
1408 void OnFinishOperation(AsyncResult result, Exception exception)
1412 this.persistenceContext.OnFinishOperationHelper(exception, false);
1416 if (this.transaction != null)
1418 this.transaction.Complete();