e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
876 lines
32 KiB
C#
876 lines
32 KiB
C#
//------------------------------------------------------------
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//------------------------------------------------------------
|
|
|
|
namespace System.ServiceModel.Activities
|
|
{
|
|
using System.Activities;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Diagnostics;
|
|
using System.Runtime;
|
|
using System.Runtime.Diagnostics;
|
|
using System.Security;
|
|
using System.Security.Permissions;
|
|
using System.ServiceModel.Activation;
|
|
using System.ServiceModel.Activities.Description;
|
|
using System.ServiceModel.Activities.Dispatcher;
|
|
using System.ServiceModel.Channels;
|
|
using System.ServiceModel.Description;
|
|
using System.ServiceModel.Diagnostics;
|
|
using System.ServiceModel.Dispatcher;
|
|
using System.Transactions;
|
|
using TD2 = System.ServiceModel.Diagnostics.Application.TD;
|
|
|
|
// This AsyncResult completion is very tricky. It can complete in two path.
|
|
// FastPath: When workflow executes synchronously(SendFault/SendReply called before ResumeBookmark completes) we complete the AsyncResult at HandleEndResumeBookmark;
|
|
// Async Path: If workflow goes async (SendFault/SendReply called after ResumeBookmark completes) we complete the AsyncResult at SendFault/SendReply.
|
|
class WorkflowOperationContext : AsyncResult
|
|
{
|
|
static readonly ReadOnlyDictionaryInternal<string, string> emptyDictionary = new ReadOnlyDictionaryInternal<string, string>(new Dictionary<string, string>());
|
|
static readonly object[] emptyObjectArray = new object[0];
|
|
static AsyncCompletion handleEndResumeBookmark = new AsyncCompletion(HandleEndResumeBookmark);
|
|
static AsyncCompletion handleEndWaitForPendingOperations = new AsyncCompletion(HandleEndWaitForPendingOperations);
|
|
static AsyncCompletion handleEndProcessReceiveContext;
|
|
static Action<AsyncResult, Exception> onCompleting = new Action<AsyncResult, Exception>(Finally);
|
|
object[] inputs;
|
|
string operationName;
|
|
object[] outputs;
|
|
object operationReturnValue;
|
|
object thisLock;
|
|
|
|
WorkflowServiceInstance workflowInstance;
|
|
Bookmark bookmark;
|
|
object bookmarkValue;
|
|
BookmarkScope bookmarkScope;
|
|
|
|
IInvokeReceivedNotification notification;
|
|
IAsyncResult pendingAsyncResult;
|
|
|
|
TimeoutHelper timeoutHelper;
|
|
Exception pendingException;
|
|
|
|
ReceiveContext receiveContext;
|
|
|
|
// perf counter data
|
|
bool performanceCountersEnabled;
|
|
long beginTime;
|
|
|
|
// tracing data
|
|
bool propagateActivity;
|
|
Guid ambientActivityId;
|
|
Guid e2eActivityId;
|
|
EventTraceActivity eventTraceActivity;
|
|
long beginOperation;
|
|
|
|
//Tracking for decrement of ASP.NET busy count
|
|
bool hasDecrementedBusyCount;
|
|
|
|
WorkflowOperationContext(object[] inputs, OperationContext operationContext, string operationName,
|
|
bool performanceCountersEnabled, bool propagateActivity, Transaction currentTransaction,
|
|
WorkflowServiceInstance workflowInstance, IInvokeReceivedNotification notification, WorkflowOperationBehavior behavior, ServiceEndpoint endpoint,
|
|
TimeSpan timeout, AsyncCallback callback, object state)
|
|
: base(callback, state)
|
|
{
|
|
this.inputs = inputs;
|
|
this.operationName = operationName;
|
|
this.OperationContext = operationContext;
|
|
this.ServiceEndpoint = endpoint;
|
|
this.CurrentTransaction = currentTransaction;
|
|
this.performanceCountersEnabled = performanceCountersEnabled;
|
|
this.propagateActivity = propagateActivity;
|
|
this.timeoutHelper = new TimeoutHelper(timeout);
|
|
this.workflowInstance = workflowInstance;
|
|
this.thisLock = new object();
|
|
this.notification = notification;
|
|
this.OnCompleting = onCompleting;
|
|
|
|
// Resolve bookmark
|
|
Fx.Assert(behavior != null, "behavior must not be null!");
|
|
this.bookmark = behavior.OnResolveBookmark(this, out this.bookmarkScope, out this.bookmarkValue);
|
|
Fx.Assert(this.bookmark != null, "bookmark must not be null!");
|
|
|
|
bool completeSelf = false;
|
|
|
|
try
|
|
{
|
|
// set activity ID on the executing thread (Bug 113386)
|
|
if (TraceUtility.MessageFlowTracingOnly)
|
|
{
|
|
this.e2eActivityId = TraceUtility.GetReceivedActivityId(this.OperationContext);
|
|
DiagnosticTraceBase.ActivityId = this.e2eActivityId;
|
|
}
|
|
|
|
if (Fx.Trace.IsEtwProviderEnabled)
|
|
{
|
|
this.eventTraceActivity = EventTraceActivityHelper.TryExtractActivity(this.OperationContext.IncomingMessage);
|
|
}
|
|
|
|
// Take ownership of the ReceiveContext when buffering is enabled by removing the property
|
|
if (this.workflowInstance.BufferedReceiveManager != null)
|
|
{
|
|
if (!ReceiveContext.TryGet(this.OperationContext.IncomingMessageProperties, out this.receiveContext))
|
|
{
|
|
Fx.Assert("ReceiveContext expected when BufferedReceives are enabled");
|
|
}
|
|
|
|
this.OperationContext.IncomingMessageProperties.Remove(ReceiveContext.Name);
|
|
}
|
|
|
|
completeSelf = ProcessRequest();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (Fx.IsFatal(e))
|
|
{
|
|
throw;
|
|
}
|
|
|
|
// Failing synchronously is one case where AsyncResult won't handle calling OnCompleting
|
|
OnCompleting(this, e);
|
|
throw;
|
|
}
|
|
|
|
if (completeSelf)
|
|
{
|
|
base.Complete(true);
|
|
}
|
|
}
|
|
|
|
public object[] Inputs
|
|
{
|
|
get
|
|
{
|
|
return this.inputs;
|
|
}
|
|
}
|
|
|
|
public OperationContext OperationContext
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public ServiceEndpoint ServiceEndpoint
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public Transaction CurrentTransaction
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public object BookmarkValue
|
|
{
|
|
get
|
|
{
|
|
return this.bookmarkValue;
|
|
}
|
|
}
|
|
|
|
public bool HasResponse
|
|
{
|
|
get
|
|
{
|
|
lock (this.thisLock)
|
|
{
|
|
return this.CurrentState == State.Completed || this.CurrentState == State.ResultReceived;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Completion state of this AsyncResult guarded by propertyLock
|
|
State CurrentState
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public Guid E2EActivityId
|
|
{
|
|
get
|
|
{
|
|
return this.e2eActivityId;
|
|
}
|
|
}
|
|
|
|
public static IAsyncResult BeginProcessRequest(WorkflowServiceInstance workflowInstance, OperationContext operationContext, string operationName,
|
|
object[] inputs, bool performanceCountersEnabled, bool propagateActivity, Transaction currentTransaction, IInvokeReceivedNotification notification,
|
|
WorkflowOperationBehavior behavior, ServiceEndpoint endpoint, TimeSpan timeout, AsyncCallback callback, object state)
|
|
{
|
|
Fx.Assert(inputs != null, "Null inputs");
|
|
return new WorkflowOperationContext(inputs, operationContext, operationName, performanceCountersEnabled,
|
|
propagateActivity, currentTransaction, workflowInstance, notification, behavior, endpoint, timeout, callback, state);
|
|
}
|
|
|
|
public static object EndProcessRequest(IAsyncResult result, out object[] outputs)
|
|
{
|
|
WorkflowOperationContext thisPtr = AsyncResult.End<WorkflowOperationContext>(result);
|
|
outputs = thisPtr.outputs;
|
|
return thisPtr.operationReturnValue;
|
|
}
|
|
|
|
public void SendFault(Exception exception)
|
|
{
|
|
Fx.Assert(exception != null, "Null Exception");
|
|
|
|
this.pendingException = exception;
|
|
|
|
bool completeNow;
|
|
|
|
lock (this.thisLock)
|
|
{
|
|
Fx.Assert(this.CurrentState != State.Completed && this.CurrentState != State.ResultReceived, "Cannot receive this call after completion/result");
|
|
completeNow = ProcessReply();
|
|
}
|
|
|
|
if (completeNow)
|
|
{
|
|
this.Complete(false, exception);
|
|
}
|
|
}
|
|
|
|
public void SendReply(Message returnValue)
|
|
{
|
|
bool completeNow;
|
|
|
|
lock (this.thisLock)
|
|
{
|
|
Fx.Assert(this.CurrentState != State.Completed && this.CurrentState != State.ResultReceived, "Cannot receive this call after completion/result");
|
|
this.outputs = WorkflowOperationContext.emptyObjectArray; // everything is in the Message return value for workflow
|
|
this.operationReturnValue = returnValue;
|
|
completeNow = ProcessReply();
|
|
}
|
|
|
|
if (completeNow)
|
|
{
|
|
base.Complete(false);
|
|
}
|
|
}
|
|
|
|
public void SendReply(object returnValue, object[] outputs)
|
|
{
|
|
bool completeNow;
|
|
|
|
lock (this.thisLock)
|
|
{
|
|
Fx.Assert(this.CurrentState != State.Completed && this.CurrentState != State.ResultReceived, "Cannot receive this call after completion/result");
|
|
this.outputs = outputs ?? WorkflowOperationContext.emptyObjectArray;
|
|
this.operationReturnValue = returnValue;
|
|
completeNow = ProcessReply();
|
|
}
|
|
|
|
if (completeNow)
|
|
{
|
|
base.Complete(false);
|
|
}
|
|
}
|
|
|
|
//No-op for two-ways.
|
|
public void SetOperationCompleted()
|
|
{
|
|
bool completeNow;
|
|
|
|
lock (this.thisLock)
|
|
{
|
|
completeNow = ProcessReply();
|
|
}
|
|
|
|
if (completeNow)
|
|
{
|
|
base.Complete(false);
|
|
}
|
|
}
|
|
|
|
bool ProcessReply()
|
|
{
|
|
bool completed = false;
|
|
this.workflowInstance.ReleaseContext(this);
|
|
this.RemovePendingOperation();
|
|
|
|
if (this.CurrentState == State.BookmarkResumption) //We are still in Bookmark Resume
|
|
{
|
|
this.CurrentState = State.ResultReceived; //HandleEndResumeBookmark will take care of Completing AsyncResult.
|
|
}
|
|
else if (this.CurrentState == State.WaitForResult) //We already finished the bookmarkOperation; Now have to signal the AsynResult.
|
|
{
|
|
this.CurrentState = State.Completed;
|
|
completed = true;
|
|
}
|
|
|
|
// we are not really completed until the ReceiveContext finishes its work
|
|
if (completed)
|
|
{
|
|
if (this.pendingException == null)
|
|
{
|
|
completed = ProcessReceiveContext();
|
|
}
|
|
else
|
|
{
|
|
// if there's a pendingException, we let the RC abandon async so there's no need
|
|
// to affect the completed status
|
|
BufferedReceiveManager.AbandonReceiveContext(this.receiveContext);
|
|
}
|
|
}
|
|
|
|
return completed;
|
|
}
|
|
|
|
void ProcessInitializationTraces()
|
|
{
|
|
//Let asp.net know that it needs to wait
|
|
IncrementBusyCount();
|
|
|
|
try
|
|
{
|
|
if (TraceUtility.MessageFlowTracingOnly)
|
|
{
|
|
//ensure that Activity ID is set
|
|
DiagnosticTraceBase.ActivityId = this.E2EActivityId;
|
|
this.propagateActivity = false;
|
|
}
|
|
if (TraceUtility.ActivityTracing || (!TraceUtility.MessageFlowTracing && this.propagateActivity))
|
|
{
|
|
this.e2eActivityId = TraceUtility.GetReceivedActivityId(this.OperationContext);
|
|
|
|
if ((this.E2EActivityId != Guid.Empty) && (this.E2EActivityId != InternalReceiveMessage.TraceCorrelationActivityId))
|
|
{
|
|
this.propagateActivity = true;
|
|
this.OperationContext.IncomingMessageProperties[MessagingActivityHelper.E2EActivityId] = this.E2EActivityId;
|
|
this.ambientActivityId = InternalReceiveMessage.TraceCorrelationActivityId;
|
|
FxTrace.Trace.SetAndTraceTransfer(this.E2EActivityId, true);
|
|
if (TD.StartSignpostEventIsEnabled())
|
|
{
|
|
TD.StartSignpostEvent(new DictionaryTraceRecord(new Dictionary<string, string>(2) {
|
|
{ MessagingActivityHelper.ActivityName, MessagingActivityHelper.ActivityNameWorkflowOperationInvoke },
|
|
{ MessagingActivityHelper.ActivityType, MessagingActivityHelper.ActivityTypeExecuteUserCode }
|
|
}));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this.propagateActivity = false;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (Fx.IsFatal(ex))
|
|
{
|
|
throw;
|
|
}
|
|
FxTrace.Exception.AsInformation(ex);
|
|
}
|
|
}
|
|
|
|
|
|
void DecrementBusyCount()
|
|
{
|
|
lock (this.thisLock)
|
|
{
|
|
if (!this.hasDecrementedBusyCount)
|
|
{
|
|
AspNetEnvironment.Current.DecrementBusyCount();
|
|
if (AspNetEnvironment.Current.TraceDecrementBusyCountIsEnabled())
|
|
{
|
|
AspNetEnvironment.Current.TraceDecrementBusyCount(SR.BusyCountTraceFormatString(this.workflowInstance.Id));
|
|
}
|
|
this.hasDecrementedBusyCount = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void IncrementBusyCount()
|
|
{
|
|
AspNetEnvironment.Current.IncrementBusyCount();
|
|
if (AspNetEnvironment.Current.TraceIncrementBusyCountIsEnabled())
|
|
{
|
|
AspNetEnvironment.Current.TraceIncrementBusyCount(SR.BusyCountTraceFormatString(this.workflowInstance.Id));
|
|
}
|
|
}
|
|
|
|
void EmitTransferFromInstanceId()
|
|
{
|
|
if (TraceUtility.MessageFlowTracing)
|
|
{
|
|
//set the WF instance ID as the Activity ID
|
|
if (DiagnosticTraceBase.ActivityId != this.workflowInstance.Id)
|
|
{
|
|
DiagnosticTraceBase.ActivityId = this.workflowInstance.Id;
|
|
}
|
|
FxTrace.Trace.SetAndTraceTransfer(this.E2EActivityId, true);
|
|
}
|
|
}
|
|
|
|
void ProcessFinalizationTraces()
|
|
{
|
|
try
|
|
{
|
|
if (this.propagateActivity)
|
|
{
|
|
Guid oldId = InternalReceiveMessage.TraceCorrelationActivityId;
|
|
if (TD.StopSignpostEventIsEnabled())
|
|
{
|
|
TD.StopSignpostEvent(new DictionaryTraceRecord(new Dictionary<string, string>(2) {
|
|
{ MessagingActivityHelper.ActivityName, MessagingActivityHelper.ActivityNameWorkflowOperationInvoke },
|
|
{ MessagingActivityHelper.ActivityType, MessagingActivityHelper.ActivityTypeExecuteUserCode }
|
|
}));
|
|
}
|
|
FxTrace.Trace.SetAndTraceTransfer(this.ambientActivityId, true);
|
|
this.ambientActivityId = Guid.Empty;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (Fx.IsFatal(ex))
|
|
{
|
|
throw;
|
|
}
|
|
FxTrace.Exception.AsInformation(ex);
|
|
}
|
|
DecrementBusyCount();
|
|
}
|
|
|
|
//Perf counter helpers.
|
|
[Fx.Tag.SecurityNote(Critical = "Critical because it accesses UnsafeNativeMethods.QueryPerformanceCounter.",
|
|
Safe = "Safe because we only make the call if the PartialTrustHelper.AppDomainFullyTrusted is true.")]
|
|
[SecuritySafeCritical]
|
|
void TrackMethodCalled()
|
|
{
|
|
if (PartialTrustHelpers.AppDomainFullyTrusted && this.performanceCountersEnabled)
|
|
{
|
|
using (new OperationContextScopeHelper(this.OperationContext))
|
|
{
|
|
PerformanceCounters.MethodCalled(this.operationName);
|
|
}
|
|
|
|
if (System.Runtime.Interop.UnsafeNativeMethods.QueryPerformanceCounter(out this.beginTime) == 0)
|
|
{
|
|
this.beginTime = -1;
|
|
}
|
|
}
|
|
|
|
if (TD2.OperationCompletedIsEnabled() ||
|
|
TD2.OperationFaultedIsEnabled() ||
|
|
TD2.OperationFailedIsEnabled())
|
|
{
|
|
this.beginOperation = DateTime.UtcNow.Ticks;
|
|
}
|
|
|
|
if (TD2.OperationInvokedIsEnabled())
|
|
{
|
|
using (new OperationContextScopeHelper(this.OperationContext))
|
|
{
|
|
TD2.OperationInvoked(this.eventTraceActivity, this.operationName, TraceUtility.GetCallerInfo(this.OperationContext));
|
|
}
|
|
}
|
|
}
|
|
|
|
void TrackMethodFaulted()
|
|
{
|
|
if (this.performanceCountersEnabled)
|
|
{
|
|
long duration = this.GetDuration();
|
|
using (new OperationContextScopeHelper(this.OperationContext))
|
|
{
|
|
PerformanceCounters.MethodReturnedFault(this.operationName, duration);
|
|
}
|
|
}
|
|
if (TD2.OperationFaultedIsEnabled())
|
|
{
|
|
using (new OperationContextScopeHelper(this.OperationContext))
|
|
{
|
|
TD2.OperationFaulted(this.eventTraceActivity, this.operationName,
|
|
TraceUtility.GetUtcBasedDurationForTrace(this.beginOperation));
|
|
}
|
|
}
|
|
}
|
|
|
|
void TrackMethodFailed()
|
|
{
|
|
if (this.performanceCountersEnabled)
|
|
{
|
|
long duration = this.GetDuration();
|
|
using (new OperationContextScopeHelper(this.OperationContext))
|
|
{
|
|
PerformanceCounters.MethodReturnedError(this.operationName, duration);
|
|
}
|
|
}
|
|
if (TD2.OperationFailedIsEnabled())
|
|
{
|
|
using (new OperationContextScopeHelper(this.OperationContext))
|
|
{
|
|
TD2.OperationFailed(this.eventTraceActivity, this.operationName,
|
|
TraceUtility.GetUtcBasedDurationForTrace(this.beginOperation));
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fx.Tag.SecurityNote(Critical = "Critical because it accesses UnsafeNativeMethods.QueryPerformanceCounter.",
|
|
Safe = "Safe because we only make the call if the PartialTrustHelper.AppDomainFullyTrusted is true.")]
|
|
[SecuritySafeCritical]
|
|
long GetDuration()
|
|
{
|
|
long currentTime = 0;
|
|
long duration = 0;
|
|
|
|
if (PartialTrustHelpers.AppDomainFullyTrusted && this.performanceCountersEnabled && (this.beginTime >= 0) &&
|
|
(System.Runtime.Interop.UnsafeNativeMethods.QueryPerformanceCounter(out currentTime) != 0))
|
|
{
|
|
duration = currentTime - this.beginTime;
|
|
}
|
|
|
|
return duration;
|
|
}
|
|
|
|
void TrackMethodSucceeded()
|
|
{
|
|
if (this.performanceCountersEnabled)
|
|
{
|
|
long duration = this.GetDuration();
|
|
using (new OperationContextScopeHelper(this.OperationContext))
|
|
{
|
|
PerformanceCounters.MethodReturnedSuccess(this.operationName, duration);
|
|
}
|
|
}
|
|
if (TD2.OperationCompletedIsEnabled())
|
|
{
|
|
using (new OperationContextScopeHelper(this.OperationContext))
|
|
{
|
|
TD2.OperationCompleted(this.eventTraceActivity, this.operationName,
|
|
TraceUtility.GetUtcBasedDurationForTrace(this.beginOperation));
|
|
}
|
|
}
|
|
}
|
|
|
|
void TrackMethodCompleted(object returnValue)
|
|
{
|
|
// WorkflowOperationInvoker always deals at the Message/Message level
|
|
Message faultMessage = returnValue as Message;
|
|
if (faultMessage != null && faultMessage.IsFault)
|
|
{
|
|
TrackMethodFaulted();
|
|
}
|
|
else
|
|
{
|
|
TrackMethodSucceeded();
|
|
}
|
|
}
|
|
|
|
bool ProcessRequest()
|
|
{
|
|
this.TrackMethodCalled();
|
|
this.ProcessInitializationTraces();
|
|
|
|
if (this.notification == null)
|
|
{
|
|
return OnResumeBookmark();
|
|
}
|
|
|
|
string sessionId = this.OperationContext.SessionId;
|
|
if (sessionId == null)
|
|
{
|
|
return OnResumeBookmark();
|
|
}
|
|
|
|
// if there is a session, queue up this request in the per session pending request queue before notifying
|
|
// the dispatcher to start the next invoke
|
|
IAsyncResult pendingAsyncResult = this.workflowInstance.BeginWaitForPendingOperations(sessionId, this.timeoutHelper.RemainingTime(), this.PrepareAsyncCompletion(handleEndWaitForPendingOperations), this);
|
|
bool completed;
|
|
|
|
this.notification.NotifyInvokeReceived();
|
|
if (pendingAsyncResult.CompletedSynchronously)
|
|
{
|
|
completed = HandleEndWaitForPendingOperations(pendingAsyncResult);
|
|
}
|
|
else
|
|
{
|
|
completed = false;
|
|
}
|
|
|
|
return completed;
|
|
}
|
|
|
|
void RemovePendingOperation()
|
|
{
|
|
if (this.pendingAsyncResult != null)
|
|
{
|
|
this.workflowInstance.RemovePendingOperation(this.OperationContext.SessionId, this.pendingAsyncResult);
|
|
this.pendingAsyncResult = null;
|
|
}
|
|
}
|
|
|
|
static bool HandleEndWaitForPendingOperations(IAsyncResult result)
|
|
{
|
|
WorkflowOperationContext thisPtr = (WorkflowOperationContext)result.AsyncState;
|
|
thisPtr.pendingAsyncResult = result;
|
|
|
|
bool success = false;
|
|
try
|
|
{
|
|
thisPtr.workflowInstance.EndWaitForPendingOperations(result);
|
|
bool retval = thisPtr.OnResumeBookmark();
|
|
success = true;
|
|
return retval;
|
|
}
|
|
finally
|
|
{
|
|
if (!success)
|
|
{
|
|
thisPtr.RemovePendingOperation();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool OnResumeBookmark()
|
|
{
|
|
bool success = false;
|
|
try
|
|
{
|
|
IAsyncResult nextResult = this.workflowInstance.BeginResumeProtocolBookmark(
|
|
this.bookmark, this.bookmarkScope, this,
|
|
this.timeoutHelper.RemainingTime(), this.PrepareAsyncCompletion(handleEndResumeBookmark), this);
|
|
|
|
bool completed;
|
|
if (nextResult.CompletedSynchronously)
|
|
{
|
|
completed = HandleEndResumeBookmark(nextResult);
|
|
}
|
|
else
|
|
{
|
|
completed = false;
|
|
}
|
|
|
|
success = true;
|
|
return completed;
|
|
}
|
|
finally
|
|
{
|
|
if (!success)
|
|
{
|
|
this.RemovePendingOperation();
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool HandleEndResumeBookmark(IAsyncResult result)
|
|
{
|
|
WorkflowOperationContext thisPtr = (WorkflowOperationContext)result.AsyncState;
|
|
|
|
bool completed = false;
|
|
bool shouldAbandon = true;
|
|
try
|
|
{
|
|
BookmarkResumptionResult resumptionResult = thisPtr.workflowInstance.EndResumeProtocolBookmark(result);
|
|
if (resumptionResult != BookmarkResumptionResult.Success)
|
|
{
|
|
// Raise UnkownMessageReceivedEvent when we fail to resume bookmark
|
|
thisPtr.OperationContext.Host.RaiseUnknownMessageReceived(thisPtr.OperationContext.IncomingMessage);
|
|
|
|
// Only delay-retry this operation once (and only if retries are supported). Future calls will ensure the bookmark is set.
|
|
if (thisPtr.workflowInstance.BufferedReceiveManager != null)
|
|
{
|
|
bool bufferSuccess = thisPtr.workflowInstance.BufferedReceiveManager.BufferReceive(
|
|
thisPtr.OperationContext, thisPtr.receiveContext, thisPtr.bookmark.Name, BufferedReceiveState.WaitingOnBookmark, false);
|
|
if (bufferSuccess)
|
|
{
|
|
if (TD.BufferOutOfOrderMessageNoBookmarkIsEnabled())
|
|
{
|
|
TD.BufferOutOfOrderMessageNoBookmark(thisPtr.eventTraceActivity, thisPtr.workflowInstance.Id.ToString(), thisPtr.bookmark.Name);
|
|
}
|
|
|
|
shouldAbandon = false;
|
|
}
|
|
}
|
|
|
|
// The throw exception is intentional whether or not BufferedReceiveManager is set.
|
|
// This is to allow exception to bubble up the stack to WCF to cleanup various state (like Transaction).
|
|
// This is queue scenario and as far as the client is concerned, the client will not see any exception.
|
|
throw FxTrace.Exception.AsError(new FaultException(OperationExecutionFault.CreateOperationNotAvailableFault(thisPtr.workflowInstance.Id, thisPtr.bookmark.Name)));
|
|
}
|
|
|
|
lock (thisPtr.thisLock)
|
|
{
|
|
if (thisPtr.CurrentState == State.ResultReceived)
|
|
{
|
|
thisPtr.CurrentState = State.Completed;
|
|
if (thisPtr.pendingException != null)
|
|
{
|
|
throw FxTrace.Exception.AsError(thisPtr.pendingException);
|
|
}
|
|
completed = true;
|
|
}
|
|
else
|
|
{
|
|
thisPtr.CurrentState = State.WaitForResult;
|
|
completed = false;
|
|
}
|
|
|
|
// we are not really completed until the ReceiveContext finishes its work
|
|
if (completed)
|
|
{
|
|
completed = thisPtr.ProcessReceiveContext();
|
|
}
|
|
|
|
shouldAbandon = false;
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (shouldAbandon)
|
|
{
|
|
BufferedReceiveManager.AbandonReceiveContext(thisPtr.receiveContext);
|
|
}
|
|
thisPtr.RemovePendingOperation();
|
|
}
|
|
|
|
return completed;
|
|
}
|
|
|
|
bool ProcessReceiveContext()
|
|
{
|
|
if (this.receiveContext != null)
|
|
{
|
|
if (handleEndProcessReceiveContext == null)
|
|
{
|
|
handleEndProcessReceiveContext = new AsyncCompletion(HandleEndProcessReceiveContext);
|
|
}
|
|
|
|
IAsyncResult nextResult = ReceiveContextAsyncResult.BeginProcessReceiveContext(this, this.receiveContext, PrepareAsyncCompletion(handleEndProcessReceiveContext), this);
|
|
return SyncContinue(nextResult);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool HandleEndProcessReceiveContext(IAsyncResult result)
|
|
{
|
|
ReceiveContextAsyncResult.EndProcessReceiveContext(result);
|
|
return true;
|
|
}
|
|
|
|
static void Finally(AsyncResult result, Exception completionException)
|
|
{
|
|
WorkflowOperationContext thisPtr = (WorkflowOperationContext)result;
|
|
thisPtr.EmitTransferFromInstanceId();
|
|
|
|
if (completionException != null)
|
|
{
|
|
if (completionException is FaultException)
|
|
{
|
|
thisPtr.TrackMethodFaulted();
|
|
}
|
|
else
|
|
{
|
|
thisPtr.TrackMethodFailed();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
thisPtr.TrackMethodCompleted(thisPtr.operationReturnValue);
|
|
}
|
|
thisPtr.ProcessFinalizationTraces();
|
|
// will be a no-op if we were never added to the instance
|
|
thisPtr.workflowInstance.ReleaseContext(thisPtr);
|
|
thisPtr.RemovePendingOperation();
|
|
}
|
|
|
|
enum State
|
|
{
|
|
BookmarkResumption,
|
|
WaitForResult,
|
|
ResultReceived,
|
|
Completed
|
|
}
|
|
|
|
class OperationContextScopeHelper : IDisposable
|
|
{
|
|
OperationContext currentOperationContext;
|
|
|
|
public OperationContextScopeHelper(OperationContext operationContext)
|
|
{
|
|
this.currentOperationContext = OperationContext.Current;
|
|
OperationContext.Current = operationContext;
|
|
}
|
|
|
|
void IDisposable.Dispose()
|
|
{
|
|
OperationContext.Current = this.currentOperationContext;
|
|
}
|
|
}
|
|
|
|
class ReceiveContextAsyncResult : TransactedAsyncResult
|
|
{
|
|
static AsyncCompletion handleEndComplete = new AsyncCompletion(HandleEndComplete);
|
|
|
|
WorkflowOperationContext context;
|
|
ReceiveContext receiveContext;
|
|
|
|
ReceiveContextAsyncResult(WorkflowOperationContext context, ReceiveContext receiveContext, AsyncCallback callback, object state)
|
|
: base(callback, state)
|
|
{
|
|
this.context = context;
|
|
this.receiveContext = receiveContext;
|
|
|
|
if (ProcessReceiveContext())
|
|
{
|
|
base.Complete(true);
|
|
}
|
|
}
|
|
|
|
public static IAsyncResult BeginProcessReceiveContext(WorkflowOperationContext context, ReceiveContext receiveContext, AsyncCallback callback, object state)
|
|
{
|
|
return new ReceiveContextAsyncResult(context, receiveContext, callback, state);
|
|
}
|
|
|
|
public static void EndProcessReceiveContext(IAsyncResult result)
|
|
{
|
|
ReceiveContextAsyncResult.End(result);
|
|
}
|
|
|
|
public static void End(IAsyncResult result)
|
|
{
|
|
AsyncResult.End<ReceiveContextAsyncResult>(result);
|
|
}
|
|
|
|
bool ProcessReceiveContext()
|
|
{
|
|
IAsyncResult result;
|
|
|
|
using (PrepareTransactionalCall(this.context.CurrentTransaction))
|
|
{
|
|
if (this.context.CurrentTransaction != null)
|
|
{
|
|
// make sure we Abandon if the transaction ends up with an outcome of Aborted
|
|
this.context.CurrentTransaction.TransactionCompleted += new TransactionCompletedEventHandler(OnTransactionComplete);
|
|
}
|
|
result = this.receiveContext.BeginComplete(
|
|
this.context.timeoutHelper.RemainingTime(), PrepareAsyncCompletion(handleEndComplete), this);
|
|
}
|
|
|
|
return SyncContinue(result);
|
|
}
|
|
|
|
// This happens out-of-band of ReceiveAsyncResult.
|
|
// When transaction was aborted, we best-effort abandon the context.
|
|
void OnTransactionComplete(object sender, TransactionEventArgs e)
|
|
{
|
|
if (e.Transaction.TransactionInformation.Status != TransactionStatus.Committed)
|
|
{
|
|
BufferedReceiveManager.AbandonReceiveContext(this.context.receiveContext);
|
|
}
|
|
}
|
|
|
|
static bool HandleEndComplete(IAsyncResult result)
|
|
{
|
|
ReceiveContextAsyncResult thisPtr = (ReceiveContextAsyncResult)result.AsyncState;
|
|
thisPtr.receiveContext.EndComplete(result);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|