Hosting an ASP.NET app in IIS has its benefits. However, the consequences of on-demand loading and app pool recycling actions are sometimes undesirable. One of them is that after the app pool has been recycled, the application is not initialized until some user hits it. This becomes a problem when we call a longer method on startup, as the user has to wait until the app is fully initialized and his request is handled. We will take a look how to deal with this issue in MVC app, but this solution can be applied to any app hosted in IIS.
The ASP.NET applications are usually hosted in IIS, most of the time within own application pools. App pool is basically a separate windows worker process called w3wp.exe, which is started only when needed. By default, the app pool in IIS has Start mode setup to OnDemand. This way, the app pool starts when IIS receives first request for a particular application bound to that app pool. The app pool is running until it's terminated for some reason (Idle Time-out, IIS has been stopped etc.). This enables IIS to host multiple applications in the same time, without keeping some of them (with lower workload) running all the time.
App pool recycling means terminating the current worker process and starting the new one (IIS overlaps the recycle method, thus the old process finish the handling of old requests and new process handles the new requests). Process recycling can happen manually, on IIS configuration changes, on IIS server restart, or when defined threshold on memory, on time or amount of processed requests is reached. Generally, when IIS detects that some worker process is unhealthy (for example because of memory leaks) it recycles it, which prevents the app from crashing.
After app pool recycling, the startup methods from
global.asax.cs
andstartup.cs
are NOT called by default.
Beside the app pool recycling, there is another source of problem. While the worker process is still alive, for any application that runs within the process an AppDomain restart can occur (without app pool recycling). Basically it means that the application starts over with the new configuration, but in the same worker process. AppDomain restart occurs for reasons such as web deploy, changes to web.config, updating files in BIN folder etc.
After AppDomain restart, the startup methods from
global.asax.cs
andstartup.cs
are NOT called by default.
Let's take a look on our sample MVC app and its requirements. We well call it MONIT and it displays some statistics from financial markets.
Because it processes huge amount of data, quite a big in-memory cache has to be constructed on application's startup.
MONIT's initialization takes a while (cca 10 seconds), because in the global.asax.cs
we call BuildInMemoryCache
method.
There is only a couple of financial analysts who use the app. They check the app's dashboard only a few times per day,
when they need to check performance of specific financial product.
For that reason, most of the time the MONIT's app is stopped, and when some user makes a
request to the dashboard page, he need to wait until the app is initialized and the dashboard is displayed.
Of course, the users are unhappy and they demand to fix it, so they can see the dashboard immediately any time they hit the application.
Because of small amount of requests, the app pool bound to MONIT app is usually stopped. When IIS receives the
request for MONIT app, it starts its app pool (i.e. new worker process), then the worker loads the AppDomain - the app is initialized
(in case of MVC app, the appropriate methods from global.asax.cs
and startup.cs
are called) and the request is handled.
From that moment, the application is up and all the following requests are handled directly, without starting the worker process and AppDomain loading.
However, if there are no more requests for period longer than defined in Idle Time-out,
the app pool is terminated. It can happen also for different reasons such as IIS server restart.
The app pool starts again when new request for MONIT app is received.
Because starting the new worker process (app pool) takes time, we want the worker process to be alive just after IIS startup and keep it alive all the time. We setup it in IIS with following 2 steps.
BuildInMemoryCache
method in global.asax.cs
,
which is called on our application's startup, when the first request is received by the worker process.
From IIS 8.0 and higher, there is also built in feature called Application Initialization. It address the warm-up problem directly, by proactively performing the initialization tasks. It can be even configured to return some static page, while the warm-up is running.
However, after some testing I found out, that it performs the initialization task after app pool recycling, but not after AppDomain restart (shame on you Microsoft...). Because of that, I suggest auto-start feature in this post.
BuildInMemoryCache
functionality to separate class from global.asax.cs
.
The CacheBuilder
class is singleton, because we want to ensure that the cache will be built only once,
even if we will call it from two places. global.asax.cs
file. We keep it there for the environments where the auto-start feature won't be setup
IProcessHostPreloadClient
. This class will be called automatically by WAS
after app pool recycling or AppDomain restart.
We edit the global applicationHost.config
file (%WINDIR%\System32\inetsrv\config\applicationHost.config
) as follows.
On 64bit OS, be sure that you use a 64bit editor for editing
applicationHost.config
, instead of some 32bit editor like Notepad++. Otherwise , you are actually opening/saving theC:\Windows\SysWOW64\inetsrv\Config\applicationHost.config
regardless of what the title bar or save path may suggest in your editor. This can lead to lot of confusion, believe me...
The best method to verify if our warm-up method works is by adding some logs to the Preload(string[] parameters)
method.
After we recycle the application pool (e.g. by restarting the IIS server or recycling the app pool manually),
or restart the AppDomain by editing the web.config file, the Preload
method should be called,
without making any requests to the hosted application.
With this setup, almost every time when some user makes a request to our MONIT application, its in-memory cache is already built so he/she don't have to wait until it's constructed, even if it was the first request after app pool recycling or AppDomain restart. I said almost every time, because the user can hit the page just in time, when the app pool was recycled and the preload method is running. But that's quite unlikely scenario, especially for low-traffic applications like this one.
I created this technical article for Signals company in cooperation...
I created this technical article for Signals company in cooperation...
Many of us use the async/await feature in C# projects,...