dotnet / runtime

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

Windows service ServiceBase access for pause/continue support #50021

Open artiomchi opened 5 years ago

artiomchi commented 5 years ago

Is your feature request related to a problem? Please describe.

Currently there's a simple way to run a netcore3 app as a windows service using the .UseWindowsService() extension. The issue is that this extension uses it's own ServiceBase class, which is the only class that receives pause and continue events, shutdown events and custom service commands.

Describe the solution you'd like

Add a second generic extension method that allows a custom class to be passed to .UseWindowsService<>(), which will be based on the existing WindowsServiceLifetime, retaining all functionality, but allowing extra customisation.

Describe alternatives you've considered

I've thought of altering the WindowsServiceLifetime class to receive a ICollection<ServiceBase> from DI, and register them alongside itself in the ServiceBase.Run() call, but that will mean the ApplicationLifetime.ApplicationStarted will be called only after the WindowsServiceLifetime service has started, not when all services have.

Also, it seems to make sense to only have one service registered, and move "service" functionality to hosted service classes instead.

Additional context

I've added an implementation to my fork, branched off release/3.0, since I wasn't sure which branch would be best to start the fix from: feature/windows-service-custom-class, with the fix being in this commit: d9bafd6

If you let me know whether I should do it from master, or any other branch, I can make a PR for this.

Tratcher commented 5 years ago

Can you give some code examples here showing the proposed usage?

Yes, we primarily work in master.

artiomchi commented 5 years ago

Sure,

The idea is to subclass the WindowsServiceLifetime, and overtide the standard ServiceBase methods, adding custom behaviour (making sure to not forget to call the base implementation on the start and stop methods).

For example:

public class CustomService : WindowsServiceLifetime
{
    public CustomService(IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory, IOptions<HostOptions> optionsAccessor)
        : base(environment, applicationLifetime, loggerFactory, optionsAccessor)
    {
        // Enable whichever events you want to be notified of
        CanPauseAndContinue = true;
        CanShutdown = true;
        CanHandleSessionChangeEvent = true;
        CanHandlePowerEvent = true;
    }

    protected override void OnStart(String[] args)
    {
        base.OnStart(args);
        // Custom start behaviour
    }

    protected override void OnPause()
    {
        base.OnPause();
        // Service continue handler
    }

    protected override void OnContinue()
    {
        base.OnContinue();
        // Service pause handler
    }

    protected override void OnShutdown()
    {
        base.OnShutdown();
        // System shutdown handler
    }

    protected override void OnSessionChange(SessionChangeDescription changeDescription)
    {
        base.OnSessionChange(changeDescription);
        // Session change handler
    }

    protected override void OnCustomCommand(Int32 command)
    {
        base.OnCustomCommand(command);
        // Custom command handler
    }
}

The service can also then be programatically paused/resumed, or sent custom commands to:

var serviceController = new ServiceController(ServiceName);
serviceController.ExecuteCommand(42);
serviceController.Pause();

I'll cherry pick the commit to the master branch then, and make a PR for a review

artiomchi commented 5 years ago

I forgot to add, to use this new class, it'd have to be registered when setting up the host:

Host.CreateDefaultBuilder(args)
    .UseWindowsService<CustomService>()
Tratcher commented 5 years ago

Couldn't you do this today by calling services.AddSingleton<IHostLifetime, CustomService>();?

artiomchi commented 5 years ago

Close, This should only be registered when running within the context of a web service. So the full solution becomes:

Host.CreateDefaultBuilder(args)
    .UseWindowsService()
    .ConfigureServices((hostContext, services) =>
    {
        if (WindowsServiceHelpers.IsWindowsService())
            services.AddSingleton<IHostLifetime, CustomService>();
    })

I just thought to suggest a more streamlined option, as well as extend the documentation, so that making a windows service that can handle power/pause events is more obvious.

If this is considered unnecessary, then I'm happy to cancel the PR and close the issue

Tratcher commented 5 years ago

Just wanted to be clear on the benefits.

@glennc what do you think?

davidfowl commented 5 years ago

This makes sense but I'm not sure it would be a derived class, couldn't it just be a type that forwards those ServiceBase events?

Host.CreateDefaultBuilder(args)
    .UseWindowsService<CustomServiceHandler>()

OR

Host.CreateDefaultBuilder(args)
    .UseWindowsService(options => options.Events = new CustomServiceHandler());
artiomchi commented 5 years ago

Hey David,

Yeah, it could be done this way too. I've been playing with several ideas in my head, and settled on my implementation for the sake of simplicity, but it does have it's issues (like not calling base.OnStart(args) method will hang the service).

Another thought I had was extending the IHostApplicationLifetime to add extra events, at least the pause/resume ones, but it's set up using CancellationTokens, so that's not really an option, since they can be triggered multiple times. Plus they'd only work with a windows service anyway.

Your suggestion re: CustomServiceHandler is a good one, but it'd most likely have to be the first implementation, since the handler might need access to other services to perform it's tasks, and should likely be injected by DI as a singleton. It could then handle standard service events for start/stop/pause/continue/shutdown/session events.

Should I then change my PR to something like this instead, adding a IWindowsServiceEventsHandler interface?

ghost commented 4 years ago

As part of the migration of components from dotnet/extensions to dotnet/runtime (https://github.com/aspnet/Announcements/issues/411) we will be bulk closing some of the older issues. If you are still interested in having this issue addressed, just comment and the issue will be automatically reactivated (even if you aren't the author). When you do that, I'll page the team to come take a look. If you've moved on or workaround the issue and no longer need this change, just ignore this and the issue will be closed in 7 days.

If you know that the issue affects a package that has moved to a different repo, please consider re-opening the issue in that repo. If you're unsure, that's OK, someone from the team can help!

mguinness commented 3 years ago

I see that PR https://github.com/dotnet/extensions/pull/2572 was closed as not adding value. Was that because there isn't analogous behavior with Unix daemons?

ghost commented 3 years ago

Paging @dotnet/extensions-migration ! This issue has been revived from staleness. Please take a look and route to the appropriate repository.

ghost commented 3 years ago

Tagging subscribers to this area: @eerhardt, @maryamariyan See info in area-owners.md if you want to be subscribed.

Issue Details
### Is your feature request related to a problem? Please describe. Currently there's a simple way to run a netcore3 app as a windows service using the `.UseWindowsService()` extension. The issue is that this extension uses it's own `ServiceBase` class, which is the only class that receives pause and continue events, shutdown events and custom service commands. ### Describe the solution you'd like Add a second generic extension method that allows a custom class to be passed to `.UseWindowsService<>()`, which will be based on the existing `WindowsServiceLifetime`, retaining all functionality, but allowing extra customisation. ### Describe alternatives you've considered I've thought of altering the `WindowsServiceLifetime` class to receive a `ICollection` from DI, and register them alongside itself in the `ServiceBase.Run()` call, but that will mean the `ApplicationLifetime.ApplicationStarted` will be called only after the `WindowsServiceLifetime` service has started, not when all services have. Also, it seems to make sense to only have one service registered, and move "service" functionality to hosted service classes instead. ### Additional context I've added an implementation to my fork, branched off `release/3.0`, since I wasn't sure which branch would be best to start the fix from: [feature/windows-service-custom-class](https://github.com/artiomchi/Extensions/tree/feature/windows-service-custom-class), with the fix being in this commit: [d9bafd6 ](https://github.com/artiomchi/Extensions/commit/d9bafd63027ba352af6839b0f0003a8e504eb519) If you let me know whether I should do it from `master`, or any other branch, I can make a PR for this.
Author: artiomchi
Assignees: -
Labels: `area-Extensions-Hosting`, `untriaged`
Milestone: -
RachelXGanon commented 2 years ago

@artiomchi - I have the same issue. I need the OnSessionChange() which not exists in worker service. Did you find a solution? I'm using .NET 5 Thank you

eerhardt commented 2 years ago

I don't think we should duplicate all the APIs of ServiceBase in Windows Service Hosting's layer. It is straightforward to implement this yourself as illustrated above. Doing it that way, you have the full power of ServiceBase without needing to add additional APIs.

See also the conversation here: https://github.com/dotnet/runtime/issues/75276#issuecomment-1258446954

@RachelXGanon - have you tried the approach outlined above? Derive a class from WindowsServiceLifetime and register it as the IHostLifetime service. That approach is also described at: https://github.com/dotnet/runtime/issues/75276#issuecomment-1242336776

RachelXGanon commented 2 years ago

@eerhardt I appreciate your quick response. Does it mean that in case that I need a service with the ServiceBase functionality, I can create custom service that derived from ServiceBase, in console app project? And I don't need the worker service template? Thank you.

eerhardt commented 2 years ago

@RachelXGanon - you can make either approach work. If you don't want/need the functionality that the "worker service" template brings you (DependencyInjection, Logging, Configuration, etc), you can just create a regular Windows Service using ServiceBase and a console app project. (or a Windows Service (.NET Framework) project that you upgrade to .NET 6/7.)

But if you want the functionality the worker service brings, you can make your own class derived from WindowsServiceLifetime (which itself derives from ServiceBase) as described above and override OnSessionChange().

A9G-Data-Droid commented 9 months ago

On the topic of what this adds; I looked at the source for WindowsServiceLifetime and it doesn't implement OnPause at all. The windows service control does have a pause button. What happens when an admin presses pause in the service control dialogue? Nothing?

KalleOlaviNiemitalo commented 9 months ago

@A9G-Data-Droid, because WindowsServiceLifetime does not set the ServiceBase.CanPauseAndContinue property, its value should remain false. The service control UI should detect that and disable the pause button.