question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. ItĀ collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

How to cancel starting up a web application that is doing some initial startup logic (like seeding a DB)?

See original GitHub issue

šŸ‘‹ Hi!

I’m trying to figure out how I can stop starting up my web application when I’m doing some initial application-startup logic, like seeding a database or loading some data from some URI/DB/etc into memory, etc.

The database seeding is an easy example, but it could be any ā€œdependencyā€ that fails. And for this example, imagine that dependency is unavailable (e.g. DB is starting up because all these services start at the ā€˜same time’ with docker-compose up).

In the public void Configure(..) method (in startup.cs) this is a great place to start DB seeding.

So what I do is:

  • Seed data.
  • if error, retry.
  • repeat for 15 times … which takes a total of about 30 seconds max.
  • else … crash/exception if 15 attempts all failed.

The ā€˜retry’ action is using Polly.

In the middle of the Polly retries I hit CTRL-C and the application says ā€œstoppingā€ … but Polly doesn’t know about this and keeps retrying.

I thought the trick was to pass the applicationLifetime.ApplicationStopping CancellationToken down to the logic which calls/retries. Doing this, the token is always false (ie. nothing to cancel … keep on trucking).

I tried asking this in the Polly repo but we’re also stuck … because we feel like this is more related to the underlying framework or more specifically, what I’m not coding correctly.

So lets look at some code and some pwitty pictures…

public void Configure(IApplicationBuilder app,
                        IHostingEnvironment env,
                        IDocumentStore documentStore,
                        IOptions<CorsSettings> corsSettings,
                        IApplicationLifetime applicationLifetime,   // <-- TAKE NOTE
                        ILogger<Startup> logger)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseProblemDetails()
        .UseCustomCors(corsSettings.Value)

        // OK -> here we go. lets 'UseRavenDb' which will seed some data.
        .UseRavenDb(env.IsDevelopment(), 
                    documentStore, 
                    applicationLifetime.ApplicationStopping,
                    logger)

        .UseAuthentication()
        .UseCustomSwagger("accounts", SwaggerTitle)
        .UseMvc();
}

So first up, lets seed some data to the DB (in this case RavenDB) and pass along the ApplicationStopping token from an IApplicationLifetime which was injected in.

this eventually calls this check to see if the database exists, before i throw data into it …

DatabaseStatistics existingDatabaseStatistics = null;
try
{
    var checkRavenDbPolicy = CheckRavenDbPolicy(cancellationToken, logger);
    checkRavenDbPolicy.Execute(token =>
    {
        existingDatabaseStatistics = documentStore.Maintenance.Send(new GetStatisticsOperation());
    }, cancellationToken);
}
... <snipped the catches>
  

The policy is a retry 15x with a 2 second delay between retries. Simple. (oldish code in the image below.)

And this is what it looks like when I hit CTRL-C in the middle of the retry’s because I didn’t start the DB (on purpose) :

Ok … application is shutting down…

Ok - so we’ve paused inside when an exception is thrown and the policy is handling it. Lets check out what the cancellationToken is …

and it looks like the cancellationToken is saying ā€˜nothing to handle. nothing cancelled. keep on cruising’.

So I’m not sure how to leverage the applicationLifetime.ApplicationStopping property, correctly. Or if that’s the correct property to leverage.

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:29 (27 by maintainers)

github_iconTop GitHub Comments

3reactions
pokecommented, May 10, 2019

In the public void Configure(..) method (in startup.cs) this is a great place to start DB seeding.

I disagree. The Configure method is a place to set up the application but not to establish pre-conditions about the application. It is further executed by other tools, like dotnet ef or the WebApplicationFactory, which makes it sub-optimal for preparing stuff for actually launching the application.

My usual recommendation is to seed the database outside of the Startup at the host or web host level. By doing that, you are seeding before you actually start the application which also gives you the benefit of not serving the application early although it is not yet ready.

So by following this approach, you have a lot more control about when the application is actually started and you can use that to properly seed your database first and only start the application once that is successfully completed. And since you are now outside of complex lifetimes, you can also control cancelling a lot easier.

In general, this would look something like this:

public async Task Main(string[] args)
{
    var host = CreateWebHostBuilder(args).Build();

    using (var scope = host.Services.CreateScope())
    {
        // retrieve the seed service and attempt to seed the database
        var databaseSeeder = scope.ServiceProvider.GetRequiredService<IDatabaseSeeder>();

        if (!await databaseSeeder.TrySeedDatabase())
        {
            Console.WriteLine("Seeding was not successful");
            return;
        }
    }

    // run the application
    await host.RunAsync();
}

And since this runs now as a normal console application without anything magic, you can also simply check for the Console.CancelKeyPress to check for Ctrl+K and pass that on to a cancellation token for example that then interrupts your seed process.

2reactions
PureKromecommented, May 29, 2019

Thank you to everyone participating in this issue and discussion. Lots of respect to you all šŸ°

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to cancel starting up a web application that is doing ...
My usual recommendation is to seed the database outside of the Startup at the host or web host level. By doing that, you...
Read more >
Where to put code to run after startup is completed
Probably the best place is in Configure method after UseMvc() call. That is also the place where you usually apply migrations.
Read more >
Data Seeding - EF Core
Data seeding is the process of populating a database with an initial set of data. There are several ways this can be accomplished...
Read more >
Migrations and Seed Data With Entity Framework Core
We are going to learn about Migrations and Seed data in Entity Framework Core and how to optimize EF Core migrations.
Read more >
Automatic Data Seeding on Go API Server
It looks like our seeding function is done! Now all is left is to set up our database connection and run the seeding...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found