Home > Software design >  How can I avoid Windows Service error 1053 in my .NET 5 WebApp during database migration with Entity
How can I avoid Windows Service error 1053 in my .NET 5 WebApp during database migration with Entity

Time:12-16

My .NET 5 WebApp runs as a Windows Service and uses Entity Framework to seed and update (migrate) its database during startup. Before calling Host.Run(), I make sure that my database is updated. This has worked perfectly fine for the past year. Now, I have a large database update that takes a few minutes to complete. During this database update, the Windows Service will shut down with error 1053, denoting a timeout. I suspect this is due to the runtime not reaching the call for Host.Run() in the given default timeout period (about 30 seconds as it seems). The problem is that I have to perform these database updates before calling Host.Run(), as the database should be properly updated before any accesses to it are made.

What is the simplest solution to this issue? I could try to write a custom Service Lifetime to increase the timeout. I could move the update of the database to be performed after Host.Run() with the additional overhead of restricting accesses while it is performed. I don't like either of these solutions yet and seek a better alternative. Maybe I am wrong in my assumptions altogether as well. My code is provided below.

public class Program
{
    public static async Task Main(string[] args)
    {
        IHost host = CreateHostBuilder(args).Build();
       
        using (IServiceScope scope = host.Services.CreateScope())
        {
            IServiceProvider services = scope.ServiceProvider;
            SeedAndUpdateDb seed = services.GetRequiredService<SeedAndUpdateDb>();
            await seed.InitializeAsync(); //<- This call takes a few minutes to complete
        }

        await host.RunAsync();
    }
    
    public static IHostBuilder CreateHostBuilder(string[] args)
    {
        var hostingConfig = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json")
            .Build();
            
        return Host.CreateDefaultBuilder(args)
            .UseWindowsService()
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
                webBuilder.ConfigureKestrel(serverOptions =>
                {
                    serverOptions.Configure(hostingConfig.GetSection("Kestrel"));
                });
                webBuilder.UseKestrel(options =>
                { });
            });
    }
}

CodePudding user response:

The bottom line is this: if your running executable is to be recognized as a started Windows Service, it must (after installation as a service and being started as such) upon service startup tell the Windows Service Control Manager that it is in fact a service and that it has started successfully.

If it doesn't do so in time, you get the dreaded error message that the service didn't start in time.

Your call to .UseWindowsService() sets up all that plumbing to the SCM, but the call to the SCM is only made inside the .NET Core Host startup code in await host.RunAsync().

So if between your executable starting (due to you starting the service) and your code calling await host.RunAsync() too much time elapses, your executable will be running and your hosted service will eventually run, but as far as Windows is concerned, your Windows Service did not start (in time).

The solution is to call the migration using a command-line flag, not on every startup. Or call the migration once inside your hosted service, so when the Windows Service is already running.

For the command-line approach: so after a deployment, you can run Your-Service.exe --migrate before starting the Windows Service.

Something like this:

public static async Task Main(string[] args)
{
    IHost host = CreateHostBuilder(args).Build();
   
    if (args.Any(a => a == "--migrate"))
    {
        using (IServiceScope scope = host.Services.CreateScope())
        {
            IServiceProvider services = scope.ServiceProvider;
            SeedAndUpdateDb seed = services.GetRequiredService<SeedAndUpdateDb>();
            await seed.InitializeAsync(); //<- This call takes a few minutes to complete
        }

        Console.WriteLine("Database migrated, you can now start the service");
    }
    else
    {
        await host.RunAsync();
    }
}
  • Related