-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WindowsSerivceLifetime is broken #72590
Comments
Tagging subscribers to this area: @dotnet/area-system-serviceprocess Issue DetailsDescriptionThere are several bugs related to WindowsServiceLifetime implementation in 3.1:
#2 is can be a problem if the service is dependent by another windows service. Reproduction StepsHere is an example of what is roughly going on when application is started as windows service. Create a new console application dotnet new console --framework netcoreapp3.1 --output ServiceExitTest
dotnet add ServiceExitTest\ServiceExitTest.csproj package --version 3.1.3 Microsoft.Extensions.Hosting.WindowsServices Replace Program.cs: namespace ServiceExitTest
{
using System;
using System.IO;
using System.ServiceProcess;
using System.Threading;
public class Program
{
private readonly static object logLock = new object();
public static void Log(string name, string message)
{
lock (logLock)
{
message = $"{DateTime.Now} [{name}] {message}";
File.AppendAllText("service_log.txt", message + Environment.NewLine);
}
}
public static void Log(string message) => Log(nameof(Program), message);
class StrangeService : ServiceBase
{
private static void Log(string message) => Program.Log(nameof(StrangeService), message);
private readonly ManualResetEvent mre = new ManualResetEvent(false);
public void StartService()
{
Log("starting service");
new Thread(new ThreadStart(Run)).Start();
Log("waiting for service to start");
mre.WaitOne();
Log("service started");
}
protected override void OnStart(string[] args)
{
Log("notifying started");
mre.Set();
Log("started notification complete");
base.OnStart(args);
}
private void Run()
{
try
{
Log("calling ServiceBase.Run(this)");
Run(this);
Log("ServiceBase.Run(this) completed");
}
catch { }
finally
{
Log("notifying completed");
mre.Set();
Log("completed notified");
}
}
}
public static int Main()
{
Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);
using var service = new StrangeService();
Log("starting strange service");
service.StartService();
Log("strange service started");
Log("exiting application");
return 1;
}
}
} Create build.cmd with following content: @echo off
sc.exe stop WINSERVICE_STOP_TEST
dotnet publish --self-contained true --configuration Release --runtime win10-x64 ServiceExitTest\ServiceExitTest.csproj
sc.exe delete WINSERVICE_STOP_TEST
sc.exe create WINSERVICE_STOP_TEST binPath= "%~dp0\ServiceExitTest\bin\Release\netcoreapp3.1\win10-x64\publish\ServiceExitTest.exe"
sc.exe start WINSERVICE_STOP_TEST Execute build.cmd and check the current state:
So even though Main method has exited service and executable remain running. This happens because StrangeService.StartService starts new foreground thread which never completes (ServiceBase.Run does not return) because StrangeService.Stop is not called. If you'll squint your eyes you can see that StrangeService is a WindowsServiceLifetime. The same behavior is observed in "real project" when error happens during IHostedService.StartAsync. There are couple ways to solve/workaround it:
About #3 - unlike SystemdLifetime, WindowsServiceLifetime reports service is up and running inside WaitForStartAsync before IHostApplicationLifetime.ApplicationStarted is raised which is completely wrong because service is not running. This can cause problems when the service is dependent by other services. Expected behavior
Actual behavior
Regression?No response Known WorkaroundsNo response ConfigurationNo response Other informationNo response
|
I would also like to note that having separate thread just to call ServiceBase.Run is a clear indication that "running an application" was an afterthought when making dotnet core host design decisions (old windows service api is not compatible with new Host/Lifetime api). For full framework I've been using following approach that does not require separate thread since forever (aka .net 1.1): // error handling logic omitted
interface IApp // host+hostlifetime
{
int Run(string[] args);
}
interface IDaemon // hosted service
{
void Start();
void Stop();
}
class ConsoleApp : IApp
{
private readonly IDaemon daemon:
public ConsoleApp(IDaemon daemon)
{
this.daemon = daemon;
}
public int Run(string[] args)
{
daemon.Start();
Console.ReadLine();// or wait for Ctrl+C
daemon.Stop();
return 0;
}
}
class ServiceApp : ServiceBase, IApp
{
private readonly IDaemon daemon:
public ServiceApp (IDaemon daemon)
{
this.daemon = daemon;
}
public int Run(string args[])
{
Run(this);
return 0;
}
protected override void OnStart(string[] args)
{
base.OnStart(args);
daemon.Start();
}
protected override void OnShutdown()
{
daemon.Stop();
base.OnShutdown();
}
protected override void OnStop()
{
daemon.Stop();
base.OnStop();
}
}
public static int Main(string[] args)
{
var daemon = CreateDaemon(args);
var app = IsWindowsService ? new ServiceApp(daemon) : new ConsoleApp(daemon);
return app.Run(args);
} |
Tagging subscribers to this area: @dotnet/area-extensions-hosting Issue DetailsDescriptionThere are several bugs related to WindowsServiceLifetime implementation in 3.1:
#2 is can be a problem if the service is dependent by another windows service. Reproduction StepsHere is an example of what is roughly going on when application is started as windows service. Create a new console application dotnet new console --framework netcoreapp3.1 --output ServiceExitTest
dotnet add ServiceExitTest\ServiceExitTest.csproj package --version 3.1.3 Microsoft.Extensions.Hosting.WindowsServices Replace Program.cs: namespace ServiceExitTest
{
using System;
using System.IO;
using System.ServiceProcess;
using System.Threading;
public class Program
{
private readonly static object logLock = new object();
public static void Log(string name, string message)
{
lock (logLock)
{
message = $"{DateTime.Now} [{name}] {message}";
File.AppendAllText("service_log.txt", message + Environment.NewLine);
}
}
public static void Log(string message) => Log(nameof(Program), message);
class StrangeService : ServiceBase
{
private static void Log(string message) => Program.Log(nameof(StrangeService), message);
private readonly ManualResetEvent mre = new ManualResetEvent(false);
public void StartService()
{
Log("starting service");
new Thread(new ThreadStart(Run)).Start();
Log("waiting for service to start");
mre.WaitOne();
Log("service started");
}
protected override void OnStart(string[] args)
{
Log("notifying started");
mre.Set();
Log("started notification complete");
base.OnStart(args);
}
private void Run()
{
try
{
Log("calling ServiceBase.Run(this)");
Run(this);
Log("ServiceBase.Run(this) completed");
}
catch { }
finally
{
Log("notifying completed");
mre.Set();
Log("completed notified");
}
}
}
public static int Main()
{
Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);
using var service = new StrangeService();
Log("starting strange service");
service.StartService();
Log("strange service started");
Log("exiting application");
return 1;
}
}
} Create build.cmd with following content: @echo off
sc.exe stop WINSERVICE_STOP_TEST
dotnet publish --self-contained true --configuration Release --runtime win10-x64 ServiceExitTest\ServiceExitTest.csproj
sc.exe delete WINSERVICE_STOP_TEST
sc.exe create WINSERVICE_STOP_TEST binPath= "%~dp0\ServiceExitTest\bin\Release\netcoreapp3.1\win10-x64\publish\ServiceExitTest.exe"
sc.exe start WINSERVICE_STOP_TEST Execute build.cmd and check the current state:
So even though Main method has exited service and executable remain running. This happens because StrangeService.StartService starts new foreground thread which never completes (ServiceBase.Run does not return) because StrangeService.Stop is not called. If you'll squint your eyes you can see that StrangeService is a WindowsServiceLifetime. The same behavior is observed in "real project" when error happens during IHostedService.StartAsync. There are couple ways to solve/workaround it:
About #3 - unlike SystemdLifetime, WindowsServiceLifetime reports service is up and running inside WaitForStartAsync before IHostApplicationLifetime.ApplicationStarted is raised which is completely wrong because service is not running. This can cause problems when the service is dependent by other services. Expected behavior
Actual behavior
Regression?No response Known WorkaroundsNo response ConfigurationNo response Other informationNo response
|
Changed the area path - I believe this issue is reporting a problem with |
@i-sinister - can you describe the issue(s) in terms of using Microsoft.Extensions.Hosting.WindowsServices, and not how WindowsServices is implemented? For example, can you give a repro project that uses Microsoft.Extensions.Hosting.WindowsServices and what the expected and actual behavior in the project is. See https://github.com/dotnet/runtime/blob/main/CONTRIBUTING.md#writing-a-good-bug-report. |
@eerhardt, here you are: namespace ServiceExitTest
{
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
public class Program
{
private readonly static object logLock = new object();
public static void Log(string name, string message)
{
lock (logLock)
{
message = $"{DateTime.Now} [{name}] {message}";
File.AppendAllText("service_log.txt", message + Environment.NewLine);
}
}
public static void Log(string message) => Log(nameof(Program), message);
public class BadService : IHostedService
{
public Task StartAsync(CancellationToken cancellationToken)
{
Log("starting bad service");
throw new Exception("kaboom");
}
public Task StopAsync(CancellationToken cancellationToken)
{
Log("stopping bad service");
return Task.CompletedTask;
}
}
public static async Task<int> Main()
{
Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);
try
{
Log("building host");
var host = Host.CreateDefaultBuilder()
.ConfigureServices(services => services.AddHostedService<BadService>())
.UseWindowsService()
.Build();
Log("running host");
await host.RunAsync();
Log("application completed");
return 0;
}
catch (Exception ex)
{
Log($"error: {ex}");
return 1;
}
}
}
} Starting windows service generates log similar to following:
After 'error ...' log line main function exits and host and all related services are disposed. However executable remains running, as well as windows service. |
I see above it appears you are using
The latest code uses a background thread: runtime/src/libraries/Microsoft.Extensions.Hosting.WindowsServices/src/WindowsServiceLifetime.cs Lines 59 to 61 in 9a0b8f3
|
Nope, as we are using 3.1 - there are multiple services that were written even before 6.0 was a thing.
This should not be happening then. I only wonder is it the correct way to handle this scenario. However the issue with reporting windows service as started while it is actually starting and apis are not available is still present |
can you describe the issue(s) with a repro project that uses Microsoft.Extensions.Hosting.WindowsServices and what the expected and actual behavior in the project is? See https://github.com/dotnet/runtime/blob/main/CONTRIBUTING.md#writing-a-good-bug-report. |
I dont understand what is unclear in the first post but anyway here is it: Replace Program.cs with:
Starting windows service, observe logs and status:
As you can see service startup is in process but service reported as running which is not correct. If you'll use |
It looks like the issue is that the ServiceBase.Run method is kicked off from The main Host logic StartAsync:
The sequence goes:
And here is the WindowsServiceLifetime.WaitForStartAsync, which kicks off a separate thread to call runtime/src/libraries/Microsoft.Extensions.Hosting.WindowsServices/src/WindowsServiceLifetime.cs Lines 45 to 70 in e71a958
This means the "Windows Service" side thinks the service is up and running before the One potential fix for this would be to move the code that kicks off a thread to call @Tratcher - do you know why it was done this way originally? @i-sinister - one potential workaround for now would be to subscribe to |
That seems like a reasonable change but I don't know what else it impacts. It means hosted services run before ServiceBase does anything right? Systemd has a lighter weight communication model where the application just sends signals over a unix domain socket signaling (started, or stopped). |
I think I remember why it was done this way. We use OnStart to signal that the service is starting up. Basically we let the windows service tell us when it has started before we start running user code. For that to be the case we needed to Run the service. It seems like instead we'd want to either:
|
This seems to be a common theme with Hosting.WindowsServices. Which app model is "in charge"? I wrote about this in #63284 (comment) w.r.t. exiting the app. There it was basically decided that the Extensions.Hosting app model "sits on top of" ServiceBase, and Extensions.Hosting is the owner/controller of the process. What if we add a wait both ways in OnStart? We do the same sequence we do today and wait for ServiceBase.OnStart to get called before running user code. But then the new change is to block the ServiceBase.OnStart waiting for the The sequence would be:
Thoughts? |
The main complaint above seems to be that the service reports as "running" before IHostedService's are called. Run is needed to tell the service we're ready to start and wait for the service host to actually start us. Does the service report "running" before or after OnStart is called? |
Right as long as we can also propagate failures so if Started never fires, we want OnStart to throw or fail or something. |
Description
There are several bugs related to WindowsServiceLifetime implementation in 3.1:
#2 is can be a problem if the service is dependent by another windows service.
Reproduction Steps
Here is an example of what is roughly going on when application is started as windows service.
Create a new console application
Replace Program.cs:
Create build.cmd with following content:
Execute build.cmd and check the current state:
So even though Main method has exited service and executable remain running. This happens because StrangeService.StartService starts new foreground thread which never completes (ServiceBase.Run does not return) because StrangeService.Stop is not called.
If you'll squint your eyes you can see that StrangeService is a WindowsServiceLifetime. The same behavior is observed in "real project" when error happens during IHostedService.StartAsync. There are couple ways to solve/workaround it:
About #3 - unlike SystemdLifetime, WindowsServiceLifetime reports service is up and running inside WaitForStartAsync before IHostApplicationLifetime.ApplicationStarted is raised which is completely wrong because service is not running. This can cause problems when the service is dependent by other services.
Expected behavior
Actual behavior
Regression?
No response
Known Workarounds
No response
Configuration
No response
Other information
No response
The text was updated successfully, but these errors were encountered: