5
\$\begingroup\$

I have worked out a code for graceful shutdown of a httplistener. However, I am not sure if its foolproof. Below I provide c# code and the powershell (v3.0) scripts I've used to run my tests...

C# Code:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace caHttpServer
{
    class Program
    {
        static void Main(string[] args)
        {
            HttpListener server = new HttpListener() {                
                IgnoreWriteExceptions = true //really not much useful
            };
            server.Prefixes.Add("http://*:8080/");
            server.Start();
            AutoResetEvent waiter = new AutoResetEvent(false);
            int counter = 0;
            AsyncCallback OnContextReceived = null;

            bool StopRequested = false;
            Console.WriteLine("MainThreadId: {0}", Thread.CurrentThread.ManagedThreadId);
            Action AcceptContext = new Action(() => {
                try
                {
                    server.BeginGetContext(OnContextReceived, null);
                }
                catch (Exception e)
                {
                    Console.WriteLine("{0}: Error: {0}", Thread.CurrentThread.ManagedThreadId, e.Message);
                }

            });

            OnContextReceived = new AsyncCallback(ar => {
                int id = Thread.CurrentThread.ManagedThreadId;
                if (server.IsListening)
                {
                    AcceptContext();
                }

                HttpListenerContext ctx = null;
                try
                {
                    ctx = server.EndGetContext(ar);
                }
                catch (Exception e)
                {
                    Console.WriteLine("{0}: Error: {1}", id, e.Message);
                }

                if (ctx != null)
                {

                    if (!StopRequested)
                    {
                        Interlocked.Increment(ref counter);
                        Console.WriteLine("{0} Processing: {1}", id, ctx.Request.RawUrl);
                        var response = ctx.Response.OutputStream;
                        int sleep = int.Parse(ctx.Request.QueryString["sleep"]);

                        Thread.Sleep(sleep);

                        try
                        {
                            using (var sw = new StreamWriter(response))
                            {
                                sw.WriteLine("{0} : Hello World", DateTime.Now.ToLongTimeString());
                            }
                            ctx.Response.Close();
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine("{0} : Error: {1}", id, e.Message);
                        }
                        Interlocked.Decrement(ref counter);
                    }
                    else
                    {
                        ctx.Response.Close();
                    }
                }

            });
            AcceptContext();
            Console.WriteLine("Listening for connections...");
            Console.CancelKeyPress += (s, evt) =>
            {
                evt.Cancel = true;
                StopRequested = true;
                waiter.Set();
            };
            waiter.WaitOne();
            //waiter.WaitOne(10000);
            //waiter.Reset();
            Console.WriteLine("Waiting for completion...");
            var task = Task.Factory.StartNew(() => {
                //I cannot get rid of this contraption...
                while (counter > 0)
                {
                    Console.WriteLine("Active: {0}", counter);
                    Thread.Sleep(2000);
                }
            });

            task.Wait();

            server.Close();
            Console.WriteLine("Stopped");
        }
    }
}

Powershell script:

$cmd = {
  param($sleep)
  $url = "http://localhost:8080/?sleep=$sleep"
  write-host $url
  Invoke-RestMethod $url
}

$foo = "foo"
$rnd = new-object System.Random
$jobs = @()

get-job | remove-job

0..5 | ForEach-Object {
    $sleep = $rnd.next(1, 10) * 1000;
    $jobs += Start-Job -ScriptBlock $cmd -ArgumentList $sleep;
}
write-host "waiting..."
$jobs | wait-job

$jobs | receive-job

There is no control about when to spawn the powershell backgroundjob. Or maybe there is...Which is why I commented the line (in c#) which has the timeout wait. I manually ran the program...then the script. After I saw some output in my program...I hit Ctrl + C to signal the shutdown.

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

The code looks well-structured and I think it will solve your problem. Just a few suggestions about the shutdown. For the commented "contraption", I don't think the Task adds anything here. This Sleep loop could just be on the main thread. I would also reduce the Sleep period to something more like 100ms so that the program will exit more immediately after the last handler exits. If you want to avoid that loop altogether, a CountdownEvent would allow you to replace it with a Wait on the event, but maybe that is overkill.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.