1 //------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //------------------------------------------------------------
4 
5 namespace System.ServiceModel.Activities
6 {
7     using System.Activities;
8     using System.Collections.Generic;
9     using System.Collections.ObjectModel;
10     using System.Diagnostics;
11     using System.Runtime;
12     using System.Runtime.Diagnostics;
13     using System.Security;
14     using System.Security.Permissions;
15     using System.ServiceModel.Activation;
16     using System.ServiceModel.Activities.Description;
17     using System.ServiceModel.Activities.Dispatcher;
18     using System.ServiceModel.Channels;
19     using System.ServiceModel.Description;
20     using System.ServiceModel.Diagnostics;
21     using System.ServiceModel.Dispatcher;
22     using System.Transactions;
23     using TD2 = System.ServiceModel.Diagnostics.Application.TD;
24 
25     // This AsyncResult completion is very tricky. It can complete in two path.
26     // FastPath: When workflow executes synchronously(SendFault/SendReply called before ResumeBookmark completes) we complete the AsyncResult at HandleEndResumeBookmark;
27     // Async Path: If workflow goes async (SendFault/SendReply called after ResumeBookmark completes) we complete the AsyncResult at SendFault/SendReply.
28     class WorkflowOperationContext : AsyncResult
29     {
30         static readonly ReadOnlyDictionaryInternal<string, string> emptyDictionary = new ReadOnlyDictionaryInternal<string, string>(new Dictionary<string, string>());
31         static readonly object[] emptyObjectArray = new object[0];
32         static AsyncCompletion handleEndResumeBookmark = new AsyncCompletion(HandleEndResumeBookmark);
33         static AsyncCompletion handleEndWaitForPendingOperations = new AsyncCompletion(HandleEndWaitForPendingOperations);
34         static AsyncCompletion handleEndProcessReceiveContext;
35         static Action<AsyncResult, Exception> onCompleting = new Action<AsyncResult, Exception>(Finally);
36         object[] inputs;
37         string operationName;
38         object[] outputs;
39         object operationReturnValue;
40         object thisLock;
41 
42         WorkflowServiceInstance workflowInstance;
43         Bookmark bookmark;
44         object bookmarkValue;
45         BookmarkScope bookmarkScope;
46 
47         IInvokeReceivedNotification notification;
48         IAsyncResult pendingAsyncResult;
49 
50         TimeoutHelper timeoutHelper;
51         Exception pendingException;
52 
53         ReceiveContext receiveContext;
54 
55         // perf counter data
56         bool performanceCountersEnabled;
57         long beginTime;
58 
59         // tracing data
60         bool propagateActivity;
61         Guid ambientActivityId;
62         Guid e2eActivityId;
63         EventTraceActivity eventTraceActivity;
64         long beginOperation;
65 
66         //Tracking for decrement of ASP.NET busy count
67         bool hasDecrementedBusyCount;
68 
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)69         WorkflowOperationContext(object[] inputs, OperationContext operationContext, string operationName,
70             bool performanceCountersEnabled, bool propagateActivity, Transaction currentTransaction,
71             WorkflowServiceInstance workflowInstance, IInvokeReceivedNotification notification, WorkflowOperationBehavior behavior, ServiceEndpoint endpoint,
72             TimeSpan timeout, AsyncCallback callback, object state)
73             : base(callback, state)
74         {
75             this.inputs = inputs;
76             this.operationName = operationName;
77             this.OperationContext = operationContext;
78             this.ServiceEndpoint = endpoint;
79             this.CurrentTransaction = currentTransaction;
80             this.performanceCountersEnabled = performanceCountersEnabled;
81             this.propagateActivity = propagateActivity;
82             this.timeoutHelper = new TimeoutHelper(timeout);
83             this.workflowInstance = workflowInstance;
84             this.thisLock = new object();
85             this.notification = notification;
86             this.OnCompleting = onCompleting;
87 
88             // Resolve bookmark
89             Fx.Assert(behavior != null, "behavior must not be null!");
90             this.bookmark = behavior.OnResolveBookmark(this, out this.bookmarkScope, out this.bookmarkValue);
91             Fx.Assert(this.bookmark != null, "bookmark must not be null!");
92 
93             bool completeSelf = false;
94 
95             try
96             {
97                 // set activity ID on the executing thread (Bug 113386)
98                 if (TraceUtility.MessageFlowTracingOnly)
99                 {
100                     this.e2eActivityId = TraceUtility.GetReceivedActivityId(this.OperationContext);
101                     DiagnosticTraceBase.ActivityId = this.e2eActivityId;
102                 }
103 
104                 if (Fx.Trace.IsEtwProviderEnabled)
105                 {
106                     this.eventTraceActivity = EventTraceActivityHelper.TryExtractActivity(this.OperationContext.IncomingMessage);
107                 }
108 
109                 // Take ownership of the ReceiveContext when buffering is enabled by removing the property
110                 if (this.workflowInstance.BufferedReceiveManager != null)
111                 {
112                     if (!ReceiveContext.TryGet(this.OperationContext.IncomingMessageProperties, out this.receiveContext))
113                     {
114                         Fx.Assert("ReceiveContext expected when BufferedReceives are enabled");
115                     }
116 
117                     this.OperationContext.IncomingMessageProperties.Remove(ReceiveContext.Name);
118                 }
119 
120                 completeSelf = ProcessRequest();
121             }
122             catch (Exception e)
123             {
124                 if (Fx.IsFatal(e))
125                 {
126                     throw;
127                 }
128 
129                 // Failing synchronously is one case where AsyncResult won't handle calling OnCompleting
130                 OnCompleting(this, e);
131                 throw;
132             }
133 
134             if (completeSelf)
135             {
136                 base.Complete(true);
137             }
138         }
139 
140         public object[] Inputs
141         {
142             get
143             {
144                 return this.inputs;
145             }
146         }
147 
148         public OperationContext OperationContext
149         {
150             get;
151             private set;
152         }
153 
154         public ServiceEndpoint ServiceEndpoint
155         {
156             get;
157             private set;
158         }
159 
160         public Transaction CurrentTransaction
161         {
162             get;
163             private set;
164         }
165 
166         public object BookmarkValue
167         {
168             get
169             {
170                 return this.bookmarkValue;
171             }
172         }
173 
174         public bool HasResponse
175         {
176             get
177             {
178                 lock (this.thisLock)
179                 {
180                     return this.CurrentState == State.Completed || this.CurrentState == State.ResultReceived;
181                 }
182             }
183         }
184 
185         //Completion state of this AsyncResult guarded by propertyLock
186         State CurrentState
187         {
188             get;
189             set;
190         }
191 
192         public Guid E2EActivityId
193         {
194             get
195             {
196                 return this.e2eActivityId;
197             }
198         }
199 
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)200         public static IAsyncResult BeginProcessRequest(WorkflowServiceInstance workflowInstance, OperationContext operationContext, string operationName,
201             object[] inputs, bool performanceCountersEnabled, bool propagateActivity, Transaction currentTransaction, IInvokeReceivedNotification notification,
202             WorkflowOperationBehavior behavior, ServiceEndpoint endpoint, TimeSpan timeout, AsyncCallback callback, object state)
203         {
204             Fx.Assert(inputs != null, "Null inputs");
205             return new WorkflowOperationContext(inputs, operationContext, operationName, performanceCountersEnabled,
206                 propagateActivity, currentTransaction, workflowInstance, notification, behavior, endpoint, timeout, callback, state);
207         }
208 
EndProcessRequest(IAsyncResult result, out object[] outputs)209         public static object EndProcessRequest(IAsyncResult result, out object[] outputs)
210         {
211             WorkflowOperationContext thisPtr = AsyncResult.End<WorkflowOperationContext>(result);
212             outputs = thisPtr.outputs;
213             return thisPtr.operationReturnValue;
214         }
215 
SendFault(Exception exception)216         public void SendFault(Exception exception)
217         {
218             Fx.Assert(exception != null, "Null Exception");
219 
220             this.pendingException = exception;
221 
222             bool completeNow;
223 
224             lock (this.thisLock)
225             {
226                 Fx.Assert(this.CurrentState != State.Completed && this.CurrentState != State.ResultReceived, "Cannot receive this call after completion/result");
227                 completeNow = ProcessReply();
228             }
229 
230             if (completeNow)
231             {
232                 this.Complete(false, exception);
233             }
234         }
235 
SendReply(Message returnValue)236         public void SendReply(Message returnValue)
237         {
238             bool completeNow;
239 
240             lock (this.thisLock)
241             {
242                 Fx.Assert(this.CurrentState != State.Completed && this.CurrentState != State.ResultReceived, "Cannot receive this call after completion/result");
243                 this.outputs = WorkflowOperationContext.emptyObjectArray; // everything is in the Message return value for workflow
244                 this.operationReturnValue = returnValue;
245                 completeNow = ProcessReply();
246             }
247 
248             if (completeNow)
249             {
250                 base.Complete(false);
251             }
252         }
253 
SendReply(object returnValue, object[] outputs)254         public void SendReply(object returnValue, object[] outputs)
255         {
256             bool completeNow;
257 
258             lock (this.thisLock)
259             {
260                 Fx.Assert(this.CurrentState != State.Completed && this.CurrentState != State.ResultReceived, "Cannot receive this call after completion/result");
261                 this.outputs = outputs ?? WorkflowOperationContext.emptyObjectArray;
262                 this.operationReturnValue = returnValue;
263                 completeNow = ProcessReply();
264             }
265 
266             if (completeNow)
267             {
268                 base.Complete(false);
269             }
270         }
271 
272         //No-op for two-ways.
SetOperationCompleted()273         public void SetOperationCompleted()
274         {
275             bool completeNow;
276 
277             lock (this.thisLock)
278             {
279                 completeNow = ProcessReply();
280             }
281 
282             if (completeNow)
283             {
284                 base.Complete(false);
285             }
286         }
287 
ProcessReply()288         bool ProcessReply()
289         {
290             bool completed = false;
291             this.workflowInstance.ReleaseContext(this);
292             this.RemovePendingOperation();
293 
294             if (this.CurrentState == State.BookmarkResumption) //We are still in Bookmark Resume
295             {
296                 this.CurrentState = State.ResultReceived; //HandleEndResumeBookmark will take care of Completing AsyncResult.
297             }
298             else if (this.CurrentState == State.WaitForResult) //We already finished the bookmarkOperation; Now have to signal the AsynResult.
299             {
300                 this.CurrentState = State.Completed;
301                 completed = true;
302             }
303 
304             // we are not really completed until the ReceiveContext finishes its work
305             if (completed)
306             {
307                 if (this.pendingException == null)
308                 {
309                     completed = ProcessReceiveContext();
310                 }
311                 else
312                 {
313                     // if there's a pendingException, we let the RC abandon async so there's no need
314                     // to affect the completed status
315                     BufferedReceiveManager.AbandonReceiveContext(this.receiveContext);
316                 }
317             }
318 
319             return completed;
320         }
321 
ProcessInitializationTraces()322         void ProcessInitializationTraces()
323         {
324             //Let asp.net know that it needs to wait
325             IncrementBusyCount();
326 
327             try
328             {
329                 if (TraceUtility.MessageFlowTracingOnly)
330                 {
331                     //ensure that Activity ID is set
332                     DiagnosticTraceBase.ActivityId = this.E2EActivityId;
333                     this.propagateActivity = false;
334                 }
335                 if (TraceUtility.ActivityTracing || (!TraceUtility.MessageFlowTracing && this.propagateActivity))
336                 {
337                     this.e2eActivityId = TraceUtility.GetReceivedActivityId(this.OperationContext);
338 
339                     if ((this.E2EActivityId != Guid.Empty) && (this.E2EActivityId != InternalReceiveMessage.TraceCorrelationActivityId))
340                     {
341                         this.propagateActivity = true;
342                         this.OperationContext.IncomingMessageProperties[MessagingActivityHelper.E2EActivityId] = this.E2EActivityId;
343                         this.ambientActivityId = InternalReceiveMessage.TraceCorrelationActivityId;
344                         FxTrace.Trace.SetAndTraceTransfer(this.E2EActivityId, true);
345                         if (TD.StartSignpostEventIsEnabled())
346                         {
347                             TD.StartSignpostEvent(new DictionaryTraceRecord(new Dictionary<string, string>(2) {
348                                                     { MessagingActivityHelper.ActivityName, MessagingActivityHelper.ActivityNameWorkflowOperationInvoke },
349                                                     { MessagingActivityHelper.ActivityType, MessagingActivityHelper.ActivityTypeExecuteUserCode }
350                             }));
351                         }
352                     }
353                     else
354                     {
355                         this.propagateActivity = false;
356                     }
357                 }
358             }
359             catch (Exception ex)
360             {
361                 if (Fx.IsFatal(ex))
362                 {
363                     throw;
364                 }
365                 FxTrace.Exception.AsInformation(ex);
366             }
367         }
368 
369 
DecrementBusyCount()370         void DecrementBusyCount()
371         {
372             lock (this.thisLock)
373             {
374                 if (!this.hasDecrementedBusyCount)
375                 {
376                     AspNetEnvironment.Current.DecrementBusyCount();
377                     if (AspNetEnvironment.Current.TraceDecrementBusyCountIsEnabled())
378                     {
379                         AspNetEnvironment.Current.TraceDecrementBusyCount(SR.BusyCountTraceFormatString(this.workflowInstance.Id));
380                     }
381                     this.hasDecrementedBusyCount = true;
382                 }
383             }
384         }
385 
IncrementBusyCount()386         void IncrementBusyCount()
387         {
388             AspNetEnvironment.Current.IncrementBusyCount();
389             if (AspNetEnvironment.Current.TraceIncrementBusyCountIsEnabled())
390             {
391                 AspNetEnvironment.Current.TraceIncrementBusyCount(SR.BusyCountTraceFormatString(this.workflowInstance.Id));
392             }
393         }
394 
EmitTransferFromInstanceId()395         void EmitTransferFromInstanceId()
396         {
397             if (TraceUtility.MessageFlowTracing)
398             {
399                 //set the WF instance ID as the Activity ID
400                 if (DiagnosticTraceBase.ActivityId != this.workflowInstance.Id)
401                 {
402                     DiagnosticTraceBase.ActivityId = this.workflowInstance.Id;
403                 }
404                 FxTrace.Trace.SetAndTraceTransfer(this.E2EActivityId, true);
405             }
406         }
407 
ProcessFinalizationTraces()408         void ProcessFinalizationTraces()
409         {
410             try
411             {
412                 if (this.propagateActivity)
413                 {
414                     Guid oldId = InternalReceiveMessage.TraceCorrelationActivityId;
415                     if (TD.StopSignpostEventIsEnabled())
416                     {
417                         TD.StopSignpostEvent(new DictionaryTraceRecord(new Dictionary<string, string>(2) {
418                                                     { MessagingActivityHelper.ActivityName, MessagingActivityHelper.ActivityNameWorkflowOperationInvoke },
419                                                     { MessagingActivityHelper.ActivityType, MessagingActivityHelper.ActivityTypeExecuteUserCode }
420                         }));
421                     }
422                     FxTrace.Trace.SetAndTraceTransfer(this.ambientActivityId, true);
423                     this.ambientActivityId = Guid.Empty;
424                 }
425             }
426             catch (Exception ex)
427             {
428                 if (Fx.IsFatal(ex))
429                 {
430                     throw;
431                 }
432                 FxTrace.Exception.AsInformation(ex);
433             }
434             DecrementBusyCount();
435         }
436 
437         //Perf counter helpers.
438         [Fx.Tag.SecurityNote(Critical = "Critical because it accesses UnsafeNativeMethods.QueryPerformanceCounter.",
439             Safe = "Safe because we only make the call if the PartialTrustHelper.AppDomainFullyTrusted is true.")]
440         [SecuritySafeCritical]
TrackMethodCalled()441         void TrackMethodCalled()
442         {
443             if (PartialTrustHelpers.AppDomainFullyTrusted && this.performanceCountersEnabled)
444             {
445                 using (new OperationContextScopeHelper(this.OperationContext))
446                 {
447                     PerformanceCounters.MethodCalled(this.operationName);
448                 }
449 
450                 if (System.Runtime.Interop.UnsafeNativeMethods.QueryPerformanceCounter(out this.beginTime) == 0)
451                 {
452                     this.beginTime = -1;
453                 }
454             }
455 
456             if (TD2.OperationCompletedIsEnabled() ||
457                     TD2.OperationFaultedIsEnabled() ||
458                     TD2.OperationFailedIsEnabled())
459             {
460                 this.beginOperation = DateTime.UtcNow.Ticks;
461             }
462 
463             if (TD2.OperationInvokedIsEnabled())
464             {
465                 using (new OperationContextScopeHelper(this.OperationContext))
466                 {
467                     TD2.OperationInvoked(this.eventTraceActivity, this.operationName, TraceUtility.GetCallerInfo(this.OperationContext));
468                 }
469             }
470         }
471 
TrackMethodFaulted()472         void TrackMethodFaulted()
473         {
474             if (this.performanceCountersEnabled)
475             {
476                 long duration = this.GetDuration();
477                 using (new OperationContextScopeHelper(this.OperationContext))
478                 {
479                     PerformanceCounters.MethodReturnedFault(this.operationName, duration);
480                 }
481             }
482             if (TD2.OperationFaultedIsEnabled())
483             {
484                 using (new OperationContextScopeHelper(this.OperationContext))
485                 {
486                     TD2.OperationFaulted(this.eventTraceActivity, this.operationName,
487                         TraceUtility.GetUtcBasedDurationForTrace(this.beginOperation));
488                 }
489             }
490         }
491 
TrackMethodFailed()492         void TrackMethodFailed()
493         {
494             if (this.performanceCountersEnabled)
495             {
496                 long duration = this.GetDuration();
497                 using (new OperationContextScopeHelper(this.OperationContext))
498                 {
499                     PerformanceCounters.MethodReturnedError(this.operationName, duration);
500                 }
501             }
502             if (TD2.OperationFailedIsEnabled())
503             {
504                 using (new OperationContextScopeHelper(this.OperationContext))
505                 {
506                     TD2.OperationFailed(this.eventTraceActivity, this.operationName,
507                         TraceUtility.GetUtcBasedDurationForTrace(this.beginOperation));
508                 }
509             }
510         }
511 
512         [Fx.Tag.SecurityNote(Critical = "Critical because it accesses UnsafeNativeMethods.QueryPerformanceCounter.",
513             Safe = "Safe because we only make the call if the PartialTrustHelper.AppDomainFullyTrusted is true.")]
514         [SecuritySafeCritical]
GetDuration()515         long GetDuration()
516         {
517             long currentTime = 0;
518             long duration = 0;
519 
520             if (PartialTrustHelpers.AppDomainFullyTrusted && this.performanceCountersEnabled && (this.beginTime >= 0) &&
521                 (System.Runtime.Interop.UnsafeNativeMethods.QueryPerformanceCounter(out currentTime) != 0))
522             {
523                 duration = currentTime - this.beginTime;
524             }
525 
526             return duration;
527         }
528 
TrackMethodSucceeded()529         void TrackMethodSucceeded()
530         {
531             if (this.performanceCountersEnabled)
532             {
533                 long duration = this.GetDuration();
534                 using (new OperationContextScopeHelper(this.OperationContext))
535                 {
536                     PerformanceCounters.MethodReturnedSuccess(this.operationName, duration);
537                 }
538             }
539             if (TD2.OperationCompletedIsEnabled())
540             {
541                 using (new OperationContextScopeHelper(this.OperationContext))
542                 {
543                     TD2.OperationCompleted(this.eventTraceActivity, this.operationName,
544                         TraceUtility.GetUtcBasedDurationForTrace(this.beginOperation));
545                 }
546             }
547         }
548 
TrackMethodCompleted(object returnValue)549         void TrackMethodCompleted(object returnValue)
550         {
551             // WorkflowOperationInvoker always deals at the Message/Message level
552             Message faultMessage = returnValue as Message;
553             if (faultMessage != null && faultMessage.IsFault)
554             {
555                 TrackMethodFaulted();
556             }
557             else
558             {
559                 TrackMethodSucceeded();
560             }
561         }
562 
ProcessRequest()563         bool ProcessRequest()
564         {
565             this.TrackMethodCalled();
566             this.ProcessInitializationTraces();
567 
568             if (this.notification == null)
569             {
570                 return OnResumeBookmark();
571             }
572 
573             string sessionId = this.OperationContext.SessionId;
574             if (sessionId == null)
575             {
576                 return OnResumeBookmark();
577             }
578 
579             // if there is a session, queue up this request in the per session pending request queue before notifying
580             // the dispatcher to start the next invoke
581             IAsyncResult pendingAsyncResult = this.workflowInstance.BeginWaitForPendingOperations(sessionId, this.timeoutHelper.RemainingTime(), this.PrepareAsyncCompletion(handleEndWaitForPendingOperations), this);
582             bool completed;
583 
584             this.notification.NotifyInvokeReceived();
585             if (pendingAsyncResult.CompletedSynchronously)
586             {
587                 completed = HandleEndWaitForPendingOperations(pendingAsyncResult);
588             }
589             else
590             {
591                 completed = false;
592             }
593 
594             return completed;
595         }
596 
RemovePendingOperation()597         void RemovePendingOperation()
598         {
599             if (this.pendingAsyncResult != null)
600             {
601                 this.workflowInstance.RemovePendingOperation(this.OperationContext.SessionId, this.pendingAsyncResult);
602                 this.pendingAsyncResult = null;
603             }
604         }
605 
HandleEndWaitForPendingOperations(IAsyncResult result)606         static bool HandleEndWaitForPendingOperations(IAsyncResult result)
607         {
608             WorkflowOperationContext thisPtr = (WorkflowOperationContext)result.AsyncState;
609             thisPtr.pendingAsyncResult = result;
610 
611             bool success = false;
612             try
613             {
614                 thisPtr.workflowInstance.EndWaitForPendingOperations(result);
615                 bool retval = thisPtr.OnResumeBookmark();
616                 success = true;
617                 return retval;
618             }
619             finally
620             {
621                 if (!success)
622                 {
623                     thisPtr.RemovePendingOperation();
624                 }
625             }
626         }
627 
OnResumeBookmark()628         bool OnResumeBookmark()
629         {
630             bool success = false;
631             try
632             {
633                 IAsyncResult nextResult = this.workflowInstance.BeginResumeProtocolBookmark(
634                     this.bookmark, this.bookmarkScope, this,
635                     this.timeoutHelper.RemainingTime(), this.PrepareAsyncCompletion(handleEndResumeBookmark), this);
636 
637                 bool completed;
638                 if (nextResult.CompletedSynchronously)
639                 {
640                     completed = HandleEndResumeBookmark(nextResult);
641                 }
642                 else
643                 {
644                     completed = false;
645                 }
646 
647                 success = true;
648                 return completed;
649             }
650             finally
651             {
652                 if (!success)
653                 {
654                     this.RemovePendingOperation();
655                 }
656             }
657         }
658 
HandleEndResumeBookmark(IAsyncResult result)659         static bool HandleEndResumeBookmark(IAsyncResult result)
660         {
661             WorkflowOperationContext thisPtr = (WorkflowOperationContext)result.AsyncState;
662 
663             bool completed = false;
664             bool shouldAbandon = true;
665             try
666             {
667                 BookmarkResumptionResult resumptionResult = thisPtr.workflowInstance.EndResumeProtocolBookmark(result);
668                 if (resumptionResult != BookmarkResumptionResult.Success)
669                 {
670                     // Raise UnkownMessageReceivedEvent when we fail to resume bookmark
671                     thisPtr.OperationContext.Host.RaiseUnknownMessageReceived(thisPtr.OperationContext.IncomingMessage);
672 
673                     // Only delay-retry this operation once (and only if retries are supported). Future calls will ensure the bookmark is set.
674                     if (thisPtr.workflowInstance.BufferedReceiveManager != null)
675                     {
676                         bool bufferSuccess = thisPtr.workflowInstance.BufferedReceiveManager.BufferReceive(
677                             thisPtr.OperationContext, thisPtr.receiveContext, thisPtr.bookmark.Name, BufferedReceiveState.WaitingOnBookmark, false);
678                         if (bufferSuccess)
679                         {
680                             if (TD.BufferOutOfOrderMessageNoBookmarkIsEnabled())
681                             {
682                                 TD.BufferOutOfOrderMessageNoBookmark(thisPtr.eventTraceActivity, thisPtr.workflowInstance.Id.ToString(), thisPtr.bookmark.Name);
683                             }
684 
685                             shouldAbandon = false;
686                         }
687                     }
688 
689                     // The throw exception is intentional whether or not BufferedReceiveManager is set.
690                     // This is to allow exception to bubble up the stack to WCF to cleanup various state (like Transaction).
691                     // This is queue scenario and as far as the client is concerned, the client will not see any exception.
692                     throw FxTrace.Exception.AsError(new FaultException(OperationExecutionFault.CreateOperationNotAvailableFault(thisPtr.workflowInstance.Id, thisPtr.bookmark.Name)));
693                 }
694 
695                 lock (thisPtr.thisLock)
696                 {
697                     if (thisPtr.CurrentState == State.ResultReceived)
698                     {
699                         thisPtr.CurrentState = State.Completed;
700                         if (thisPtr.pendingException != null)
701                         {
702                             throw FxTrace.Exception.AsError(thisPtr.pendingException);
703                         }
704                         completed = true;
705                     }
706                     else
707                     {
708                         thisPtr.CurrentState = State.WaitForResult;
709                         completed = false;
710                     }
711 
712                     // we are not really completed until the ReceiveContext finishes its work
713                     if (completed)
714                     {
715                         completed = thisPtr.ProcessReceiveContext();
716                     }
717 
718                     shouldAbandon = false;
719                 }
720             }
721             finally
722             {
723                 if (shouldAbandon)
724                 {
725                     BufferedReceiveManager.AbandonReceiveContext(thisPtr.receiveContext);
726                 }
727                 thisPtr.RemovePendingOperation();
728             }
729 
730             return completed;
731         }
732 
ProcessReceiveContext()733         bool ProcessReceiveContext()
734         {
735             if (this.receiveContext != null)
736             {
737                 if (handleEndProcessReceiveContext == null)
738                 {
739                     handleEndProcessReceiveContext = new AsyncCompletion(HandleEndProcessReceiveContext);
740                 }
741 
742                 IAsyncResult nextResult = ReceiveContextAsyncResult.BeginProcessReceiveContext(this, this.receiveContext, PrepareAsyncCompletion(handleEndProcessReceiveContext), this);
743                 return SyncContinue(nextResult);
744             }
745 
746             return true;
747         }
748 
HandleEndProcessReceiveContext(IAsyncResult result)749         static bool HandleEndProcessReceiveContext(IAsyncResult result)
750         {
751             ReceiveContextAsyncResult.EndProcessReceiveContext(result);
752             return true;
753         }
754 
Finally(AsyncResult result, Exception completionException)755         static void Finally(AsyncResult result, Exception completionException)
756         {
757             WorkflowOperationContext thisPtr = (WorkflowOperationContext)result;
758             thisPtr.EmitTransferFromInstanceId();
759 
760             if (completionException != null)
761             {
762                 if (completionException is FaultException)
763                 {
764                     thisPtr.TrackMethodFaulted();
765                 }
766                 else
767                 {
768                     thisPtr.TrackMethodFailed();
769                 }
770             }
771             else
772             {
773                 thisPtr.TrackMethodCompleted(thisPtr.operationReturnValue);
774             }
775             thisPtr.ProcessFinalizationTraces();
776             // will be a no-op if we were never added to the instance
777             thisPtr.workflowInstance.ReleaseContext(thisPtr);
778             thisPtr.RemovePendingOperation();
779         }
780 
781         enum State
782         {
783             BookmarkResumption,
784             WaitForResult,
785             ResultReceived,
786             Completed
787         }
788 
789         class OperationContextScopeHelper : IDisposable
790         {
791             OperationContext currentOperationContext;
792 
OperationContextScopeHelper(OperationContext operationContext)793             public OperationContextScopeHelper(OperationContext operationContext)
794             {
795                 this.currentOperationContext = OperationContext.Current;
796                 OperationContext.Current = operationContext;
797             }
798 
IDisposable.Dispose()799             void IDisposable.Dispose()
800             {
801                 OperationContext.Current = this.currentOperationContext;
802             }
803         }
804 
805         class ReceiveContextAsyncResult : TransactedAsyncResult
806         {
807             static AsyncCompletion handleEndComplete = new AsyncCompletion(HandleEndComplete);
808 
809             WorkflowOperationContext context;
810             ReceiveContext receiveContext;
811 
ReceiveContextAsyncResult(WorkflowOperationContext context, ReceiveContext receiveContext, AsyncCallback callback, object state)812             ReceiveContextAsyncResult(WorkflowOperationContext context, ReceiveContext receiveContext, AsyncCallback callback, object state)
813                 : base(callback, state)
814             {
815                 this.context = context;
816                 this.receiveContext = receiveContext;
817 
818                 if (ProcessReceiveContext())
819                 {
820                     base.Complete(true);
821                 }
822             }
823 
BeginProcessReceiveContext(WorkflowOperationContext context, ReceiveContext receiveContext, AsyncCallback callback, object state)824             public static IAsyncResult BeginProcessReceiveContext(WorkflowOperationContext context, ReceiveContext receiveContext, AsyncCallback callback, object state)
825             {
826                 return new ReceiveContextAsyncResult(context, receiveContext, callback, state);
827             }
828 
EndProcessReceiveContext(IAsyncResult result)829             public static void EndProcessReceiveContext(IAsyncResult result)
830             {
831                 ReceiveContextAsyncResult.End(result);
832             }
833 
End(IAsyncResult result)834             public static void End(IAsyncResult result)
835             {
836                 AsyncResult.End<ReceiveContextAsyncResult>(result);
837             }
838 
ProcessReceiveContext()839             bool ProcessReceiveContext()
840             {
841                 IAsyncResult result;
842 
843                 using (PrepareTransactionalCall(this.context.CurrentTransaction))
844                 {
845                     if (this.context.CurrentTransaction != null)
846                     {
847                         // make sure we Abandon if the transaction ends up with an outcome of Aborted
848                         this.context.CurrentTransaction.TransactionCompleted += new TransactionCompletedEventHandler(OnTransactionComplete);
849                     }
850                     result = this.receiveContext.BeginComplete(
851                         this.context.timeoutHelper.RemainingTime(), PrepareAsyncCompletion(handleEndComplete), this);
852                 }
853 
854                 return SyncContinue(result);
855             }
856 
857             // This happens out-of-band of ReceiveAsyncResult.
858             // When transaction was aborted, we best-effort abandon the context.
OnTransactionComplete(object sender, TransactionEventArgs e)859             void OnTransactionComplete(object sender, TransactionEventArgs e)
860             {
861                 if (e.Transaction.TransactionInformation.Status != TransactionStatus.Committed)
862                 {
863                     BufferedReceiveManager.AbandonReceiveContext(this.context.receiveContext);
864                 }
865             }
866 
HandleEndComplete(IAsyncResult result)867             static bool HandleEndComplete(IAsyncResult result)
868             {
869                 ReceiveContextAsyncResult thisPtr = (ReceiveContextAsyncResult)result.AsyncState;
870                 thisPtr.receiveContext.EndComplete(result);
871                 return true;
872             }
873         }
874     }
875 }
876