When dealing with WCF services in IIS, application pool recycling can significantly impact performance, especially when your service performs expensive initialization tasks. Let's break down the key concepts:
// Example of expensive initialization in Global.asax
protected void Application_Start(object sender, EventArgs e)
{
// This heavy operation gets wiped during recycling
InitializeGlobalCache(); // Takes 2+ minutes
WarmUpServiceEndpoints(); // CPU-intensive
}
You've correctly identified three critical settings that affect recycling:
- CPU Limit Interval (0 = disabled): When set to non-zero, IIS monitors CPU usage over this interval (minutes). If the limit is exceeded, recycling occurs.
- Idle Time-out (0 = disabled): Worker process shuts down after specified idle time. Different from recycling as it's triggered by inactivity.
- Regular Time Interval (0 = disabled): Forces recycling at fixed intervals regardless of system health.
The distinction between worker process shutdown and application pool recycling:
Worker Process Shutdown | Application Pool Recycling |
---|---|
Triggered by idle timeout | Triggered by schedule/config changes |
Complete process termination | New process spins up before old terminates |
All sessions lost immediately | Overlapping processes during switch |
Beyond the settings you've modified, consider these configurations:
<applicationPools>
<add name="MyWcfAppPool"
managedRuntimeVersion="v4.0"
startMode="AlwaysRunning">
<recycling logEventOnRecycle="Time,Requests">
<periodicRestart time="00:00:00"/>
</recycling>
<processModel idleTimeout="00:00:00"/>
</add>
</applicationPools>
For services that absolutely cannot tolerate recycling:
- Implement a state persistence layer that survives recycling
- Use Windows Service hosting as a fallback
- Create a "warmup" endpoint that triggers initialization
// Example warmup endpoint
[OperationContract]
public void PreloadCache()
{
if (CacheManager.IsInitialized) return;
lock(_initLock)
{
InitializeGlobalCache();
}
}
Remember that completely disabling recycling isn't recommended for production environments. Instead, implement proper cache serialization and initialization strategies to minimize the impact when recycling does occur.
When dealing with WCF services that perform costly initialization (like loading large datasets into memory), IIS recycling becomes a critical pain point. The application pool recycling behavior you're experiencing is actually IIS's default health management mechanism.
The modifications you implemented address the three primary triggers for recycling:
- CPU Limit (0): Prevents recycling when CPU usage exceeds a threshold over the monitoring period
- Idle Time-out (0): Stops IIS from shutting down inactive worker processes
- Regular Time Interval (0): Disables scheduled recycling
1. CPU Limit Interval Explained
The CPU Limit setting has two components:
Limit - Maximum percentage CPU usage allowed (0 = no limit)
Limit Interval - Monitoring window in minutes (0 = disabled)
Setting both to 0 completely disables CPU-based recycling.
2. What "Recycled" Actually Means
Recycling initiates a graceful shutdown sequence:
- IIS stops routing new requests to the worker process
- Existing requests complete or time out
- The w3wp.exe process terminates
- A new worker process starts with fresh initialization
3. Worker Process vs Application Pool Recycling
The key distinction:
Worker Process Shutdown | Application Pool Recycling |
---|---|
Affects single process instance | Full restart of the app pool environment |
Triggered by inactivity (Idle Time-out) | Triggered by schedule/config changes |
Memory is completely released | May preserve some state (depending on config) |
For mission-critical WCF services, consider these extra safeguards:
Windows Service Hosting Alternative
Create a hybrid hosting solution:
// In Program.cs
public static void Main()
{
// Run as Windows Service if not interactive
if (!Environment.UserInteractive)
{
ServiceBase.Run(new WcfWindowsService());
return;
}
// Otherwise run as console app for debugging
StartWcfHost();
Console.ReadLine();
StopWcfHost();
}
Persistent Cache Storage
Implement a disk-based cache fallback:
public class ExpensiveResourceCache
{
private static readonly string CachePath = @"C:\wcf_cache\resource.dat";
public static Resource GetResource()
{
if (File.Exists(CachePath) &&
File.GetLastWriteTime(CachePath) > DateTime.Now.AddHours(-1))
{
return Deserialize(File.ReadAllBytes(CachePath));
}
var resource = FetchResourceFromSource();
File.WriteAllBytes(CachePath, Serialize(resource));
return resource;
}
}
Application Initialization Module
Configure in applicationHost.config:
<applicationPools>
<add name="MyAppPool" startMode="AlwaysRunning">
<recycling logEventOnRecycle="Time, Requests, Schedule" />
</add>
</applicationPools>
<sites>
<site name="MySite">
<application path="/" preloadEnabled="true" />
</site>
</sites>
Add this diagnostic code to detect recycling events:
protected void Application_Start()
{
var currentPID = Process.GetCurrentProcess().Id;
Application["InstanceID"] = currentPID;
AppDomain.CurrentDomain.DomainUnload += (s, e) =>
{
Logger.Warn($"AppDomain unloading! PID: {currentPID}");
};
}
protected void Application_BeginRequest()
{
var runningPID = (int?)Application["InstanceID"];
if (runningPID != Process.GetCurrentProcess().Id)
{
Logger.Error($"PROCESS RECYCLED! Was {runningPID}, now {Process.GetCurrentProcess().Id}");
}
}
Verify these settings in applicationHost.config:
<applicationPools>
<add name="MyAppPool"
autoStart="true"
startMode="AlwaysRunning"
queueLength="5000"
managedRuntimeVersion="v4.0">
<recycling disallowOverlappingRotation="false"
disallowRotationOnConfigChange="true"
logEventOnRecycle="Time, Memory, PrivateMemory">
<schedule>
<clear />
</schedule>
</recycling>
<cpu limit="0" action="NoAction" resetInterval="0" />
<processModel idleTimeout="0"
maxProcesses="1"
shutdownTimeLimit="90"
startupTimeLimit="90" />
</add>
</applicationPools>