Closed danmoseley closed 3 years ago
Comments/feedback welcome.
@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.
cc @eerhardt since he originally identified this need.
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
}
}
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.
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.
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).
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:
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.
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.
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();
}
}
Any news about this feature?
@expcat, what kind of problem do you have? About 2 year I use systemd simple script with Asp.Net Core with no problems.
.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.
ASP.NET Core has the IHostedService
feature which partially satisfies this. CC @Pilchie
@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?
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
Closing per support in Microsoft.Extensions.Hosting.Systemd
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