//----------------------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- namespace System.ServiceModel.Dispatcher { using System; using System.Collections.ObjectModel; using System.Diagnostics; using System.Runtime; using System.Runtime.CompilerServices; using System.Runtime.Diagnostics; using System.Security; using System.ServiceModel; using System.ServiceModel.Activation; using System.ServiceModel.Channels; using System.ServiceModel.Diagnostics; using System.Threading; using System.Xml; using System.Transactions; using System.ServiceModel.Diagnostics.Application; delegate void MessageRpcProcessor(ref MessageRpc rpc); struct MessageRpc { internal readonly ServiceChannel Channel; internal readonly ChannelHandler channelHandler; internal readonly object[] Correlation; internal readonly ServiceHostBase Host; internal readonly OperationContext OperationContext; internal ServiceModelActivity Activity; internal Guid ResponseActivityId; internal IAsyncResult AsyncResult; internal bool CanSendReply; internal bool SuccessfullySendReply; internal CorrelationCallbackMessageProperty CorrelationCallback; internal object[] InputParameters; internal object[] OutputParameters; internal object ReturnParameter; internal bool ParametersDisposed; internal bool DidDeserializeRequestBody; internal TransactionMessageProperty TransactionMessageProperty; internal TransactedBatchContext TransactedBatchContext; internal Exception Error; internal MessageRpcProcessor ErrorProcessor; internal ErrorHandlerFaultInfo FaultInfo; internal bool HasSecurityContext; internal object Instance; internal bool MessageRpcOwnsInstanceContextThrottle; internal MessageRpcProcessor NextProcessor; internal Collection NotUnderstoodHeaders; internal DispatchOperationRuntime Operation; internal Message Request; internal RequestContext RequestContext; internal bool RequestContextThrewOnReply; internal UniqueId RequestID; internal Message Reply; internal TimeoutHelper ReplyTimeoutHelper; internal RequestReplyCorrelator.ReplyToInfo ReplyToInfo; internal MessageVersion RequestVersion; internal ServiceSecurityContext SecurityContext; internal InstanceContext InstanceContext; internal bool SuccessfullyBoundInstance; internal bool SuccessfullyIncrementedActivity; internal bool SuccessfullyLockedInstance; internal ReceiveContextRPCFacet ReceiveContext; internal TransactionRpcFacet transaction; internal IAspNetMessageProperty HostingProperty; internal MessageRpcInvokeNotification InvokeNotification; internal EventTraceActivity EventTraceActivity; static AsyncCallback handleEndComplete = Fx.ThunkCallback(new AsyncCallback(HandleEndComplete)); static AsyncCallback handleEndAbandon = Fx.ThunkCallback(new AsyncCallback(HandleEndAbandon)); bool paused; bool switchedThreads; bool isInstanceContextSingleton; SignalGate invokeContinueGate; internal MessageRpc(RequestContext requestContext, Message request, DispatchOperationRuntime operation, ServiceChannel channel, ServiceHostBase host, ChannelHandler channelHandler, bool cleanThread, OperationContext operationContext, InstanceContext instanceContext, EventTraceActivity eventTraceActivity) { Fx.Assert((operationContext != null), "System.ServiceModel.Dispatcher.MessageRpc.MessageRpc(), operationContext == null"); Fx.Assert(channelHandler != null, "System.ServiceModel.Dispatcher.MessageRpc.MessageRpc(), channelHandler == null"); this.Activity = null; this.EventTraceActivity = eventTraceActivity; this.AsyncResult = null; this.CanSendReply = true; this.Channel = channel; this.channelHandler = channelHandler; this.Correlation = EmptyArray.Allocate(operation.Parent.CorrelationCount); this.CorrelationCallback = null; this.DidDeserializeRequestBody = false; this.TransactionMessageProperty = null; this.TransactedBatchContext = null; this.Error = null; this.ErrorProcessor = null; this.FaultInfo = new ErrorHandlerFaultInfo(request.Version.Addressing.DefaultFaultAction); this.HasSecurityContext = false; this.Host = host; this.Instance = null; this.MessageRpcOwnsInstanceContextThrottle = false; this.NextProcessor = null; this.NotUnderstoodHeaders = null; this.Operation = operation; this.OperationContext = operationContext; this.paused = false; this.ParametersDisposed = false; this.ReceiveContext = null; this.Request = request; this.RequestContext = requestContext; this.RequestContextThrewOnReply = false; this.SuccessfullySendReply = false; this.RequestVersion = request.Version; this.Reply = null; this.ReplyTimeoutHelper = new TimeoutHelper(); this.SecurityContext = null; this.InstanceContext = instanceContext; this.SuccessfullyBoundInstance = false; this.SuccessfullyIncrementedActivity = false; this.SuccessfullyLockedInstance = false; this.switchedThreads = !cleanThread; this.transaction = null; this.InputParameters = null; this.OutputParameters = null; this.ReturnParameter = null; this.isInstanceContextSingleton = InstanceContextProviderBase.IsProviderSingleton(this.Channel.DispatchRuntime.InstanceContextProvider); this.invokeContinueGate = null; if (!operation.IsOneWay && !operation.Parent.ManualAddressing) { this.RequestID = request.Headers.MessageId; this.ReplyToInfo = new RequestReplyCorrelator.ReplyToInfo(request); } else { this.RequestID = null; this.ReplyToInfo = new RequestReplyCorrelator.ReplyToInfo(); } this.HostingProperty = AspNetEnvironment.Current.GetHostingProperty(request, true); if (DiagnosticUtility.ShouldUseActivity) { this.Activity = TraceUtility.ExtractActivity(this.Request); } if (DiagnosticUtility.ShouldUseActivity || TraceUtility.ShouldPropagateActivity) { this.ResponseActivityId = ActivityIdHeader.ExtractActivityId(this.Request); } else { this.ResponseActivityId = Guid.Empty; } this.InvokeNotification = new MessageRpcInvokeNotification(this.Activity, this.channelHandler); if (this.EventTraceActivity == null && FxTrace.Trace.IsEnd2EndActivityTracingEnabled) { if (this.Request != null) { this.EventTraceActivity = EventTraceActivityHelper.TryExtractActivity(this.Request, true); } } } internal bool FinalizeCorrelationImplicitly { get { return this.CorrelationCallback != null && this.CorrelationCallback.IsFullyDefined; } } internal bool IsPaused { get { return this.paused; } } internal bool SwitchedThreads { get { return this.switchedThreads; } } internal bool IsInstanceContextSingleton { set { this.isInstanceContextSingleton = value; } } internal TransactionRpcFacet Transaction { get { if (this.transaction == null) { this.transaction = new TransactionRpcFacet(ref this); } return this.transaction; } } internal void Abort() { this.AbortRequestContext(); this.AbortChannel(); this.AbortInstanceContext(); } void AbortRequestContext(RequestContext requestContext) { try { requestContext.Abort(); ReceiveContextRPCFacet receiveContext = this.ReceiveContext; if (receiveContext != null) { this.ReceiveContext = null; IAsyncResult result = receiveContext.BeginAbandon( TimeSpan.MaxValue, handleEndAbandon, new CallbackState { ReceiveContext = receiveContext, ChannelHandler = this.channelHandler }); if (result.CompletedSynchronously) { receiveContext.EndAbandon(result); } } } catch (Exception e) { if (Fx.IsFatal(e)) { throw; } this.channelHandler.HandleError(e); } } internal void AbortRequestContext() { if (this.OperationContext.RequestContext != null) { this.AbortRequestContext(this.OperationContext.RequestContext); } if ((this.RequestContext != null) && (this.RequestContext != this.OperationContext.RequestContext)) { this.AbortRequestContext(this.RequestContext); } TraceCallDurationInDispatcherIfNecessary(false); } void TraceCallDurationInDispatcherIfNecessary(bool requestContextWasClosedSuccessfully) { // only need to trace once (either for the failure or success case) if (TD.DispatchFailedIsEnabled()) { if (requestContextWasClosedSuccessfully) { TD.DispatchSuccessful(this.EventTraceActivity, this.Operation.Name); } else { TD.DispatchFailed(this.EventTraceActivity, this.Operation.Name); } } } internal void CloseRequestContext() { if (this.OperationContext.RequestContext != null) { this.DisposeRequestContext(this.OperationContext.RequestContext); } if ((this.RequestContext != null) && (this.RequestContext != this.OperationContext.RequestContext)) { this.DisposeRequestContext(this.RequestContext); } TraceCallDurationInDispatcherIfNecessary(true); } void DisposeRequestContext(RequestContext context) { try { context.Close(); ReceiveContextRPCFacet receiveContext = this.ReceiveContext; if (receiveContext != null) { this.ReceiveContext = null; IAsyncResult result = receiveContext.BeginComplete( TimeSpan.MaxValue, null, this.channelHandler, handleEndComplete, new CallbackState { ChannelHandler = this.channelHandler, ReceiveContext = receiveContext }); if (result.CompletedSynchronously) { receiveContext.EndComplete(result); } } } catch (Exception e) { if (Fx.IsFatal(e)) { throw; } this.AbortRequestContext(context); this.channelHandler.HandleError(e); } } static void HandleEndAbandon(IAsyncResult result) { if (result.CompletedSynchronously) { return; } CallbackState callbackState = (CallbackState)result.AsyncState; try { callbackState.ReceiveContext.EndAbandon(result); } catch (Exception e) { if (Fx.IsFatal(e)) { throw; } callbackState.ChannelHandler.HandleError(e); } } static void HandleEndComplete(IAsyncResult result) { if (result.CompletedSynchronously) { return; } CallbackState callbackState = (CallbackState)result.AsyncState; try { callbackState.ReceiveContext.EndComplete(result); } catch (Exception e) { if (Fx.IsFatal(e)) { throw; } callbackState.ChannelHandler.HandleError(e); } } internal void AbortChannel() { if ((this.Channel != null) && this.Channel.HasSession) { try { this.Channel.Abort(); } catch (Exception e) { if (Fx.IsFatal(e)) { throw; } this.channelHandler.HandleError(e); } } } internal void CloseChannel() { if ((this.Channel != null) && this.Channel.HasSession) { try { this.Channel.Close(ChannelHandler.CloseAfterFaultTimeout); } catch (Exception e) { if (Fx.IsFatal(e)) { throw; } this.channelHandler.HandleError(e); } } } internal void AbortInstanceContext() { if (this.InstanceContext != null && !this.isInstanceContextSingleton) { try { this.InstanceContext.Abort(); } catch (Exception e) { if (Fx.IsFatal(e)) { throw; } this.channelHandler.HandleError(e); } } } internal void EnsureReceive() { using (ServiceModelActivity.BoundOperation(this.Activity)) { ChannelHandler.Register(this.channelHandler); } } bool ProcessError(Exception e) { MessageRpcProcessor handler = this.ErrorProcessor; try { Type exceptionType = e.GetType(); if (exceptionType.IsAssignableFrom(typeof(FaultException))) { DiagnosticUtility.TraceHandledException(e, TraceEventType.Information); } else { DiagnosticUtility.TraceHandledException(e, TraceEventType.Error); } if (TraceUtility.MessageFlowTracingOnly) { TraceUtility.SetActivityId(this.Request.Properties); if (Guid.Empty == DiagnosticTraceBase.ActivityId) { Guid receivedActivityId = TraceUtility.ExtractActivityId(this.Request); if (Guid.Empty != receivedActivityId) { DiagnosticTraceBase.ActivityId = receivedActivityId; } } } this.Error = e; if (this.ErrorProcessor != null) { this.ErrorProcessor(ref this); } return (this.Error == null); } #pragma warning suppress 56500 // covered by FxCOP catch (Exception e2) { if (Fx.IsFatal(e2)) { throw; } return ((handler != this.ErrorProcessor) && this.ProcessError(e2)); } } internal void DisposeParameters(bool excludeInput) { if (this.Operation.DisposeParameters) { this.DisposeParametersCore(excludeInput); } } internal void DisposeParametersCore(bool excludeInput) { if (!this.ParametersDisposed) { if (!excludeInput) { this.DisposeParameterList(this.InputParameters); } this.DisposeParameterList(this.OutputParameters); IDisposable disposableParameter = this.ReturnParameter as IDisposable; if (disposableParameter != null) { try { disposableParameter.Dispose(); } catch (Exception e) { if (Fx.IsFatal(e)) { throw; } this.channelHandler.HandleError(e); } } this.ParametersDisposed = true; } } void DisposeParameterList(object[] parameters) { IDisposable disposableParameter = null; if (parameters != null) { foreach (Object obj in parameters) { disposableParameter = obj as IDisposable; if (disposableParameter != null) { try { disposableParameter.Dispose(); } catch (Exception e) { if (Fx.IsFatal(e)) { throw; } this.channelHandler.HandleError(e); } } } } } // See notes on UnPause and Resume (mutually exclusive) // Pausing will Increment the BusyCount for the hosting environment internal IResumeMessageRpc Pause() { Wrapper wrapper = new Wrapper(ref this); this.paused = true; return wrapper; } [Fx.Tag.SecurityNote(Critical = "Calls SecurityCritical method ApplyHostingIntegrationContextNoInline. Caller must ensure that" + "function is called appropriately and result is guarded and Dispose()'d correctly.")] [SecurityCritical] IDisposable ApplyHostingIntegrationContext() { if (this.HostingProperty != null) { return this.ApplyHostingIntegrationContextNoInline(); } else { return null; } } [Fx.Tag.SecurityNote(Critical = "Calls SecurityCritical method HostingMessageProperty.ApplyIntegrationContext. Caller must ensure that" + "function is called appropriately and result is guarded and Dispose()'d correctly.")] [SecurityCritical] [MethodImpl(MethodImplOptions.NoInlining)] IDisposable ApplyHostingIntegrationContextNoInline() { return this.HostingProperty.ApplyIntegrationContext(); } [Fx.Tag.SecurityNote(Critical = "Calls SecurityCritical method ApplyHostingIntegrationContext.", Safe = "Does call properly and calls Dispose, doesn't leak control of the IDisposable out of the function.")] [SecuritySafeCritical] internal bool Process(bool isOperationContextSet) { using (ServiceModelActivity.BoundOperation(this.Activity)) { bool completed = true; if (this.NextProcessor != null) { MessageRpcProcessor processor = this.NextProcessor; this.NextProcessor = null; OperationContext originalContext; OperationContext.Holder contextHolder; if (!isOperationContextSet) { contextHolder = OperationContext.CurrentHolder; originalContext = contextHolder.Context; } else { contextHolder = null; originalContext = null; } IncrementBusyCount(); IDisposable hostedIntegrationContext = this.ApplyHostingIntegrationContext(); try { if (!isOperationContextSet) { contextHolder.Context = this.OperationContext; } processor(ref this); if (!this.paused) { this.OperationContext.SetClientReply(null, false); } } #pragma warning suppress 56500 // covered by FxCOP catch (Exception e) { if (Fx.IsFatal(e)) { throw; } if (!this.ProcessError(e) && this.FaultInfo.Fault == null) { this.Abort(); } } finally { try { DecrementBusyCount(); if (hostedIntegrationContext != null) { hostedIntegrationContext.Dispose(); } if (!isOperationContextSet) { contextHolder.Context = originalContext; } completed = !this.paused; if (completed) { this.channelHandler.DispatchDone(); this.OperationContext.ClearClientReplyNoThrow(); } } #pragma warning suppress 56500 // covered by FxCOP catch (Exception e) { if (Fx.IsFatal(e)) { throw; } throw DiagnosticUtility.ExceptionUtility.ThrowHelperFatal(e.Message, e); } } } return completed; } } // UnPause is called on the original MessageRpc to continue work on the current thread, and the copy is ignored. // Since the copy is ignored, Decrement the BusyCount internal void UnPause() { this.paused = false; DecrementBusyCount(); } internal bool UnlockInvokeContinueGate(out IAsyncResult result) { return this.invokeContinueGate.Unlock(out result); } internal void PrepareInvokeContinueGate() { this.invokeContinueGate = new SignalGate(); } void IncrementBusyCount() { // Only increment the counter on the service side. if (this.Host != null) { this.Host.IncrementBusyCount(); if (AspNetEnvironment.Current.TraceIncrementBusyCountIsEnabled()) { AspNetEnvironment.Current.TraceIncrementBusyCount(SR.GetString(SR.ServiceBusyCountTrace, this.Operation.Action)); } } } void DecrementBusyCount() { if (this.Host != null) { this.Host.DecrementBusyCount(); if (AspNetEnvironment.Current.TraceDecrementBusyCountIsEnabled()) { AspNetEnvironment.Current.TraceDecrementBusyCount(SR.GetString(SR.ServiceBusyCountTrace, this.Operation.Action)); } } } class CallbackState { public ReceiveContextRPCFacet ReceiveContext { get; set; } public ChannelHandler ChannelHandler { get; set; } } class Wrapper : IResumeMessageRpc { MessageRpc rpc; bool alreadyResumed; internal Wrapper(ref MessageRpc rpc) { this.rpc = rpc; if (rpc.NextProcessor == null) { Fx.Assert("MessageRpc.Wrapper.Wrapper: (rpc.NextProcessor != null)"); } this.rpc.IncrementBusyCount(); } public InstanceContext GetMessageInstanceContext() { return this.rpc.InstanceContext; } // Resume is called on the copy on some completing thread, whereupon work continues on that thread. // BusyCount is Decremented as the copy is now complete public void Resume(out bool alreadyResumedNoLock) { try { alreadyResumedNoLock = this.alreadyResumed; this.alreadyResumed = true; this.rpc.switchedThreads = true; if (this.rpc.Process(false) && !rpc.InvokeNotification.DidInvokerEnsurePump) { this.rpc.EnsureReceive(); } } finally { this.rpc.DecrementBusyCount(); } } public void Resume(IAsyncResult result) { this.rpc.AsyncResult = result; this.Resume(); } public void Resume(object instance) { this.rpc.Instance = instance; this.Resume(); } public void Resume() { using (ServiceModelActivity.BoundOperation(this.rpc.Activity, true)) { bool alreadyResumedNoLock; this.Resume(out alreadyResumedNoLock); if (alreadyResumedNoLock) { string text = SR.GetString(SR.SFxMultipleCallbackFromAsyncOperation, rpc.Operation.Name); Exception error = new InvalidOperationException(text); throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(error); } } } public void SignalConditionalResume(IAsyncResult result) { if (this.rpc.invokeContinueGate.Signal(result)) { this.rpc.AsyncResult = result; Resume(); } } } } class MessageRpcInvokeNotification : IInvokeReceivedNotification { ServiceModelActivity activity; ChannelHandler handler; public MessageRpcInvokeNotification(ServiceModelActivity activity, ChannelHandler handler) { this.activity = activity; this.handler = handler; } public bool DidInvokerEnsurePump { get; set; } public void NotifyInvokeReceived() { using (ServiceModelActivity.BoundOperation(this.activity)) { ChannelHandler.Register(this.handler); } this.DidInvokerEnsurePump = true; } public void NotifyInvokeReceived(RequestContext request) { using (ServiceModelActivity.BoundOperation(this.activity)) { ChannelHandler.Register(this.handler, request); } this.DidInvokerEnsurePump = true; } } }