5

I have OWIN server as part of console app. You can see the main method here:

class Program
{
    public static ManualResetEventSlim StopSwitch = new ManualResetEventSlim();

    static void Main(string[] args)
    {
        Console.CancelKeyPress += (s, a) =>
        {
            a.Cancel = true;
            StopSwitch.Set();
        };

        using (WebApp.Start<Startup>("http://+:8080/"))
        {
            Console.WriteLine("Server is running...");
            Console.WriteLine("Press CTRL+C to stop it.");
            StopSwitch.Wait();
            Console.WriteLine("Server is stopping...");
        }

        Console.ReadKey();
        Console.WriteLine("Server stopped. Press any key to close app...");
    }
}

When processing of the request is little bit longer and in the same time user presses CTRL+C to stop the application the request processing is stopped immediately and response is not sent. Is it possible to change this behavior? I would like to refuse all new requests but wait until the requests which are currently processing are done and stop server after that.

My initial idea is to create OWIN middleware which will keep track of requests which are currently processing and postpone the stop action until everything is done. Middleware would also short-circuit all requests during the stopping phase. But this solution doesn't sound good to me.

Lukas Pirkl
  • 1,417
  • 3
  • 15
  • 31
  • Sounds like quite a good idea to me. I think you need _something_ to short-circuit new requests, and putting something in the pipeline seems like the right place to do it. For tracking requests you could just use a basic [`Interlocked.Increment`](http://msdn.microsoft.com/en-us/library/system.threading.interlocked.increment(v=vs.110).aspx) / `Decrement` counter when requests start and end so you know when the last requests has finished. – Rhumborl Sep 29 '14 at 08:28

1 Answers1

4

I ended with the suggested approach with the middleware:

public class ShutDownMiddleware
{
    private readonly Func<IDictionary<string, object>, Task> next;
    private static int requestCount = 0;
    private static bool shutDownStateOn = false;

    public static void ShutDown()
    {
        shutDownStateOn = true;
    }

    public static int GetRequestCount()
    {
        return requestCount;
    }

    public ShutDownMiddleware(Func<IDictionary<string, object>, Task> next)
    {
        this.next = next;
    }

    public async Task Invoke(IDictionary<string, object> environment)
    {
        if (shutDownStateOn)
        {
            environment["owin.ResponseStatusCode"] = HttpStatusCode.ServiceUnavailable;
            return;
        }

        Interlocked.Increment(ref requestCount);
        try
        {
            await next.Invoke(environment);
        }
        finally
        {
            Interlocked.Decrement(ref requestCount);
        }
    }
}

This is registered as a first middleware in the pipeline and in the main method of program I can use it like this:

public class Program
{
    public static ManualResetEventSlim StopSwitch = new ManualResetEventSlim();

    static void Main(string[] args)
    {
        Console.CancelKeyPress += (s, a) =>
        {
            a.Cancel = true;
            StopSwitch.Set();
        };

        using (WebApp.Start<Startup>("http://+:8080/"))
        {
            Console.WriteLine("Server is running...");
            Console.WriteLine("Press CTRL+C to stop it.");
            StopSwitch.Wait();
            Console.WriteLine("Server is stopping...");
            ShutDownMiddleware.ShutDown();
            while (ShutDownMiddleware.GetRequestCount() != 0)
            {
                Thread.Sleep(TimeSpan.FromSeconds(1));
            }
        }
    }
}

I also found this: https://katanaproject.codeplex.com/workitem/281 They are talking about similar approach.

Lukas Pirkl
  • 1,417
  • 3
  • 15
  • 31
  • Your code is not completely thread-safe. Consider this scenario: requestCount is 0, ShutDown() is called but at the same time a new request arrives and still evaluates shutDownStateOn to false. ShutDown() continues, returns and the main thread evaluates GetRequestCount() to 0, so that the application stops. However, the request described above is still running. – Niklas Peter Oct 26 '16 at 06:15
  • I found another problem: When ````await next.Invoke(environment);```` you only know that this middleware and probably all succeeding middlewares are completed, but after that it will take some time to transmit the generated response over the network. Hence if you terminate the application immediately after GetRequestCount() gets 0, transmitting the response might be interrupted. – Niklas Peter Oct 26 '16 at 10:48