3

In a ServiceStack Self-Hosted service, is it possible to gracefully shutdown the service when if pending requests exist?

Use AppHost.Stop()? (derived from AppHostHttpListenerBase)

Scott
  • 21,211
  • 8
  • 65
  • 72
stefan2410
  • 1,931
  • 2
  • 16
  • 21

1 Answers1

4

I don't think there is a built in mechanism for this, though it would be nice to see it. I use my own simplistic Graceful shutdown method.

Essentially I have a static bool, IsShuttingDown flag that is checked prior to starting each request, at the first possible opportunity in the service pipeline. (RawHttpHandlers)

If this flag is set true it means I am not wanting the service to handle any more requests, and will instead send http status 503 Unavailable to the client.

My graceful shutdown method simply sets IsShuttingDown flag and starts a timeout timer of 60 seconds to give any currently processing requests time to complete. After which the service stops calling AppHost.Stop(). (See end of question for how to do it without a timer)

My code is for ServiceStack v3, you may have to modify it slightly to get it to work with v4 if you are using that version.

In your AppHost:

public static bool IsShuttingDown = false;

public override void Configure(Funq.Container container)
{

    // Other configuration options ...

    // Handle the graceful shutdown response
    var gracefulShutdownHandler = new CustomActionHandler((httpReq, httpRes) => {
        httpRes.StatusCode = 503;
        httpRes.StatusDescription = "Unavailable";
        httpRes.Write("Service Unavailable");
        httpRes.EndRequest();
    });

    SetConfig(new EndpointHostConfig {
        // Other EndPoint configuration options ...
        RawHttpHandlers = { httpReq => IsShuttingDown ? gracefulShutdownHandler : null }
    });

}

The CustomActionHandler is just copied from here, it is responsible for handling the request. (A custom action handler is included already in v4 so it wouldn't be needed)

public class CustomActionHandler : IServiceStackHttpHandler, IHttpHandler 
{
    public Action<IHttpRequest, IHttpResponse> Action { get; set; }

    public CustomActionHandler(Action<IHttpRequest, IHttpResponse> action)
    {
        if (action == null)
            throw new Exception("Action was not supplied to ActionHandler");

        Action = action;
    }

    public void ProcessRequest(IHttpRequest httpReq, IHttpResponse httpRes, string operationName)
    {            
        Action(httpReq, httpRes);
    }

    public void ProcessRequest(HttpContext context)
    {
        ProcessRequest(context.Request.ToRequest(GetType().Name), 
            context.Response.ToResponse(),
            GetType().Name);
    }

    public bool IsReusable
    {
        get { return false; }
    }
}

I appreciate that using a timer doesn't guarantee that all requests will have ended in the 60 seconds, but it works for my needs, where most requests are handled in far far less time.

To avoid using a timer (immediate shutdown when all connections closed):

Because there is no access to the underlying connection pool, you would have to keep track of what connections are active.

For this method I would use the PreExecuteServiceFilter and PostExecuteServiceFilter to increment & decrement an active connections counter. I am thinking you would want to use Interlocked.Increment and Interlocked.Decrement to ensure thread safety of your count. I haven't tested this, and there is probably a better way.

In your AppHost:

public static int ConnectionCount;

// Configure Method
// As above but with additional count tracking.

    ConnectionCount = 0;

    SetConfig(new EndpointHostConfig {
        // Other EndPoint configuration options ...
        RawHttpHandlers = { httpReq => IsShuttingDown ? gracefulShutdownHandler : null },
        
        // Track active connection count
        PreExecuteServiceFilter = () => Interlocked.Increment(ref ConnectionCount),
        PostExecuteServiceFilter = (obj, req, res) => {
            Interlocked.Decrement(ref ConnectionCount);

            // Check if shutting down, and if there are no more connections, stop
            if(IsShuttingDown && ConnectionCount==0){
                res.EndRequest(); // Ensure last request gets their data before service stops.
                this.Stop();
            }
        },
    });

Hope some of this helps anyway.

Community
  • 1
  • 1
Scott
  • 21,211
  • 8
  • 65
  • 72
  • I'm not particularly familiar with the service, but if there's any way to maintain a `Set` of your currently running `Thread`s you can ensure that they've all stopped, potentially allowing you to complete much sooner. – Mumbleskates Dec 14 '13 at 09:29
  • @Widdershins I am not sure you can get at the underlying connection thread pool. But as I mention in the last paragraph you could track the connection count yourself, and thus terminate when they have all completed. – Scott Dec 14 '13 at 09:31
  • thanks Scott, I think also, it would useful to have a built in mechanism for this. I use v3 and v4. I like the IsShuttingDown flag, but I would prefer to avoid the timer, if possible. For example, if ServiceStack had an event like the Application.Idle, when all pending requests were handled and if IsShuttingDown then to Stop. – stefan2410 Dec 14 '13 at 10:48
  • @stefan2410 I agree that it would be great to have an `Application.Idle` event, and access to the count of active connections would be useful. You should [make a suggestion here](http://servicestack.uservoice.com/forums/176786-feature-requests). The connection counter at the end of my answer would be the only work around to avoid having a timeout timer. – Scott Dec 14 '13 at 10:53
  • @Scott, with the ConnectionCount , then maybe we can in ServiceRunner.AfterEachRequest, If IsShuttingDown==true and ConnectionCount==0 (or 1, if not yet decrement), to stop the service. What do you think ? – stefan2410 Dec 14 '13 at 11:06
  • @stefan2410 Yeah I can see where you are going with that. I would do that also in `PostExecuteServiceFilter` just to keep it all together, and it is the final execution point of a request. I'll update with that suggestion. – Scott Dec 14 '13 at 11:16
  • @stefan2410 I have updated with the stop method when the connection count is 0. – Scott Dec 14 '13 at 11:20
  • 1
    @stefan2410 Awesome. I have made a small tweak where I send `res.EndRequest()` just incase closing the `AppHost` with `this.Stop()` prevents the last request from getting their result. But you can test and see if that's necessary. – Scott Dec 14 '13 at 11:29