When an IIS application pool fails to shut down gracefully within the allotted time, Windows logs event ID 5011 with the message: "A process serving application pool 'xxxx' exceeded time limits during shut down". This typically manifests as intermittent 500 errors that are difficult to reproduce.
From production experience, these are the most common culprits:
// Example of problematic code that might cause shutdown hangs
public class LongRunningService : IRegisteredObject
{
public void Stop(bool immediate)
{
// Bad practice - synchronous long-running operation
Thread.Sleep(120000); // Exceeds default 90s shutdown timeout
}
}
The default shutdown timeout is 90 seconds. To modify in PowerShell:
Import-Module WebAdministration
Set-ItemProperty "IIS:\AppPools\YourAppPool" -Name processModel.shutdownTimeLimit -Value "00:02:00"
For .NET applications implementing IRegisteredObject:
// Proper async shutdown implementation
public async Task StopAsync(bool immediate, CancellationToken token)
{
if (immediate) return;
try {
await CleanupResourcesAsync(token);
}
catch (OperationCanceledException) {
// Gracefully handle timeout
}
}
- Capture dump files during shutdown using
procdump -ma -n 2 -s 30 w3wp.exe - Analyze with WinDbg:
!analyze -vand!clrstack - Check for deadlocks in worker processes
Consider these IIS settings adjustments:
<applicationPools>
<add name="MyAppPool"
autoStart="true"
startMode="AlwaysRunning"
shutdownTimeLimit="00:02:00"
idleTimeout="00:00:00">
<recycling logEventOnRecycle="Time, Requests">
<periodicRestart time="00:00:00" />
</recycling>
</add>
</applicationPools>
When an IIS application pool fails to shut down gracefully within the allocated time, Windows logs Event ID 5139 with the message: "A process serving application pool 'X' exceeded time limits during shut down." This typically manifests as random HTTP 500 errors, especially in applications with long-running operations.
The shutdown timeout violation usually occurs when:
- Application code blocks during
Application_Endor disposal patterns - Database connections aren't properly closed (e.g., missing
usingstatements) - Custom HTTP modules implement synchronous operations in
Dispose() - Third-party components perform cleanup in non-optimized ways
IIS enforces a default 90-second shutdown timeout (configurable via appcmd.exe). During recycling:
// Example of problematic disposal pattern
public class ResourceIntensiveService : IDisposable
{
private SqlConnection _conn;
public void Dispose()
{
// Potential shutdown blocker
_conn.Close(); // Synchronous call
CleanupTempFiles(); // Long-running operation
}
}
Immediate mitigation:
appcmd set config /section:applicationPools
/$$name='YourAppPool'$$.processModel.shutdownTimeLimit:00:05:00
Code-level fixes:
// Revised async disposal pattern
public async ValueTask DisposeAsync()
{
await _conn.CloseAsync();
await Task.Run(() => CleanupTempFiles());
}
Capture shutdown hangs using:
DebugDiag.exe /hang /pn:w3wp.exe /pool:YourAppPool
Analyze the dump with WinDbg to identify blocking threads.
- Implement
IRegisteredObjectfor controlled shutdown - Use async throughout the call stack
- Profile
Application_Endwith MiniProfiler - Consider health checks for background operations
A SaaS platform reduced shutdown errors by 92% after:
// In Global.asax.cs
protected void Application_End()
{
HostingEnvironment.UnregisterObject(this);
Task.Run(async () => {
await _messageBus.FlushAsync();
}).Wait(TimeSpan.FromSeconds(30));
}