Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why set the RequestInitializationTimeout property?

Tags:

What is the HttpTransportElement's RequestInitializationTimeout about? MSDN says:

Gets or sets the timespan that specifies how long the request initialization has to complete before timing out.

What exactly is "request initialization"? Why might I want to set this property?

like image 572
D.R. Avatar asked Jun 17 '15 19:06

D.R.


1 Answers

There is no clear documentation regarding RequestInitializationTimeout.

There is a comment inside the source code of SharedHttpTransportManager.IsCompatible regarding RequestInitializationTimeout checking:

// We are NOT checking the RequestInitializationTimeout here since the HttpChannelListener should be handle them
// individually. However, some of the scenarios might be impacted, e.g., if we have one endpoint with high RequestInitializationTimeout
// and the other is just normal, the first endpoint might be occupying all the receiving loops, then the requests to the normal endpoint
// will experience timeout issues. The mitigation for this issue is that customers should be able to increase the MaxPendingAccepts number.

If we check the TimeoutException that it assigns to the requestException that is passed along with the HttpRequestContext.SetMessage(message, requestException) inside the OnParseComplete, we can see:

new TimeoutException(SR.GetString(
    SR.RequestInitializationTimeoutReached,
    this.HttpRequestContext.Listener.RequestInitializationTimeout,
    "RequestInitializationTimeout",
    typeof(HttpTransportBindingElement).Name))

and the regarding SR.RequestInitializationTimeoutReached System.Private.ServiceModel exception message is:

The initialization process of the request message timed out after {0}. To increase this quota, use the '{1}' property on the '{2}'.

that will be formatted to something like this:

The initialization process of the request message timed out after 00:00:10. To increase this quota, use the 'RequestInitializationTimeout' property on the 'HttpTransportBindingElement'.

tl;dr

If we follow the source code, we can find it's usage inside EmptyHttpPipeline.

In contructor EmptyHttpPipeline.EmptyHttpPipeline

If there is a RequestInitializationTimeout TimeSpan (and not the default 00:00:00 that disables it), an IOThreadTimer is set with the dueTime set to the TimeSpan ticks, the OnRequestInitializationTimeout Action callback and this (the actual EmptyHttpPipeline object) as the callbackState.

if (this.httpRequestContext.Listener.RequestInitializationTimeout != HttpTransportDefaults.RequestInitializationTimeout)
{
    this.requestInitializationTimer = new IOThreadTimer(onRequestInitializationTimeout, this, false);
    this.requestInitializationTimer.Set(this.httpRequestContext.Listener.RequestInitializationTimeout);
}

When it's being checked?

It's checked when the EmptyHttpPipeline.OnParseComplete method is being called by trying to cancel the requestInitializationTimer. If there is a timer and it's being previously canceled or overdue, it creates a TimeoutException that it assigns to the requestException that it's passed to the HttpPipeline.HttpRequestContext.SetMessage along with the message:

protected override void OnParseComplete(Message message, Exception requestException)
{
    if (!this.CancelRequestInitializationTimer() && requestException == null)
    {
        requestException = FxTrace.Exception.AsError(new TimeoutException(SR.GetString(
                                SR.RequestInitializationTimeoutReached,
                                this.HttpRequestContext.Listener.RequestInitializationTimeout,
                                "RequestInitializationTimeout",
                                typeof(HttpTransportBindingElement).Name)));
    }

    this.HttpRequestContext.SetMessage(message, requestException);
}

The OnParseComplete method is being called inside EnqueueMessageAsyncResult.CompleteParseAndEnqueue using the HandleParseIncomingMessage AsyncCallback inside the EnqueueMessageAsyncResult.End method. The EnqueueMessageAsyncResult object is created when invoking EmptyHttpPipeline.BeginProcessInboundRequest and it terminates inside EmptyHttpPipeline.EndProcessInboundRequest.

BeginProcessInboundRequest and EndProcessInboundRequest

internal override IAsyncResult BeginProcessInboundRequest(
    ReplyChannelAcceptor replyChannelAcceptor,
    Action dequeuedCallback,
    AsyncCallback callback,
    object state)
{
    this.TraceBeginProcessInboundRequestStart();
    return new EnqueueMessageAsyncResult(replyChannelAcceptor, dequeuedCallback, this, callback, state);
}

internal override void EndProcessInboundRequest(IAsyncResult result)
{
    // It will trigger HandleParseIncomingMessage AsyncCallback,
    // that will call CompleteParseAndEnqueue
    EnqueueMessageAsyncResult.End(result);
    this.TraceProcessInboundRequestStop();
}

EnqueueMessageAsyncResult.CompleteParseAndEnqueue

void CompleteParseAndEnqueue(IAsyncResult result)
{
    using (DiagnosticUtility.ShouldUseActivity ? ServiceModelActivity.BoundOperation(this.CallbackActivity) : null)
    {
        Exception requestException;
        Message message = this.pipeline.EndParseIncomingMesssage(result, out requestException);
        if ((message == null) && (requestException == null))
        {
            throw FxTrace.Exception.AsError(
                    new ProtocolException(
                        SR.GetString(SR.MessageXmlProtocolError),
                        new XmlException(SR.GetString(SR.MessageIsEmpty))));
        }

        // Here the EmptyHttpPipeline.OnParseComplete is being invoked
        this.pipeline.OnParseComplete(message, requestException);
        this.acceptor.Enqueue(this.pipeline.HttpRequestContext, this.dequeuedCallback, true);
    }
}

How it's being checked and canceled?

It's being checked using the CancelRequestInitializationTimer method and if there is no timer set, it just returns true; if it's previously canceled it returns false, else it calls IOThreadTimer.Cancel method to check and return true if it's canceled or false if the timer is overdue.

EmptyHttpPipeline.CancelRequestInitializationTimer

protected bool CancelRequestInitializationTimer()
{
    if (this.requestInitializationTimer == null)
    {
        return true;
    }

    if (this.requestInitializationTimerCancelled)
    {
        return false;
    }

    bool result = this.requestInitializationTimer.Cancel();
    this.requestInitializationTimerCancelled = true;

    return result;
}

What is actually being canceled?

Inside the OnRequestInitializationTimeout method, we can see that it cancels the HttpPipeline which aborts the HttpPipeline.httpRequestContext:

EmptyHttpPipeline.OnRequestInitializationTimeout

static void OnRequestInitializationTimeout(object obj)
{
    Fx.Assert(obj != null, "obj should not be null.");
    HttpPipeline thisPtr = (HttpPipeline)obj;
    thisPtr.Cancel();
}

HttpPipeline.Cancel

public virtual void Cancel()
{
  this.httpRequestContext.Abort();
}

When do we get an EmptyHttpPipeline object?

We get an EmptyHttpPipeline if we set transportIntegrationHandler = null using the static HttpPipeline.CreateHttpPipeline that accepts a TransportIntegrationHandler and there is no HttpRequestContext.HttpMessagesSupported:

public static HttpPipeline CreateHttpPipeline(HttpRequestContext httpRequestContext, TransportIntegrationHandler transportIntegrationHandler, bool isWebSocketTransport)
{
    if (transportIntegrationHandler == null)
    {
        Fx.Assert(!isWebSocketTransport, "isWebSocketTransport should be false if there's no HTTP message handler existing.");

        if (httpRequestContext.HttpMessagesSupported)
        {
            return new HttpMessageSupportedHttpPipeline(httpRequestContext);
        }

        return new EmptyHttpPipeline(httpRequestContext);
    }

    return NormalHttpPipeline.CreatePipeline(httpRequestContext, transportIntegrationHandler, isWebSocketTransport);
}

The CreateHttpPipeline is called on HttpRequestContext.InitializeHttpPipeline:

HttpRequestContext.InitializeHttpPipeline

public void InitializeHttpPipeline(TransportIntegrationHandler transportIntegrationHandler)
{
    this.httpPipeline = HttpPipeline.CreateHttpPipeline(this, transportIntegrationHandler, this.IsWebSocketRequest);
}

which is called on HttpRequestContext requestContext (passed in HttpContextReceivedAsyncResult constructor) inside HttpContextReceivedAsyncResult<TListenerChannel>.ProcessHttpContextAsync using HttpChannelListener<TListenerChannel>.transportIntegrationHandler

HttpContextReceivedAsyncResult<TListenerChannel>.ProcessHttpContextAsync

AsyncCompletionResult ProcessHttpContextAsync()
{
    bool abort = false;
    try
    {
        this.context.InitializeHttpPipeline(this.listener.transportIntegrationHandler);
        ...
    }
    ...
}

Conclusion

All these leave us with the conclusion that when a HttpPipeline is created without a TransportIntegrationHandler and no HttpMessagesSupported for the HttpRequestContext, then the new EmptyHttpPipeline object will have a timeout (if RequestInitializationTimeout is set) for BeginProcessInboundRequest/EndProcessInboundRequest and set an exception when setting the message using EmptyHttpPipeline.HttpRequestContext.SetMessage inside EmptyHttpPipeline.OnParseComplete.

like image 184
Christos Lytras Avatar answered Oct 27 '22 12:10

Christos Lytras