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 -v
and!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_End
or disposal patterns - Database connections aren't properly closed (e.g., missing
using
statements) - 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
IRegisteredObject
for controlled shutdown - Use async throughout the call stack
- Profile
Application_End
with 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));
}