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.

QuartzHostedService: Wait for ApplicationStarted

See original GitHub issue

Hosted services and (by extension) Quartz jobs are started before the application has successfully started. For example, if the application serves HTTP requests, the jobs are started before it is ready to respond to those requests - even before the app can respond to health checks.

This is particularly annoying for smart deployments. For example, one might use Kubernetes to spin up new application instances and consider them successfully started once they answer a health check positively. Only after this happens do we include the new instances in the load balancer and do we spin down the old application instances. However, if the new instances fail to become healthy in a timely manner, they are spun down again. The issue is that such never-officially-included instances may have already started running jobs.

Solution

The problem would be mostly mitigated if the Quartz hosted service would wait for application startup to complete:

await Task.Delay(Timeout.InfiniteTimeSpan, // Wait "indefinitely", until ApplicationStarted is triggered
    this.HostApplicationLifetime.ApplicationStarted) // IHostApplicationLifetime injected in ctor
    .ContinueWith( _ => {}, TaskContinuationOptions.OnlyOnCanceled) // Without an OperationCanceledException
    .ConfigureAwait(false));

This way, at least we know that the application has managed to start successfully. For example, any unrelated hosted services that cause startup failure will now automatically precede our jobs, which is good.

Optionally, this behavior could be configured by a boolean, QuartsHostedServiceOptions.AwaitApplicationStarted. However, I see no issue with the delay, and I consider the timing to be more correct, so I believe the adjusted behavior should be the default.

Could such a default be considered a breaking change? Probably not: Currently, there are no guarantees that a failure in a Quartz job will occur before startup completes (and thus would have the ability to prevent a successful startup). In actuality, they might happen early and prevent startup, or they might happen later and not prevent it. Going from an unpredictable scenario to a predictable seems non-breaking to me, but please correct me if I am overlooking something.

Alternatives

There is QuartzHostedServiceOptions.StartDelay. However, arbitrary delays are bad; startup times may be unpredictable.

Additional Context

We might consider the need to solve this problem as a design flaw in BackgroundService.

Services that perform startup and/or shutdown logic should implement IHostedService. Services that implement background logic (like Quartz jobs, arguably) should inherit from BackgroundService. This changes what methods the service needs to implement, in a way that makes the most sense for the situation.

For IHostedService, it makes sense that StartAsync is invoked before a host starts listening and application startup is considered completed, as an IHostedService is permitted to cause application startup to fail (since .NET Core 3). This enables startup dependencies.

However, BackgroundService also runs its Execute method before startup, as a result of IHostedService.StartAsync being invoked. I would argue that the abstract BackgroundService class should be responsible for awaiting the ApplicationStarted token. (In fact, I might open an issue with this very suggestion.)

Regardless, BackgroundService.Execute is currently being invoked early, so we should handle the situation as well as possible.

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:5 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
lahmacommented, Dec 4, 2021

Closing this as fixed, thank you for the PR!

1reaction
lahmacommented, Nov 24, 2021

Thank you @andrewlock for the insights, I think we have an agreement that the change is welcome. @Timovzl I would appreciate a PR to tweak the behavior and maybe we can try to ping Andrew again if he happened to have the time to review it based on discussion.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Using Quartz.NET with ASP.NET Core and worker services
NET Core and worker service apps using the Quartz.Extensions.Hosting package. ... NET waits for the jobs to end gracefully before exiting.
Read more >
How to start Quartz in ASP.NET Core?
Wait (); } // initiates shutdown of the scheduler, and waits until jobs exit gracefully (within allotted timeout) public void Stop() { if...
Read more >
Background tasks with hosted services in ASP.NET Core
ApplicationStarted is triggered. ... No further services are started until ExecuteAsync becomes asynchronous, such as by calling await .
Read more >
How to start using .NET Background Services
Background services are ideal for many scenarios, especially if your users are willing to wait for results. Some use-case scenarios for ...
Read more >
ASP.NET Core application shutdown events
ApplicationStarted callback hooks the ApplicationStarted event correctly, but the Stopping and Stopped functions do not get called. I am ...
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