dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.38k stars 4.75k forks source link

Daemon support for .NET Core #799

Closed danmoseley closed 3 years ago

danmoseley commented 7 years ago

Consider support analogous to what ServiceBase offers for Windows services. Either a library or sample. Need to understand how we could depend on Mono.Posix if we depend on it.

This is not committed for 2.1

danmoseley commented 7 years ago

Comments/feedback welcome.

danmoseley commented 6 years ago

@jnm2 @KSYMEK @iamcarbon since you thumbed this up, any of you interested in sketching out what the shape of this might be? If it's new API, a proposal following the API review process perhaps? Note we don't yet have the ability to ship something out of CoreFX that depends on Mono.Posix. So it would be interesting to consider whether we need to/should for this.

danmoseley commented 6 years ago

cc @eerhardt since he originally identified this need.

iamcarbon commented 6 years ago

Perhaps something like:

namespace System.Hosting.Services
{
    public abstract class ServiceHost 
    {
        public abstract Task StartAsync(string[] args);

        public abstract Task StopAsync(StopServiceRequest request);
    }

     public class StopServiceRequest 
     {
           public StopServiceReason Reason { get; }

           // the amount of time, if known, that the service has to gracefully stop
           public TimeSpan Timeout { get; }
      }

      public enum StopServiceReason 
      {  
            UserInitiated,
            RebootRequested,
            ShutdownRequested
      }
}
danmoseley commented 6 years ago

Thanks @iamcarbon that's a start, I'm assuming there's more needed. For example I'm looking at the Python interface and it has API for setting current directory, user/group ID, signal handling, input/output streams, ... I have not written a Unix daemon so which is why community collaboration on the interface is welcome here.

tmds commented 6 years ago

The 'regular' way a daemon starts is a process that does a (double) 'fork' and exits. The daemon itself is a child. The parent process termination signals to the init process that it is 'ready'. That is, if it exposing some service, that service can be accessed (e.g. socket will be listening when the parent terminates).

If your system uses systemd, then you can get a long way already. systemd supports the traditional forking type. It also has a simple type where it turns a command line app into a daemon and takes care of the forking, etc. There is also a notify type, which extends simple with a protocol that allows the app to signal to systemd when it is ready. I wrote a library that implements that protocol: https://github.com/tmds/Tmds.Systemd

On non-systemd type systems, it would be good to know what hurdles people are facing when trying to turn a .NET process into a daemon.

tmds commented 6 years ago

Also interesting to know: a SIGTERM handler can be added via the AssemblyLoadContext.Default.Unloading event. The Console.CancelKeyPress is not generated on SIGTERM (but on SIGINT/SIGQUIT).

eerhardt commented 6 years ago

An option for general signal handling is to use the https://www.nuget.org/packages/Mono.Posix.NETStandard/ package.

From https://github.com/dotnet/corefx/issues/3188#issuecomment-340436998

For signal handling, you have 2 options:

  1. Use Stdlib.SetSignalAction - to set which action should be performed when the signal is raised. The options are either the default (SIG_DFL), error (SIG_ERR), or ignore (SIG_IGN) handlers.
  2. Use UnixSignal to respond to a raised signal:
    var sigintRaised = new UnixSignal(Signum.SIGINT);
    while (sigintRaised.WaitOne()) {
        Console.WriteLine(“Control-C pressed!”);
    }

Note that it blocks the thread until the signal is raised. See http://docs.go-mono.com/?link=T%3aMono.Unix.UnixSignal for docs.

danmoseley commented 6 years ago

Something similar in Mono: https://github.com/mono/mono/blob/master/mcs/tools/mono-service/mono-service.cs

cc @arunjvs since he was asking for this also.

arunjvs commented 6 years ago

I remember a previous conversation that GNU/Linux daemons are not at par with ServiceBase. But that is not completely true – at least OnStart() and OnStop() can be implemented by ServiceBase.Run() and handling SIGTERM. How it is sent is itself different on Windows (SCM messages on DCOM) vs Linux (systemd service spec file with a custom kill SIGTERM command on a PID file). In fact even OnPause() and OnResume() can be potentially implemented through SIGSTOP and SIGCONT. I don’t think we need to pull all DCOM baggage from Windows such as ServiceBase.Container/Site. ServiceBase's core ideas seem fairly platform independent to me.

Mono seems to solve this problem really thoroughly and maybe we can adopt it. It also seems to have some kind of a replacement implementation of Window's SCM embedded in the runner and ServiceBase.

We however unblocked ourselves with this quick stop gap solution - no handling of state changes, exceptions, concurrency, etc. - just bare bones happy day scenario:

    //Maybe you can more fully implement ServiceBase on Linux at par
    //with Windows if you implement it as a D-Bus service rather than
    //a systemd process. But then you also have to implement SCM as a
    //systemd process since D-Bus is mostly only an IPC mechanism today.
    //Also it has GUI baggage.
    public abstract class ServiceBase : IDisposable
    {
        public ServiceBase()
        {
            //TODO: Init ServiceHandle to an apt WaitHandle
        }

        public string ServiceName { get; set; }

        //TODO: actually use this to signal state changes to the run method.
        //Infact use a higher abstraction than the raw pointer.
        protected IntPtr ServiceHandle { get; }

        //TODO: Concurrency
        protected abstract void OnStart(String[] args);

        //TODO: Concurrency
        protected abstract void OnStop();

        //TODO: OnStop() and Release Handle?
        protected abstract void Dispose(bool disposing);

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public static void Run(ServiceBase service)
        {
            //These are different from the process command line args
            //that are the implementers responsibility to handle in main().
            //This is from the registry.
            //Doesn't seem to be used a lot other than dev-test scenarios, not
            //does there a seem a need to support on Linux either.
            service.OnStart(new string[0]);

            ManualResetEventSlim unloadEndEvent = new ManualResetEventSlim();
            ManualResetEventSlim unloadStartEvent = new ManualResetEventSlim();

            AssemblyLoadContext.Default.Unloading += ctx =>
            {
                unloadStartEvent.Set();

                unloadEndEvent.Wait();

                //Note that service exit code could be different from process exit code.
                //But could they be same on Unix, if you restrict to single ServiceBase per
                //process (You also want to do it since unlike COM messages, signals like
                //SIGTERM,SIGSTOP,SIGINT come to the whole process anyway). 
            };

            //TODO: Simultaneously wait on the service stopping/changing state due to other reasons.
            unloadStartEvent.Wait();

            service.OnStop();

            unloadEndEvent.Set();
        }
    }
expcat commented 5 years ago

Any news about this feature?

ishvedov commented 5 years ago

@expcat, what kind of problem do you have? About 2 year I use systemd simple script with Asp.Net Core with no problems.

tmds commented 5 years ago

.NET Core 3.0 generic Host has some improved support for building systemd services: https://devblogs.microsoft.com/dotnet/net-core-and-systemd/.

Maybe we can close this issue, until some specific needs come up.

scalablecory commented 4 years ago

ASP.NET Core has the IHostedService feature which partially satisfies this. CC @Pilchie

Pilchie commented 4 years ago

@scalablecory I'm not actually sure what beyond the systemd support for services this would track. @danmosemsft - worth keeping, or close based on systemd support?

KalleOlaviNiemitalo commented 4 years ago

In fact even OnPause() and OnResume() can be potentially implemented through SIGSTOP and SIGCONT.

SIGSTOP cannot be caught, so OnPause() would not be called. SIGTSTP is the more polite request, but I don't know whether systemd even has a pause concept. https://www.freedesktop.org/software/systemd/man/systemd.kill.html

ericstj commented 3 years ago

Closing per support in Microsoft.Extensions.Hosting.Systemd