runtime: CoreCLR's CNTRL_SHUTDOWN_EVENT handler prevents graceful exit of services during system shutdown
Description
I am not sure if this belongs here, but this is a new weird behaviour I have been fighting with lately.
I have a service, where the ‘Main_Worker’ is a ‘IHostedService’ and a ‘BackgroundService’. I have overriden the ‘StopAsync’ function and the only thing it really does is write to the Windows Event Log a message and save a txt file with some data.
While I was working with this structure in .NET 6, it all worked preatty well. Then I upgraded to .Net 7, and it seems that now ‘StopAsync’ is not triggering when I turn of the PC by a normal Power -> Shutdown or if I use ‘Process.Start(“cmd”, @“/c shutdown -s -t 0”);’
When in .NET 6, both ways it worked without problems.
Reproduction Steps
public async override Task<Task> StopAsync(CancellationToken cancellationToken) { Log.Text("Stopping Worker_Main service..."); logger.LogWarning("Test Service is Stopping..."); await Log.SaveCurrentLog(); return base.StopAsync(cancellationToken); }
That is my StopAsync function, it used to work like this with no problems. But now, even if you take away the await line, not even the logger is appearing.
Expected behavior
At least I would expect to see the logged message in the Windows Event Logger when the PC is turned on again.
Actual behavior
No trace of the message being logged or the file being written. The service probably closes, yet I have no idea if it is in a forced mode or if it is actually being closed in a gracefull way.
Regression?
No response
Known Workarounds
I do not have workarounds at the moment.
Configuration
Have tested in Windows 10 & 11 Both are x64
Other information
No response
About this issue
- Original URL
- State: open
- Created a year ago
- Comments: 25 (18 by maintainers)
This one is actually different than the error 1067 issue. In that case the service process was exiting before completing ServiceBase’s OnStop method which is what notified SCM that the service cleanly stopped. In that case it would run all ApplicationStopping and ApplicationStopped.
In this case the machine is shutting down. I was looking at a similar issue internally and found that the problem was actually due to https://github.com/dotnet/runtime/pull/41101. During a shutdown a service will first receive a CNTRL_SHUTDOWN_EVENT. The default is to do nothing for a service process per https://learn.microsoft.com/en-us/windows/console/handlerroutine#remarks, but that was changed for .NET processes in https://github.com/dotnet/runtime/pull/41101.
I was able to workaround this by adding ConsoleControlHandler that cancels other handlers when seeing CNTRL_SHUTDOWN_EVENT - stopping the runtime’s handler from shutting down the process and letting the service exit normally. In .NET 6.0 you can do this with a one-liner:
Just make sure that registration stays alive for the entire duration of the service - I put it in the Main() routine.
We should look into adding something like this to ServiceBase – or having the runtime try to avoid handling CNTRL_SHUTDOWN_EVENT for services.
believe there is a workaround in place to help with this issue? Will mark it for 9 to determine the feasibility of the fix.
This issue did help us discover the shutdown problem with CoreCLR’s CNTRL_SHUTDOWN_EVENT which I mentioned here: https://github.com/dotnet/runtime/issues/83093#issuecomment-1470814801.
We’ve seen other reports of this same problem, so I’d like to keep this issue open to track a fix for that in CoreCLR. @janvorli was looking into a fix for that.
Right, so I think that things have gone really clearer for me with this.
Part of the problem is that I was trying to use the backgroundservice in a way its not meant to be used.
So:
I have modified how I do the logging, and seems to be working fine now.
Thanks a lot @ericstj for your help through this.
If we all are ok, I am good to close this issue.
@gkapellmann I took a look at your repro. I was able to find out what was wrong by using Time-Travel debugging and examining the behavior at shutdown.
The
PosixSignalRegistration
workaround helped in that it did prevent the runtime from disabling exception handling, but the problem was there was still an unhandled exception in the logger:This was preventing you from running your SaveCurrentLog method. I was able to workaround that by wrapping the call to _logger.LogInformation in your stopasync with a try/catch.
I’ll take a deeper look at this to see why it’s happening. Could be that this is normal during shutdown. Could be that a handle got disposed out from under the logger.
Ideally we don’t do this only in ServiceBase since that wouldn’t fix cases where folks were implementing services themselves without using ServiceBase. Instead we should try to replicate whatever the OS does in the default control handler to distinguish between normal console processes and services.
Looks similar to https://github.com/dotnet/runtime/issues/62579 and https://github.com/dotnet/runtime/issues/81274, @gkapellmann could you try the workaround mentioned and let us know if that helps your issue? The workaround is for unblocking you for short term, if you could let us know how it goes, it would be a useful data for to the team to find appropriate fix.