We still dig into “mystery” half-finished jobs after a routine app pool recycle and land on the same default. IIS was willing to wait on the worker process. The .NET generic host was not. BackgroundService.ExecuteAsync got a cancellation token, ran for a few seconds, then the process went away—no request-path exception, no failed deploy, just truncated queue work and rows left mid-update in SQL Server.
HostOptions.ShutdownTimeout defaults to five seconds. When ANCM signals shutdown, the host cancels hosted services and only waits that long before tearing down. Overlapped recycle makes it feel random: the new worker is already serving traffic while the old one silently drops cleanup. On Windows Server with IIS 10.0 this is ordinary pool behavior, not a hosting outage.
#Align the host stop with the pool limit
Raise ShutdownTimeout in Program.cs so your drain path can finish, and keep it below the app pool Shutdown Time Limit (commonly 90 seconds) so IIS is not the one hard-killing first. For most ASP.NET Core 10 apps we see on shared and Windows VPS pools, 30–60 seconds is enough for a disciplined stop; if you need minutes, the work does not belong in the web app pool.
builder.Services.Configure<HostOptions>(options =>
{
// Must stay under the IIS app pool Shutdown Time Limit
options.ShutdownTimeout = TimeSpan.FromSeconds(60);
});
In ExecuteAsync, treat OperationCanceledException as a normal recycle signal: stop taking new items, finish or checkpoint the current unit of work, then exit. Swallowing cancellation and plowing ahead is how you get ObjectDisposedException on DI scopes and SQL connections while the host is already closing.
#What we reject in review
- Don't use fire-and-forget Task.Run from a controller for anything you care about. That work is not a hosted service, gets no ShutdownTimeout courtesy, and vanishes with the worker process.
- Don't crank ShutdownTimeout to several minutes so startup migrations, report fan-out, or third-party API storms can finish. Move those to the deploy step or an out-of-process worker; the app pool is the HTTP front door.
If the job must survive recycle, put the durable state in SQL Server (or another queue the next worker can claim) and keep the in-process service thin. Five seconds is a reasonable default for empty demos. It is not a batch contract on a live IIS app pool.
Comments
No comments yet