Jay Jay - 25 days ago 20
C# Question

Control lifetime of .NET Core console application hosted in docker

Disclaimer - this is almost the same question as docker container exits immediately even with Console.ReadLine() in a .net core console application - but I don't think accepted answer on this question is satisfactory.

What I am trying to achieve

I am building a console application (it is a HTTP service using ServiceStack) which is built with .NET core (dnxcore50 - this is a console app, not an ASP.NET application). I am running this application in a docker container on a Linux machine. This I have done, and the HTTP service works.

My problem

Having said that 'my service works' - and it does, there is a problem hosting the service in a docker container. I am using

Console.ReadLine()
after starting up my HTTP listener, but this code does not block within the docker container and the container will exit immediately after starting. I can start the docker container in 'interactive' mode, and the service will sit there listening until I kill the interactive session and then the container will exit.

Code for Repo

The code below is a complete code listing for creating my test .NET core servicestack console application.

public class Program
{
public static void Main(string[] args)
{
new AppHost().Init().Start("http://*:8088/");
Console.WriteLine("listening on port 8088");
Console.ReadLine();

}
}

public class AppHost : AppSelfHostBase
{
// Initializes your AppHost Instance, with the Service Name and assembly containing the Services
public AppHost() : base("My Test Service", typeof(MyTestService).GetAssembly()) { }

// Configure your AppHost with the necessary configuration and dependencies your App needs
public override void Configure(Container container)
{

}
}

public class MyTestService: Service
{
public TestResponse Any(TestRequest request)
{
string message = string.Format("Hello {0}", request.Name);
Console.WriteLine(message);
return new TestResponse {Message = message};
}

}

[Api("Test method")]
[Route("/test/{Name}", "GET", Summary = "Get Message", Notes = "Gets a message incorporating the passed in name")]
public class TestRequest : IReturn<TestResponse>
{
[ApiMember(Name = "Name", Description = "Your Name", ParameterType = "path", DataType = "string")]
public string Name { get; set; }
}

public class TestResponse
{
[ApiMember(Name = "Message", Description = "A Message", ParameterType = "path", DataType = "string")]
public string Message { get; set; }
}


The old way of solving this problem
So having previously hosted using Mono (Mono had severe performance issues - hence the switch to .NET core) - the way to fix this behaviour was to use
Mono.Posix
listen for a kill signal like this:

using Mono.Unix;
using Mono.Unix.Native;

...

static void Main(string[] args)
{
//Start your service here...

// check if we're running on mono
if (Type.GetType("Mono.Runtime") != null)
{
// on mono, processes will usually run as daemons - this allows you to listen
// for termination signals (ctrl+c, shutdown, etc) and finalize correctly
UnixSignal.WaitAny(new[] {
new UnixSignal(Signum.SIGINT),
new UnixSignal(Signum.SIGTERM),
new UnixSignal(Signum.SIGQUIT),
new UnixSignal(Signum.SIGHUP)
});
}
else
{
Console.ReadLine();
}
}


Now - I understand that this won't work for .NET Core (obviously because Mono.Posix is for Mono!)

The solution outlined in the related article (top of this post) is no use to me - in a production environment, I can't expect to keep a docker container alive by ensuring it has an interactive session available which will keep the Console.ReadLine working because there is a STD-IN stream there...

Is there another way to keep my docker container alive (using the
-d
(detached) option when invoking
docker run
) when hosting a .NET Core application?

Code refactor as part of Mythz suggestion

public static void Main(string[] args)
{
Run(new AppHost().Init(), "http://*:8088/");
}

public static void Run(ServiceStackHost host, params string[] uris)
{
AppSelfHostBase appSelfHostBase = (AppSelfHostBase)host;

using (IWebHost webHost = appSelfHostBase.ConfigureHost(new WebHostBuilder(), uris).Build())
{
ManualResetEventSlim done = new ManualResetEventSlim(false);
using (CancellationTokenSource cts = new CancellationTokenSource())
{
Action shutdown = () =>
{
if (!cts.IsCancellationRequested)
{
Console.WriteLine("Application is shutting down...");
cts.Cancel();
}

done.Wait();
};

Console.CancelKeyPress += (sender, eventArgs) =>
{
shutdown();
// Don't terminate the process immediately, wait for the Main thread to exit gracefully.
eventArgs.Cancel = true;
};

Console.WriteLine("Application started. Press Ctrl+C to shut down.");
webHost.Run(cts.Token);
done.Set();
}
}
}

Answer

If you're going to host .NET Core apps in Docker I'd recommend just following the normal .NET Core Hosting API where it calls IWebHost.Run() to block the main thread and keep the Console Application alive.

AppHostSelfBase is just a wrapper around .NET Core's hosting API but calls the non-blocking IWebHost.Start() instead. To get the behavior of IWebHost.Run() you should be able to reuse the same approach of ManualResetEventSlim and Console.CancelKeyPress that WebHost.Run()'s implementation uses, but personally it's just easier to use .NET Core's Hosting API and call Run() and just register your ServiceStack AppHost as a .NET Core module.

Comments