Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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();
            }
        }
    }

Final Solution!

For Posterity - the solution I have gone with is the code which can be found here (Thanks to Myths for clarification): https://github.com/NetCoreApps/Hello/blob/master/src/SelfHost/Program.cs

Repo of the pertinent code:

public static void Main(string[] args)
    {
        var host = new WebHostBuilder()
            .UseKestrel()
            .UseContentRoot(Directory.GetCurrentDirectory())
            .UseStartup<Startup>()
            .UseUrls("http://*:8088/")
            .Build();

        host.Run();
    }
}

public class Startup
{
    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        // app.UseStaticFiles();

        app.UseServiceStack(new AppHost());

        app.Run(context =>
        {
            context.Response.Redirect("/metadata");
            return Task.FromResult(0);
        });
    }

In NuGet, I have Microsoft.NETCore.App, ServiceStack.Core and ServiceStack.Kestrel installed.

like image 960
Jay Avatar asked Nov 09 '16 11:11

Jay


People also ask

Can ASP NET application be run in Docker container?

By default, Docker runs on port 80 with ASP.NET Core, but you can override that. In the example below, the Kestrel server that will run in the container is being configured to listen on port 5000. The other environment variable is simply specifying our environment, which is development in this case.

What are Dockers in .NET Core?

Docker container is used for developing and publishing applications. It is a Linux-based open-source platform. It is a way of hiding or protecting your code which you need to run applications, ASP.NET core is one of them. It is dependent on the type of operating system you use.


1 Answers

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.

like image 117
mythz Avatar answered Sep 28 '22 04:09

mythz