536cd135cc
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
1425 lines
58 KiB
C#
1425 lines
58 KiB
C#
//-----------------------------------------------------------------------------
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
namespace System.ServiceModel.Activities.Dispatcher
|
|
{
|
|
using System.Activities;
|
|
using System.Activities.DurableInstancing;
|
|
using System.Activities.Hosting;
|
|
using System.Collections;
|
|
using System.Collections.ObjectModel;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Runtime;
|
|
using System.Runtime.DurableInstancing;
|
|
using System.ServiceModel.Channels;
|
|
using System.ServiceModel.Activities.Description;
|
|
using System.Transactions;
|
|
using System.Xml.Linq;
|
|
|
|
sealed class PersistenceContext : CommunicationObject
|
|
{
|
|
// Dictionary keyed by Transaction HashCode. The value is the enlistment for that transaction.
|
|
internal static Dictionary<int, PersistenceContextEnlistment> Enlistments = new Dictionary<int, PersistenceContextEnlistment>();
|
|
|
|
readonly PersistenceProviderDirectory directory;
|
|
|
|
readonly InstanceStore store;
|
|
readonly InstanceHandle handle;
|
|
|
|
readonly HashSet<InstanceKey> keysToAssociate;
|
|
readonly HashSet<InstanceKey> keysToDisassociate;
|
|
|
|
static TimeSpan defaultOpenTimeout = TimeSpan.FromSeconds(90);
|
|
static TimeSpan defaultCloseTimeout = TimeSpan.FromSeconds(90);
|
|
|
|
|
|
bool operationInProgress;
|
|
|
|
WorkflowServiceInstance workflowInstance;
|
|
|
|
// The hash code of the transaction that has this particular context "locked".
|
|
// If the value of this property is 0, then no transaction is working on this context
|
|
// and it is available to be "locked" by a transaction. Locking for transactions is done
|
|
// with QueueForTransactionLock. This method returns a TransactionWaitAsyncResult. If the
|
|
// lock was obtained by the call, the resulting AsyncResult will be marked as "Completed"
|
|
// upon return from QueueForTransactionLock. If not, the caller should wait on the
|
|
// AsyncResult.AsyncWaitHandle before proceeding to update any of the fields of the context.
|
|
int lockingTransaction;
|
|
//We are keeping a reference to both the transaction object and the hash code to avoid calling the GetHashCode multiple times
|
|
Transaction lockingTransactionObject;
|
|
|
|
// This is the queue of TransactionWaitAsyncResult objects that are waiting for the
|
|
// context to become "unlocked" with respect to a transaction. DequeueTransactionWaiter
|
|
// removes the first element from this queue and returns it. If there is no element on the
|
|
// queue, null is returned indicating that there was no outstanding waiter.
|
|
Queue<TransactionWaitAsyncResult> transactionWaiterQueue;
|
|
|
|
// Used by PPD when there is no store.
|
|
internal PersistenceContext(PersistenceProviderDirectory directory,
|
|
Guid instanceId, InstanceKey key, IEnumerable<InstanceKey> associatedKeys)
|
|
{
|
|
Fx.Assert(directory != null, "Directory is null in PersistenceContext.");
|
|
Fx.Assert(instanceId != Guid.Empty, "Cannot provide an empty instance ID.");
|
|
|
|
this.directory = directory;
|
|
|
|
InstanceId = instanceId;
|
|
|
|
AssociatedKeys = associatedKeys != null ? new HashSet<InstanceKey>(associatedKeys) :
|
|
new HashSet<InstanceKey>();
|
|
if (key != null && !AssociatedKeys.Contains(key))
|
|
{
|
|
AssociatedKeys.Add(key);
|
|
}
|
|
|
|
this.keysToAssociate = new HashSet<InstanceKey>(AssociatedKeys);
|
|
this.keysToDisassociate = new HashSet<InstanceKey>();
|
|
|
|
this.lockingTransaction = 0;
|
|
this.Detaching = false;
|
|
this.transactionWaiterQueue = new Queue<TransactionWaitAsyncResult>();
|
|
}
|
|
|
|
// Used by PPD when there is a store.
|
|
internal PersistenceContext(PersistenceProviderDirectory directory, InstanceStore store,
|
|
InstanceHandle handle, Guid instanceId, IEnumerable<InstanceKey> associatedKeys,
|
|
bool newInstance, bool locked, InstanceView view, WorkflowIdentityKey updatedIdentity)
|
|
: this(directory, instanceId, null, associatedKeys)
|
|
{
|
|
Fx.Assert(store != null, "Null store passed to PersistenceContext.");
|
|
Fx.Assert(handle != null, "Null handle passed to PersistenceContext.");
|
|
|
|
this.store = store;
|
|
this.handle = handle;
|
|
|
|
IsInitialized = !newInstance;
|
|
IsLocked = locked;
|
|
|
|
if (view != null)
|
|
{
|
|
ReadSuspendedInfo(view);
|
|
}
|
|
|
|
// If we were loaded or we locked the instance, the keys will have been sync'd.
|
|
if (IsInitialized || IsLocked)
|
|
{
|
|
RationalizeSavedKeys(false);
|
|
}
|
|
|
|
if (IsInitialized)
|
|
{
|
|
Fx.Assert(view != null, "View must be specified on an initialized instance.");
|
|
WorkflowIdentity definitionIdentity;
|
|
|
|
if (!TryGetValue<WorkflowIdentity>(view.InstanceMetadata, Workflow45Namespace.DefinitionIdentity, out definitionIdentity))
|
|
{
|
|
definitionIdentity = null;
|
|
}
|
|
|
|
this.workflowInstance = this.directory.InitializeInstance(InstanceId, this, definitionIdentity, updatedIdentity, view.InstanceData, null);
|
|
}
|
|
}
|
|
|
|
public Guid InstanceId { get; private set; }
|
|
|
|
public bool IsLocked { get; private set; }
|
|
public bool IsInitialized { get; private set; }
|
|
public bool IsCompleted { get; private set; }
|
|
public bool IsVisible { get; internal set; }
|
|
|
|
public bool IsSuspended { get; set; }
|
|
public string SuspendedReason { get; set; }
|
|
|
|
// Set to true when we detach from the PPD under a transaction. When the transaction completes,
|
|
// either commit or abort, we will finish the removal from the PPD.
|
|
internal bool Detaching
|
|
{
|
|
get; set;
|
|
}
|
|
|
|
public bool CanPersist
|
|
{
|
|
get
|
|
{
|
|
return (this.store != null);
|
|
}
|
|
}
|
|
|
|
public bool IsHandleValid
|
|
{
|
|
get
|
|
{
|
|
return this.handle == null || this.handle.IsValid;
|
|
}
|
|
}
|
|
|
|
internal Transaction LockingTransaction
|
|
{
|
|
get
|
|
{
|
|
lock (ThisLock)
|
|
{
|
|
ThrowIfDisposedOrNotOpen();
|
|
return this.lockingTransactionObject;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Used only by PPD.
|
|
internal bool IsPermanentlyRemoved { get; set; }
|
|
|
|
// If there's a directory, only it can write to this collection as long as the isntance is locked. Otherwise,
|
|
// only this class can.
|
|
internal HashSet<InstanceKey> AssociatedKeys { get; private set; }
|
|
internal ReadOnlyCollection<BookmarkInfo> Bookmarks { get; set; }
|
|
|
|
protected override TimeSpan DefaultCloseTimeout { get { return defaultCloseTimeout; } }
|
|
protected override TimeSpan DefaultOpenTimeout { get { return defaultOpenTimeout; } }
|
|
|
|
// Remove key associations. These are never immediately propagated to the store / cache. Succeeds
|
|
// if the keys don't exist or are associated with a different instance (in which case they are
|
|
// not disassociated).
|
|
public void DisassociateKeys(ICollection<InstanceKey> expiredKeys)
|
|
{
|
|
ThrowIfDisposedOrNotOpen();
|
|
Fx.Assert(expiredKeys != null, "'expiredKeys' parameter to DisassociateKeys cannot be null.");
|
|
|
|
try
|
|
{
|
|
StartOperation();
|
|
ThrowIfCompleted();
|
|
ThrowIfNotVisible();
|
|
Fx.Assert(!IsInitialized || IsLocked, "Should not be visible if initialized and not locked.");
|
|
|
|
foreach (InstanceKey key in expiredKeys)
|
|
{
|
|
if (AssociatedKeys.Contains(key) && !this.keysToDisassociate.Contains(key))
|
|
{
|
|
this.keysToDisassociate.Add(key);
|
|
this.keysToAssociate.Remove(key);
|
|
}
|
|
else
|
|
{
|
|
Fx.Assert(!this.keysToAssociate.Contains(key), "Cannot be planning to associate this key.");
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
FinishOperation();
|
|
}
|
|
}
|
|
|
|
public IAsyncResult BeginSave(
|
|
IDictionary<XName, InstanceValue> instance,
|
|
SaveStatus saveStatus,
|
|
TimeSpan timeout,
|
|
AsyncCallback callback,
|
|
object state)
|
|
{
|
|
ThrowIfDisposedOrNotOpen();
|
|
Fx.AssertAndThrow(instance != null, "'instance' parameter to BeginSave cannot be null.");
|
|
|
|
return new SaveAsyncResult(this, instance, saveStatus, timeout, callback, state);
|
|
}
|
|
|
|
public void EndSave(IAsyncResult result)
|
|
{
|
|
SaveAsyncResult.End(result);
|
|
}
|
|
|
|
public IAsyncResult BeginRelease(TimeSpan timeout, AsyncCallback callback, object state)
|
|
{
|
|
ThrowIfDisposedOrNotOpen();
|
|
|
|
return new ReleaseAsyncResult(this, timeout, callback, state);
|
|
}
|
|
|
|
public void EndRelease(IAsyncResult result)
|
|
{
|
|
ReleaseAsyncResult.End(result);
|
|
}
|
|
|
|
public IAsyncResult BeginAssociateKeys(
|
|
ICollection<InstanceKey> associatedKeys, TimeSpan timeout, AsyncCallback callback, object state)
|
|
{
|
|
return BeginAssociateKeysHelper(associatedKeys, timeout, true, callback, state);
|
|
}
|
|
|
|
internal IAsyncResult BeginAssociateInfrastructureKeys(
|
|
ICollection<InstanceKey> associatedKeys, TimeSpan timeout, AsyncCallback callback, object state)
|
|
{
|
|
return BeginAssociateKeysHelper(associatedKeys, timeout, true, callback, state);
|
|
}
|
|
|
|
IAsyncResult BeginAssociateKeysHelper(ICollection<InstanceKey> associatedKeys,
|
|
TimeSpan timeout, bool applicationKeys, AsyncCallback callback, object state)
|
|
{
|
|
ThrowIfDisposedOrNotOpen();
|
|
Fx.Assert(associatedKeys != null, "'associatedKeys' parameter to BeginAssociateKeys cannot be null.");
|
|
|
|
return new AssociateKeysAsyncResult(this, associatedKeys, timeout, applicationKeys, callback, state);
|
|
}
|
|
|
|
public void EndAssociateKeys(IAsyncResult result)
|
|
{
|
|
AssociateKeysAsyncResult.End(result);
|
|
}
|
|
|
|
internal void EndAssociateInfrastructureKeys(IAsyncResult result)
|
|
{
|
|
AssociateKeysAsyncResult.End(result);
|
|
}
|
|
|
|
// UpdateSuspendMetadata and Unlock instance
|
|
public IAsyncResult BeginUpdateSuspendMetadata(Exception reason, TimeSpan timeout, AsyncCallback callback, object state)
|
|
{
|
|
ThrowIfDisposedOrNotOpen();
|
|
|
|
return new UpdateSuspendMetadataAsyncResult(this, reason, timeout, callback, state);
|
|
}
|
|
|
|
public void EndUpdateSuspendMetadata(IAsyncResult result)
|
|
{
|
|
UpdateSuspendMetadataAsyncResult.End(result);
|
|
}
|
|
|
|
public WorkflowServiceInstance GetInstance(WorkflowGetInstanceContext parameters)
|
|
{
|
|
if (this.workflowInstance == null && parameters != null)
|
|
{
|
|
lock (ThisLock)
|
|
{
|
|
ThrowIfDisposedOrNotOpen();
|
|
|
|
if (this.workflowInstance == null)
|
|
{
|
|
try
|
|
{
|
|
WorkflowServiceInstance result;
|
|
if (parameters.WorkflowHostingEndpoint != null)
|
|
{
|
|
WorkflowHostingResponseContext responseContext = new WorkflowHostingResponseContext();
|
|
WorkflowCreationContext creationContext = parameters.WorkflowHostingEndpoint.OnGetCreationContext(parameters.Inputs, parameters.OperationContext, InstanceId, responseContext);
|
|
if (creationContext == null)
|
|
{
|
|
throw FxTrace.Exception.AsError(WorkflowHostingEndpoint.CreateDispatchFaultException());
|
|
}
|
|
result = this.directory.InitializeInstance(InstanceId, this, null, creationContext);
|
|
|
|
// Return args
|
|
parameters.WorkflowCreationContext = creationContext;
|
|
parameters.WorkflowHostingResponseContext = responseContext;
|
|
}
|
|
else
|
|
{
|
|
result = this.directory.InitializeInstance(InstanceId, this, null, null);
|
|
}
|
|
this.workflowInstance = result;
|
|
}
|
|
finally
|
|
{
|
|
if (this.workflowInstance == null)
|
|
{
|
|
Fault();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return this.workflowInstance;
|
|
}
|
|
|
|
protected override void OnAbort()
|
|
{
|
|
if (this.handle != null)
|
|
{
|
|
this.handle.Free();
|
|
}
|
|
}
|
|
|
|
protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state)
|
|
{
|
|
return new CloseAsyncResult(this, callback, state);
|
|
}
|
|
|
|
protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
|
|
{
|
|
return new CompletedAsyncResult(callback, state);
|
|
}
|
|
|
|
protected override void OnClose(TimeSpan timeout)
|
|
{
|
|
try
|
|
{
|
|
StartOperation();
|
|
|
|
if (this.store != null)
|
|
{
|
|
this.handle.Free();
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
FinishOperation();
|
|
}
|
|
}
|
|
|
|
protected override void OnEndClose(IAsyncResult result)
|
|
{
|
|
CloseAsyncResult.End(result);
|
|
}
|
|
|
|
protected override void OnEndOpen(IAsyncResult result)
|
|
{
|
|
CompletedAsyncResult.End(result);
|
|
}
|
|
|
|
// PersistenceProviderDirectory calls Open in an async path. Do not introduce blocking work to this method
|
|
// without changing PersistenceProviderDirectory to call BeginOpen instead.
|
|
protected override void OnOpen(TimeSpan timeout)
|
|
{
|
|
}
|
|
|
|
protected override void OnClosing()
|
|
{
|
|
base.OnClosing();
|
|
this.directory.RemoveInstance(this, true);
|
|
}
|
|
|
|
protected override void OnFaulted()
|
|
{
|
|
base.OnFaulted();
|
|
this.directory.RemoveInstance(this, true);
|
|
}
|
|
|
|
void RationalizeSavedKeys(bool updateDirectory)
|
|
{
|
|
if (updateDirectory)
|
|
{
|
|
this.directory.RemoveAssociations(this, this.keysToDisassociate);
|
|
}
|
|
else
|
|
{
|
|
foreach (InstanceKey key in this.keysToDisassociate)
|
|
{
|
|
AssociatedKeys.Remove(key);
|
|
}
|
|
}
|
|
|
|
this.keysToAssociate.Clear();
|
|
this.keysToDisassociate.Clear();
|
|
}
|
|
|
|
void ReadSuspendedInfo(InstanceView view)
|
|
{
|
|
string suspendedReason = null;
|
|
if (TryGetValue<string>(view.InstanceMetadata, WorkflowServiceNamespace.SuspendReason, out suspendedReason))
|
|
{
|
|
IsSuspended = true;
|
|
SuspendedReason = suspendedReason;
|
|
}
|
|
else
|
|
{
|
|
IsSuspended = false;
|
|
SuspendedReason = null;
|
|
}
|
|
}
|
|
|
|
void StartOperation()
|
|
{
|
|
Fx.AssertAndThrow(!this.operationInProgress, "PersistenceContext doesn't support multiple operations.");
|
|
this.operationInProgress = true;
|
|
}
|
|
|
|
void FinishOperation()
|
|
{
|
|
this.operationInProgress = false;
|
|
}
|
|
|
|
void OnFinishOperationHelper(Exception exception, bool ownsThrottle)
|
|
{
|
|
try
|
|
{
|
|
if (exception is OperationCanceledException)
|
|
{
|
|
throw FxTrace.Exception.AsError(new CommunicationObjectAbortedException(SR.HandleFreedInDirectory, exception));
|
|
}
|
|
else if (exception is TimeoutException)
|
|
{
|
|
Fault();
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (ownsThrottle)
|
|
{
|
|
this.directory.ReleaseThrottle();
|
|
}
|
|
FinishOperation();
|
|
}
|
|
}
|
|
|
|
void ThrowIfCompleted()
|
|
{
|
|
Fx.AssertAndThrow(!IsCompleted, "PersistenceContext operation invalid: instance already completed.");
|
|
}
|
|
|
|
void ThrowIfNotVisible()
|
|
{
|
|
// Be charitable to racy aborts.
|
|
if (!IsVisible)
|
|
{
|
|
lock (ThisLock)
|
|
{
|
|
Fx.AssertAndThrow(State != CommunicationState.Opened,
|
|
"PersistenceContext operation invalid: instance must be visible.");
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static bool TryGetValue<T>(IDictionary<XName, InstanceValue> data, XName key, out T value)
|
|
{
|
|
InstanceValue instanceValue;
|
|
value = default(T);
|
|
if (data.TryGetValue(key, out instanceValue) && !instanceValue.IsDeletedValue)
|
|
{
|
|
if (instanceValue.Value is T)
|
|
{
|
|
value = (T)instanceValue.Value;
|
|
return true;
|
|
}
|
|
else if (instanceValue.Value == null && !(value is ValueType))
|
|
{
|
|
// need to check for null assignments to value types
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (instanceValue.Value == null)
|
|
{
|
|
throw FxTrace.Exception.AsError(new InstancePersistenceException(SRCore.NullAssignedToValueType(typeof(T))));
|
|
}
|
|
else
|
|
{
|
|
throw FxTrace.Exception.AsError(new InstancePersistenceException(SRCore.IncorrectValueType(typeof(T), instanceValue.Value.GetType())));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
internal TransactionWaitAsyncResult BeginEnlist(TimeSpan timeout, AsyncCallback callback, object state)
|
|
{
|
|
ThrowIfDisposedOrNotOpen();
|
|
// The transaction to enlist on is in Transaction.Current. The actual enlistment, if needed, will be made in
|
|
// TransactionWaitAsyncResult when it is notified that it has the transaction lock.
|
|
return new TransactionWaitAsyncResult(Transaction.Current, this, timeout, callback, state);
|
|
}
|
|
|
|
[SuppressMessage(FxCop.Category.ReliabilityBasic, FxCop.Rule.CommunicationObjectThrowIf,
|
|
Justification = "We are intentionally re-validating the state of the instance after having acquired the transaction lock")]
|
|
internal void EndEnlist(IAsyncResult result)
|
|
{
|
|
TransactionWaitAsyncResult.End(result);
|
|
// The PersistenceContext may have been aborted while we were waiting for the transaction lock.
|
|
ThrowIfDisposedOrNotOpen();
|
|
}
|
|
|
|
|
|
// Returns true if the call was able to obtain the transaction lock; false if we had
|
|
// to queue the request for the lock.
|
|
internal bool QueueForTransactionLock(Transaction requestingTransaction, TransactionWaitAsyncResult txWaitAsyncResult)
|
|
{
|
|
lock (ThisLock)
|
|
{
|
|
// If the transaction "lock" is not already held, give it to this requester.
|
|
if (0 == this.lockingTransaction)
|
|
{
|
|
// It's possible that this particular request is not transacted.
|
|
if (null != requestingTransaction)
|
|
{
|
|
this.lockingTransaction = requestingTransaction.GetHashCode();
|
|
this.lockingTransactionObject = requestingTransaction.Clone();
|
|
}
|
|
// No queuing because we weren't already locked by a transaction.
|
|
return true;
|
|
}
|
|
else if ((null != requestingTransaction) && (this.lockingTransaction == requestingTransaction.GetHashCode()))
|
|
{
|
|
// Same transaction as the locking transaction - no queuing.
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// Some other transaction has the lock, so add the AsyncResult to the queue.
|
|
this.transactionWaiterQueue.Enqueue(txWaitAsyncResult);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Dequeue and schedule the top element on queue of waiting TransactionWaitAsyncResult objects.
|
|
// Before returning this also makes the transaction represented by the dequeued TransactionWaitAsyncResult
|
|
// the owner of the transaction "lock" for this context.
|
|
internal void ScheduleNextTransactionWaiter()
|
|
{
|
|
TransactionWaitAsyncResult dequeuedWaiter = null;
|
|
bool detachThis = false;
|
|
|
|
lock (ThisLock)
|
|
{
|
|
// Only try Dequeue if we have entries on the queue.
|
|
bool atLeastOneSuccessfullyCompleted = false;
|
|
if (0 < this.transactionWaiterQueue.Count)
|
|
{
|
|
while ((0 < this.transactionWaiterQueue.Count) && !atLeastOneSuccessfullyCompleted)
|
|
{
|
|
dequeuedWaiter = this.transactionWaiterQueue.Dequeue();
|
|
|
|
// It's possible that the waiter didn't have a transaction.
|
|
// If that is the case, we don't have a transaction to "lock" the context.
|
|
if (null != dequeuedWaiter.Transaction)
|
|
{
|
|
this.lockingTransactionObject = dequeuedWaiter.Transaction;
|
|
this.lockingTransaction = lockingTransactionObject.GetHashCode();
|
|
}
|
|
else
|
|
{
|
|
this.lockingTransaction = 0;
|
|
this.lockingTransactionObject = null;
|
|
}
|
|
|
|
atLeastOneSuccessfullyCompleted = dequeuedWaiter.Complete() || atLeastOneSuccessfullyCompleted;
|
|
|
|
if (this.Detaching)
|
|
{
|
|
detachThis = true;
|
|
this.Detaching = false;
|
|
}
|
|
|
|
// If we are doing a permanent detach, we must have received an OnClosing or
|
|
// OnFaulted while the PersistenceContext was locked for a transaction. In that
|
|
// case, we want to wake up ALL waiters.
|
|
if (this.IsPermanentlyRemoved)
|
|
{
|
|
this.lockingTransaction = 0;
|
|
this.lockingTransactionObject = null;
|
|
while (0 < this.transactionWaiterQueue.Count)
|
|
{
|
|
dequeuedWaiter = this.transactionWaiterQueue.Dequeue();
|
|
atLeastOneSuccessfullyCompleted = dequeuedWaiter.Complete() || atLeastOneSuccessfullyCompleted;
|
|
}
|
|
}
|
|
|
|
// Now we need to look for any adjacent waiters in the queue that are
|
|
// waiting for the same transaction. If we still have entries on the queue,
|
|
// we must have a waiterToComplete. Note that if we were doing a permanent detach,
|
|
// there won't be any waiters left in the queue at this point.
|
|
while (0 < this.transactionWaiterQueue.Count)
|
|
{
|
|
TransactionWaitAsyncResult nextWaiter = this.transactionWaiterQueue.Peek();
|
|
if (0 == this.lockingTransaction)
|
|
{
|
|
// We dequeue this waiter because we shouldn't block transactional waiters
|
|
// behind non-transactional waiters because there is nothing to wake up the
|
|
// transactional waiters in that case. Also set this.LockingTransaction
|
|
// to that of the next waiter.
|
|
if (null != nextWaiter.Transaction)
|
|
{
|
|
this.lockingTransactionObject = nextWaiter.Transaction;
|
|
this.lockingTransaction = this.lockingTransactionObject.GetHashCode();
|
|
}
|
|
}
|
|
else if (null != nextWaiter.Transaction)
|
|
{
|
|
// Stop looking if the new lockingTransaction is different than
|
|
// the nextWaiter's transaction.
|
|
if (this.lockingTransaction != nextWaiter.Transaction.GetHashCode())
|
|
{
|
|
break; // out of the inner-while
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// The nextWaiter is non-transational, so it doesn't match the current
|
|
// lock holder, so we are done.
|
|
break; // out of the inner-while
|
|
}
|
|
|
|
dequeuedWaiter = this.transactionWaiterQueue.Dequeue();
|
|
atLeastOneSuccessfullyCompleted = dequeuedWaiter.Complete() || atLeastOneSuccessfullyCompleted;
|
|
}
|
|
}
|
|
}
|
|
if (!atLeastOneSuccessfullyCompleted)
|
|
{
|
|
// There are no more waiters, so the context is no longer "locked" by a transaction.
|
|
this.lockingTransaction = 0;
|
|
this.lockingTransactionObject = null;
|
|
}
|
|
}
|
|
|
|
// If we are detaching and it is NOT permanently removed, finish the detach by calling RemoveInstance non-transactionally.
|
|
// It will be marked as permanently removed in OnClosing and OnFaulted and it will have already been removed, so we don't
|
|
// want to try to remove it again.
|
|
if (detachThis)
|
|
{
|
|
this.directory.RemoveInstance(this, false);
|
|
}
|
|
}
|
|
|
|
bool ScheduleDetach()
|
|
{
|
|
lock (ThisLock)
|
|
{
|
|
if (this.lockingTransaction != 0)
|
|
{
|
|
Detaching = true;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void PopulateActivationMetadata(SaveWorkflowCommand saveCommand)
|
|
{
|
|
bool saveIdentity;
|
|
if (!IsInitialized)
|
|
{
|
|
Fx.Assert(this.directory.InstanceMetadataChanges != null, "We should always be non-null here.");
|
|
foreach (KeyValuePair<XName, InstanceValue> pair in this.directory.InstanceMetadataChanges)
|
|
{
|
|
saveCommand.InstanceMetadataChanges.Add(pair.Key, pair.Value);
|
|
}
|
|
saveIdentity = this.workflowInstance.DefinitionIdentity != null;
|
|
}
|
|
else
|
|
{
|
|
saveIdentity = this.workflowInstance.HasBeenUpdated;
|
|
}
|
|
|
|
if (saveIdentity)
|
|
{
|
|
if (this.workflowInstance.DefinitionIdentity != null)
|
|
{
|
|
saveCommand.InstanceMetadataChanges.Add(Workflow45Namespace.DefinitionIdentity, new InstanceValue(this.workflowInstance.DefinitionIdentity, InstanceValueOptions.None));
|
|
}
|
|
else
|
|
{
|
|
saveCommand.InstanceMetadataChanges.Add(Workflow45Namespace.DefinitionIdentity, InstanceValue.DeletedValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
class CloseAsyncResult : AsyncResult
|
|
{
|
|
PersistenceContext persistenceContext;
|
|
|
|
public CloseAsyncResult(PersistenceContext persistenceContext, AsyncCallback callback, object state)
|
|
: base(callback, state)
|
|
{
|
|
this.persistenceContext = persistenceContext;
|
|
OnCompleting = new Action<AsyncResult, Exception>(OnFinishOperation);
|
|
|
|
bool success = false;
|
|
bool completeSelf = false;
|
|
try
|
|
{
|
|
this.persistenceContext.StartOperation();
|
|
|
|
if (this.persistenceContext.store != null)
|
|
{
|
|
Fx.Assert(this.persistenceContext.handle != null, "WorkflowInstance failed to call SetHandle - from OnBeginClose.");
|
|
this.persistenceContext.handle.Free();
|
|
}
|
|
completeSelf = true;
|
|
success = true;
|
|
}
|
|
finally
|
|
{
|
|
if (!success)
|
|
{
|
|
this.persistenceContext.FinishOperation();
|
|
}
|
|
}
|
|
|
|
if (completeSelf)
|
|
{
|
|
base.Complete(true);
|
|
}
|
|
}
|
|
|
|
public static void End(IAsyncResult result)
|
|
{
|
|
AsyncResult.End<CloseAsyncResult>(result);
|
|
}
|
|
|
|
void OnFinishOperation(AsyncResult result, Exception exception)
|
|
{
|
|
this.persistenceContext.FinishOperation();
|
|
}
|
|
}
|
|
|
|
class SaveAsyncResult : TransactedAsyncResult
|
|
{
|
|
static readonly AsyncCompletion handleEndExecute = new AsyncCompletion(HandleEndExecute);
|
|
static readonly AsyncCompletion handleEndEnlist = new AsyncCompletion(HandleEndEnlist);
|
|
|
|
readonly PersistenceContext persistenceContext;
|
|
readonly SaveStatus saveStatus;
|
|
readonly TimeoutHelper timeoutHelper;
|
|
readonly DependentTransaction transaction;
|
|
|
|
public SaveAsyncResult(PersistenceContext persistenceContext, IDictionary<XName, InstanceValue> instance, SaveStatus saveStatus, TimeSpan timeout,
|
|
AsyncCallback callback, object state)
|
|
: base(callback, state)
|
|
{
|
|
this.persistenceContext = persistenceContext;
|
|
OnCompleting = new Action<AsyncResult, Exception>(OnFinishOperation);
|
|
|
|
this.saveStatus = saveStatus;
|
|
|
|
bool success = false;
|
|
try
|
|
{
|
|
this.persistenceContext.StartOperation();
|
|
|
|
this.persistenceContext.ThrowIfCompleted();
|
|
this.persistenceContext.ThrowIfNotVisible();
|
|
Fx.Assert(!this.persistenceContext.IsInitialized || this.persistenceContext.IsLocked,
|
|
"Should not be visible if initialized and not locked.");
|
|
|
|
this.timeoutHelper = new TimeoutHelper(timeout);
|
|
|
|
Transaction currentTransaction = Transaction.Current;
|
|
if (currentTransaction != null)
|
|
{
|
|
this.transaction = currentTransaction.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
|
|
}
|
|
|
|
if (this.persistenceContext.store != null)
|
|
{
|
|
SaveWorkflowCommand saveCommand = new SaveWorkflowCommand();
|
|
foreach (KeyValuePair<XName, InstanceValue> value in instance)
|
|
{
|
|
saveCommand.InstanceData.Add(value);
|
|
}
|
|
this.persistenceContext.PopulateActivationMetadata(saveCommand);
|
|
if (this.persistenceContext.IsSuspended)
|
|
{
|
|
saveCommand.InstanceMetadataChanges.Add(WorkflowServiceNamespace.SuspendReason, new InstanceValue(this.persistenceContext.SuspendedReason));
|
|
}
|
|
else
|
|
{
|
|
saveCommand.InstanceMetadataChanges.Add(WorkflowServiceNamespace.SuspendReason, InstanceValue.DeletedValue);
|
|
saveCommand.InstanceMetadataChanges.Add(WorkflowServiceNamespace.SuspendException, InstanceValue.DeletedValue);
|
|
}
|
|
foreach (InstanceKey key in this.persistenceContext.keysToAssociate)
|
|
{
|
|
saveCommand.InstanceKeysToAssociate.Add(key.Value, key.Metadata);
|
|
}
|
|
foreach (InstanceKey key in this.persistenceContext.keysToDisassociate)
|
|
{
|
|
// We are going to Complete and Disassociate with the same Save command.
|
|
saveCommand.InstanceKeysToComplete.Add(key.Value);
|
|
saveCommand.InstanceKeysToFree.Add(key.Value);
|
|
}
|
|
|
|
if (this.saveStatus == SaveStatus.Completed)
|
|
{
|
|
saveCommand.CompleteInstance = true;
|
|
saveCommand.UnlockInstance = true;
|
|
}
|
|
else
|
|
{
|
|
saveCommand.UnlockInstance = this.saveStatus == SaveStatus.Unlocked;
|
|
}
|
|
|
|
IAsyncResult result = this.persistenceContext.store.BeginExecute(
|
|
this.persistenceContext.handle,
|
|
saveCommand,
|
|
this.timeoutHelper.RemainingTime(),
|
|
PrepareAsyncCompletion(SaveAsyncResult.handleEndExecute),
|
|
this);
|
|
if (SyncContinue(result))
|
|
{
|
|
Complete(true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (this.saveStatus == SaveStatus.Completed)
|
|
{
|
|
this.persistenceContext.IsCompleted = true;
|
|
this.persistenceContext.IsLocked = false;
|
|
}
|
|
else
|
|
{
|
|
this.persistenceContext.IsLocked = this.saveStatus != SaveStatus.Unlocked;
|
|
}
|
|
if (AfterSave())
|
|
{
|
|
Complete(true);
|
|
}
|
|
}
|
|
success = true;
|
|
}
|
|
catch (OperationCanceledException exception)
|
|
{
|
|
throw FxTrace.Exception.AsError(new CommunicationObjectAbortedException(SR.HandleFreedInDirectory, exception));
|
|
}
|
|
catch (TimeoutException)
|
|
{
|
|
this.persistenceContext.Fault();
|
|
throw;
|
|
}
|
|
finally
|
|
{
|
|
if (!success)
|
|
{
|
|
try
|
|
{
|
|
if (this.transaction != null)
|
|
{
|
|
this.transaction.Complete();
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
this.persistenceContext.FinishOperation();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void End(IAsyncResult result)
|
|
{
|
|
AsyncResult.End<SaveAsyncResult>(result);
|
|
}
|
|
|
|
static bool HandleEndExecute(IAsyncResult result)
|
|
{
|
|
SaveAsyncResult thisPtr = (SaveAsyncResult)result.AsyncState;
|
|
thisPtr.persistenceContext.store.EndExecute(result);
|
|
thisPtr.persistenceContext.IsCompleted = thisPtr.saveStatus == SaveStatus.Completed;
|
|
thisPtr.persistenceContext.IsLocked = thisPtr.saveStatus == SaveStatus.Locked;
|
|
return thisPtr.AfterSave();
|
|
}
|
|
|
|
bool AfterSave()
|
|
{
|
|
this.persistenceContext.IsInitialized = true;
|
|
|
|
if (this.saveStatus != SaveStatus.Locked)
|
|
{
|
|
IAsyncResult result;
|
|
using (PrepareTransactionalCall(this.transaction))
|
|
{
|
|
result = this.persistenceContext.BeginEnlist(this.timeoutHelper.RemainingTime(), PrepareAsyncCompletion(SaveAsyncResult.handleEndEnlist), this);
|
|
}
|
|
return SyncContinue(result);
|
|
}
|
|
|
|
return AfterEnlist();
|
|
}
|
|
|
|
bool AfterEnlist()
|
|
{
|
|
this.persistenceContext.RationalizeSavedKeys(this.saveStatus == SaveStatus.Locked);
|
|
return true;
|
|
}
|
|
|
|
static bool HandleEndEnlist(IAsyncResult result)
|
|
{
|
|
SaveAsyncResult thisPtr = (SaveAsyncResult)result.AsyncState;
|
|
thisPtr.persistenceContext.EndEnlist(result);
|
|
|
|
if (!thisPtr.persistenceContext.ScheduleDetach())
|
|
{
|
|
thisPtr.persistenceContext.directory.RemoveInstance(thisPtr.persistenceContext);
|
|
}
|
|
return thisPtr.AfterEnlist();
|
|
}
|
|
|
|
void OnFinishOperation(AsyncResult result, Exception exception)
|
|
{
|
|
try
|
|
{
|
|
this.persistenceContext.OnFinishOperationHelper(exception, false);
|
|
}
|
|
finally
|
|
{
|
|
if (this.transaction != null)
|
|
{
|
|
this.transaction.Complete();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class ReleaseAsyncResult : TransactedAsyncResult
|
|
{
|
|
static readonly AsyncCompletion handleEndExecute = new AsyncCompletion(HandleEndExecute);
|
|
static readonly AsyncCompletion handleEndEnlist = new AsyncCompletion(HandleEndEnlist);
|
|
|
|
readonly PersistenceContext persistenceContext;
|
|
readonly TimeoutHelper timeoutHelper;
|
|
readonly DependentTransaction transaction;
|
|
|
|
public ReleaseAsyncResult(PersistenceContext persistenceContext, TimeSpan timeout, AsyncCallback callback, object state)
|
|
: base(callback, state)
|
|
{
|
|
this.persistenceContext = persistenceContext;
|
|
OnCompleting = new Action<AsyncResult, Exception>(OnFinishOperation);
|
|
|
|
bool success = false;
|
|
try
|
|
{
|
|
this.persistenceContext.StartOperation();
|
|
|
|
this.timeoutHelper = new TimeoutHelper(timeout);
|
|
|
|
Transaction currentTransaction = Transaction.Current;
|
|
if (currentTransaction != null)
|
|
{
|
|
this.transaction = currentTransaction.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
|
|
}
|
|
|
|
if (this.persistenceContext.IsVisible)
|
|
{
|
|
if (this.persistenceContext.store != null && this.persistenceContext.IsLocked)
|
|
{
|
|
SaveWorkflowCommand saveCommand = new SaveWorkflowCommand() { UnlockInstance = true };
|
|
this.persistenceContext.PopulateActivationMetadata(saveCommand);
|
|
IAsyncResult result = this.persistenceContext.store.BeginExecute(
|
|
this.persistenceContext.handle,
|
|
saveCommand,
|
|
this.timeoutHelper.RemainingTime(),
|
|
PrepareAsyncCompletion(ReleaseAsyncResult.handleEndExecute),
|
|
this);
|
|
if (SyncContinue(result))
|
|
{
|
|
Complete(true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (AfterUnlock())
|
|
{
|
|
Complete(true);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If we're not visible because we were aborted in a ----, the caller needs to know.
|
|
lock (this.persistenceContext.ThisLock)
|
|
{
|
|
this.persistenceContext.ThrowIfDisposedOrNotOpen();
|
|
}
|
|
Complete(true);
|
|
}
|
|
success = true;
|
|
}
|
|
catch (OperationCanceledException exception)
|
|
{
|
|
throw FxTrace.Exception.AsError(new CommunicationObjectAbortedException(SR.HandleFreedInDirectory, exception));
|
|
}
|
|
catch (TimeoutException)
|
|
{
|
|
this.persistenceContext.Fault();
|
|
throw;
|
|
}
|
|
finally
|
|
{
|
|
if (!success)
|
|
{
|
|
try
|
|
{
|
|
if (this.transaction != null)
|
|
{
|
|
this.transaction.Complete();
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
this.persistenceContext.FinishOperation();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void End(IAsyncResult result)
|
|
{
|
|
AsyncResult.End<ReleaseAsyncResult>(result);
|
|
}
|
|
|
|
static bool HandleEndExecute(IAsyncResult result)
|
|
{
|
|
ReleaseAsyncResult thisPtr = (ReleaseAsyncResult)result.AsyncState;
|
|
thisPtr.persistenceContext.store.EndExecute(result);
|
|
return thisPtr.AfterUnlock();
|
|
}
|
|
|
|
bool AfterUnlock()
|
|
{
|
|
this.persistenceContext.IsLocked = false;
|
|
|
|
IAsyncResult result;
|
|
using (PrepareTransactionalCall(this.transaction))
|
|
{
|
|
result = this.persistenceContext.BeginEnlist(this.timeoutHelper.RemainingTime(), PrepareAsyncCompletion(ReleaseAsyncResult.handleEndEnlist), this);
|
|
}
|
|
return SyncContinue(result);
|
|
}
|
|
|
|
static bool HandleEndEnlist(IAsyncResult result)
|
|
{
|
|
ReleaseAsyncResult thisPtr = (ReleaseAsyncResult)result.AsyncState;
|
|
thisPtr.persistenceContext.EndEnlist(result);
|
|
|
|
if (!thisPtr.persistenceContext.ScheduleDetach())
|
|
{
|
|
thisPtr.persistenceContext.directory.RemoveInstance(thisPtr.persistenceContext);
|
|
}
|
|
|
|
foreach (InstanceKey key in thisPtr.persistenceContext.keysToAssociate)
|
|
{
|
|
thisPtr.persistenceContext.AssociatedKeys.Remove(key);
|
|
}
|
|
thisPtr.persistenceContext.keysToAssociate.Clear();
|
|
thisPtr.persistenceContext.keysToDisassociate.Clear();
|
|
|
|
return true;
|
|
}
|
|
|
|
void OnFinishOperation(AsyncResult result, Exception exception)
|
|
{
|
|
try
|
|
{
|
|
this.persistenceContext.OnFinishOperationHelper(exception, false);
|
|
}
|
|
finally
|
|
{
|
|
if (this.transaction != null)
|
|
{
|
|
this.transaction.Complete();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class AssociateKeysAsyncResult : TransactedAsyncResult
|
|
{
|
|
static readonly AsyncCompletion handleEndExecute = new AsyncCompletion(HandleEndExecute);
|
|
static readonly AsyncCompletion handleEndEnlist = new AsyncCompletion(HandleEndEnlist);
|
|
|
|
readonly PersistenceContext persistenceContext;
|
|
readonly bool applicationKeys;
|
|
readonly ICollection<InstanceKey> keysToAssociate;
|
|
readonly TimeoutHelper timeoutHelper;
|
|
readonly DependentTransaction transaction;
|
|
|
|
public AssociateKeysAsyncResult(PersistenceContext persistenceContext, ICollection<InstanceKey> associatedKeys, TimeSpan timeout,
|
|
bool applicationKeys, AsyncCallback callback, object state)
|
|
: base(callback, state)
|
|
{
|
|
this.persistenceContext = persistenceContext;
|
|
this.applicationKeys = applicationKeys;
|
|
this.keysToAssociate = associatedKeys;
|
|
this.timeoutHelper = new TimeoutHelper(timeout);
|
|
|
|
OnCompleting = new Action<AsyncResult, Exception>(OnFinishOperation);
|
|
|
|
bool success = false;
|
|
try
|
|
{
|
|
this.persistenceContext.StartOperation();
|
|
|
|
this.persistenceContext.ThrowIfCompleted();
|
|
this.persistenceContext.ThrowIfNotVisible();
|
|
Fx.Assert(!this.persistenceContext.IsInitialized || this.persistenceContext.IsLocked,
|
|
"Should not be visible if initialized and not locked.");
|
|
|
|
Transaction currentTransaction = Transaction.Current;
|
|
if (currentTransaction != null)
|
|
{
|
|
this.transaction = currentTransaction.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
|
|
}
|
|
|
|
// We need to get the transaction lock and enlist on the transaction, if there is one.
|
|
IAsyncResult enlistResult = persistenceContext.BeginEnlist(this.timeoutHelper.RemainingTime(),
|
|
this.PrepareAsyncCompletion(handleEndEnlist), this);
|
|
if (SyncContinue(enlistResult))
|
|
{
|
|
Complete(true);
|
|
}
|
|
success = true;
|
|
}
|
|
catch (InstancePersistenceException)
|
|
{
|
|
this.persistenceContext.Fault();
|
|
throw;
|
|
}
|
|
catch (OperationCanceledException exception)
|
|
{
|
|
throw FxTrace.Exception.AsError(new CommunicationObjectAbortedException(SR.HandleFreedInDirectory, exception));
|
|
}
|
|
catch (TimeoutException)
|
|
{
|
|
this.persistenceContext.Fault();
|
|
throw;
|
|
}
|
|
finally
|
|
{
|
|
if (!success)
|
|
{
|
|
try
|
|
{
|
|
// We need to complete our dependent clone because OnFinishOperation will not
|
|
// get called in this case.
|
|
if (this.transaction != null)
|
|
{
|
|
this.transaction.Complete();
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
this.persistenceContext.FinishOperation();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void End(IAsyncResult result)
|
|
{
|
|
AsyncResult.End<AssociateKeysAsyncResult>(result);
|
|
}
|
|
|
|
static bool HandleEndExecute(IAsyncResult result)
|
|
{
|
|
AssociateKeysAsyncResult thisPtr = (AssociateKeysAsyncResult)result.AsyncState;
|
|
thisPtr.persistenceContext.store.EndExecute(result);
|
|
return thisPtr.AfterUpdate();
|
|
}
|
|
|
|
static bool HandleEndEnlist(IAsyncResult result)
|
|
{
|
|
AssociateKeysAsyncResult thisPtr = (AssociateKeysAsyncResult)result.AsyncState;
|
|
bool returnValue = false;
|
|
|
|
if (!thisPtr.persistenceContext.directory.TryAddAssociations(
|
|
thisPtr.persistenceContext,
|
|
thisPtr.keysToAssociate,
|
|
thisPtr.persistenceContext.keysToAssociate,
|
|
thisPtr.applicationKeys ? thisPtr.persistenceContext.keysToDisassociate : null))
|
|
{
|
|
lock (thisPtr.persistenceContext.ThisLock)
|
|
{
|
|
thisPtr.persistenceContext.ThrowIfDisposedOrNotOpen();
|
|
}
|
|
throw Fx.AssertAndThrow("Should only fail to add keys in a ---- with abort.");
|
|
}
|
|
|
|
if (thisPtr.persistenceContext.directory.ConsistencyScope == DurableConsistencyScope.Global)
|
|
{
|
|
// Only do a SetKeysToPersist or Save command if we have keys to associate or disassociate.
|
|
// It's possible that we got invoked with a key that was already in the
|
|
// AssociatedKeys collection.
|
|
if ((thisPtr.persistenceContext.keysToAssociate.Count != 0) ||
|
|
((thisPtr.persistenceContext.keysToDisassociate.Count != 0) &&
|
|
(thisPtr.applicationKeys)))
|
|
{
|
|
if (thisPtr.persistenceContext.store != null)
|
|
{
|
|
SaveWorkflowCommand saveCommand = new SaveWorkflowCommand();
|
|
foreach (InstanceKey key in thisPtr.persistenceContext.keysToAssociate)
|
|
{
|
|
saveCommand.InstanceKeysToAssociate.Add(key.Value, key.Metadata);
|
|
}
|
|
if (thisPtr.applicationKeys)
|
|
{
|
|
foreach (InstanceKey key in thisPtr.persistenceContext.keysToDisassociate)
|
|
{
|
|
// We are going to Complete and Disassociate with the same Save command.
|
|
saveCommand.InstanceKeysToComplete.Add(key.Value);
|
|
saveCommand.InstanceKeysToFree.Add(key.Value);
|
|
}
|
|
}
|
|
IAsyncResult beginExecuteResult = null;
|
|
using (thisPtr.PrepareTransactionalCall(thisPtr.transaction))
|
|
{
|
|
beginExecuteResult = thisPtr.persistenceContext.store.BeginExecute(
|
|
thisPtr.persistenceContext.handle,
|
|
saveCommand,
|
|
thisPtr.timeoutHelper.RemainingTime(),
|
|
thisPtr.PrepareAsyncCompletion(AssociateKeysAsyncResult.handleEndExecute),
|
|
thisPtr);
|
|
}
|
|
returnValue = thisPtr.SyncContinue(beginExecuteResult);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
returnValue = thisPtr.AfterUpdate();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
returnValue = thisPtr.AfterUpdate();
|
|
}
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
bool AfterUpdate()
|
|
{
|
|
if (this.applicationKeys)
|
|
{
|
|
this.persistenceContext.RationalizeSavedKeys(true);
|
|
}
|
|
else
|
|
{
|
|
this.persistenceContext.keysToAssociate.Clear();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void OnFinishOperation(AsyncResult result, Exception exception)
|
|
{
|
|
if (exception is InstancePersistenceException)
|
|
{
|
|
this.persistenceContext.Fault();
|
|
}
|
|
try
|
|
{
|
|
this.persistenceContext.OnFinishOperationHelper(exception, false);
|
|
}
|
|
finally
|
|
{
|
|
// We are all done. If we have a savedTransaction, we need to complete it now.
|
|
if (this.transaction != null)
|
|
{
|
|
this.transaction.Complete();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class UpdateSuspendMetadataAsyncResult : AsyncResult
|
|
{
|
|
static readonly AsyncCompletion handleEndExecute = new AsyncCompletion(HandleEndExecute);
|
|
|
|
readonly PersistenceContext persistenceContext;
|
|
readonly TimeoutHelper timeoutHelper;
|
|
readonly DependentTransaction transaction;
|
|
|
|
public UpdateSuspendMetadataAsyncResult(PersistenceContext persistenceContext, Exception reason, TimeSpan timeout, AsyncCallback callback, object state)
|
|
: base(callback, state)
|
|
{
|
|
this.persistenceContext = persistenceContext;
|
|
OnCompleting = new Action<AsyncResult, Exception>(OnFinishOperation);
|
|
|
|
bool success = false;
|
|
try
|
|
{
|
|
this.persistenceContext.StartOperation();
|
|
|
|
this.timeoutHelper = new TimeoutHelper(timeout);
|
|
|
|
Transaction currentTransaction = Transaction.Current;
|
|
if (currentTransaction != null)
|
|
{
|
|
this.transaction = currentTransaction.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
|
|
}
|
|
|
|
if (this.persistenceContext.store != null)
|
|
{
|
|
SaveWorkflowCommand saveCommand = new SaveWorkflowCommand();
|
|
this.persistenceContext.PopulateActivationMetadata(saveCommand);
|
|
saveCommand.InstanceMetadataChanges[WorkflowServiceNamespace.SuspendReason] = new InstanceValue(reason.Message);
|
|
saveCommand.InstanceMetadataChanges[WorkflowServiceNamespace.SuspendException] = new InstanceValue(reason, InstanceValueOptions.WriteOnly | InstanceValueOptions.Optional);
|
|
saveCommand.UnlockInstance = true;
|
|
|
|
IAsyncResult result = this.persistenceContext.store.BeginExecute(
|
|
this.persistenceContext.handle,
|
|
saveCommand,
|
|
this.timeoutHelper.RemainingTime(),
|
|
PrepareAsyncCompletion(handleEndExecute),
|
|
this);
|
|
if (SyncContinue(result))
|
|
{
|
|
Complete(true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Complete(true);
|
|
}
|
|
success = true;
|
|
}
|
|
catch (OperationCanceledException exception)
|
|
{
|
|
throw FxTrace.Exception.AsError(new CommunicationObjectAbortedException(SR.HandleFreedInDirectory, exception));
|
|
}
|
|
catch (TimeoutException)
|
|
{
|
|
this.persistenceContext.Fault();
|
|
throw;
|
|
}
|
|
finally
|
|
{
|
|
if (!success)
|
|
{
|
|
try
|
|
{
|
|
if (this.transaction != null)
|
|
{
|
|
this.transaction.Complete();
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
this.persistenceContext.FinishOperation();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void End(IAsyncResult result)
|
|
{
|
|
AsyncResult.End<UpdateSuspendMetadataAsyncResult>(result);
|
|
}
|
|
|
|
static bool HandleEndExecute(IAsyncResult result)
|
|
{
|
|
UpdateSuspendMetadataAsyncResult thisPtr = (UpdateSuspendMetadataAsyncResult)result.AsyncState;
|
|
thisPtr.persistenceContext.store.EndExecute(result);
|
|
return true;
|
|
}
|
|
|
|
void OnFinishOperation(AsyncResult result, Exception exception)
|
|
{
|
|
try
|
|
{
|
|
this.persistenceContext.OnFinishOperationHelper(exception, false);
|
|
}
|
|
finally
|
|
{
|
|
if (this.transaction != null)
|
|
{
|
|
this.transaction.Complete();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|