Forcing an ASP.NET Application to 'stay alive'

This post in the 'stupid pet tricks' category I suppose - it's not one of the things that you need to do every day, but if you need to run an application that requires that your ASP.NET application stays up and running without the discontinuity of AppDomain or worker process shutdowns this can be useful.
First some background on the scenario. I'm working on a small project for a customer that deals with a background scheduler that's running continuously and checks the contents of a POP3 box at intervals. The scheduler itself is created on a new, separate thread that then keeps running in the background. The thread runs in an endless loop with an AutoResetEvent to control the check frequency and active status.
Now your (and mine too) first thought might be - a scheduler has no business in ASP.NET, write a Windows Service or something else that runs permanently on a machine. However, in this particular application the requirement was that it had to run within the confines of a Web server due to the hosting requirements - the application couldn't be hosted locally and it has to be - for the time being - run on an ISP box.
So the question then is: How do you create a process that runs as 'always' active in ASP.NET?
There a few ways, but the easiest I've found is to just spin up a separate thread that runs in an endless loop with some sort of exit flag condition and so keeps the thread alive and running indefinitely for the lifetime of the application. A scheduler like this is simple enough to create:
///
/// This is the manager class that handles running the email operation on a background thread
///
public class Scheduler : IDisposable
{
///
/// Determines the status fo the Scheduler
///
public bool Cancelled
{
get { return _Cancelled; }
set { _Cancelled = value; }
}
private bool _Cancelled = false;
///
/// The frequency of checks against hte POP3 box are
/// performed in Seconds.
///
private int CheckFrequency = 180;
EmailForwarder Forwarder = new EmailForwarder();
AutoResetEvent WaitHandle = new AutoResetEvent(false);
object SyncLock = new Object();
public Scheduler()
{
}
///
/// Starts the background thread processing
///
/// Frequency that checks are performed in seconds
public void Start(int checkFrequency)
{
this.Forwarder.DeleteMessages = App.Configuration.DeletePop3Messages;
// *** Ensure that any waiting instances are shut down
//this.WaitHandle.Set();
this.CheckFrequency = checkFrequency;
this.Cancelled = false;
Thread t = new Thread(Run);
t.Start();
}
///
/// Causes the processing to stop. If the operation is still
/// active it will stop after the current message processing
/// completes
///
public void Stop()
{
lock (this.SyncLock)
{
if (Cancelled)
return;
this.Cancelled = true;
this.WaitHandle.Set();
}
}
///
/// Runs the actual processing loop by checking the mail box
///
private void Run()
{
this.Forwarder.LogMessage("...Starting Service",true);
// *** Start out waiting
this.WaitHandle.WaitOne(this.CheckFrequency * 1000, true);
while (!Cancelled)
{
if (App.Configuration.LogDetail)
this.Forwarder.LogMessage("...Checking mailbox...", true);
if (!this.Forwarder.ProcessMessages())
this.Forwarder.LogMessage("...Processing failed: " + Forwarder.ErrorMessage, false);
else
{
if (App.Configuration.LogDetail)
this.Forwarder.LogMessage("...Processing completed", true);
}
// *** Http Ping to force the server to stay alive
this.PingServer();
// *** Put in
this.WaitHandle.WaitOne(this.CheckFrequency * 1000,true);
}
this.Forwarder.LogMessage("...Shutting down service", true);
}
public void PingServer()
{
try
{
WebClient http = new WebClient();
string Result = http.DownloadString(App.Configuration.PingUrl);
}
catch (Exception ex)
{
string Message = ex.Message;
}
}
#region IDisposable Members
public void Dispose()
{
this.Stop();
}
#endregion
}
This is a slightly specific implementation, but the but the overall concept can be used with most applications with slight changes in the Run methods implementation (heck I should have made this generic and created a DoProcessing() method to override). Basically there are a start and stop methods that check a Cancelled flag. If the cancelled flag is set to true the Run method's loop terminates and the separately spun up thread is terminated. But while Cancelled is not set, the thread keeps looping and running in the background doing its thing at the specified CheckFrequency interval.
One important aspect of this scenario is where to hook up the instantiation of this class - you'll want to this to happen only once for the given application. A good place for this is Application_Start (in global.asax or in a custom module) or alternately in a static constructor of a property that is sure to get fired in the course of the application on every hit. In global.asax the code looks like this:
void Application_End(object sender, EventArgs e)
{
if (App.Scheduler != null)
{
App.Scheduler.Dispose();
App.Scheduler = null;
}
// Force the App to be restarted immediately
new Scheduler().PingServer();
}
App.Scheduler is a static property on an App class I create for every project. My App class contains basic Application settings and constants as well as any static instances of objects that through this mechanism become easily and globally accessible in the application. Another class I always have on my App class for example is App.Configuration which contains all the configuration settings (which also persist into Web.config or an external store).
Application Lifetime
So the above code accounts for starting a background thread and keeping it running, and it will keep running until the application shuts down. But there's are two problems here if this scheduler is meant as an always on type of service:
- If the Application shuts down for any reason the scheduler shuts down with it
- Nothing happens until the application was hit at least once
The former is obvious enough, but it's more common than you might think. ASP.NET applications can recyle to a number of different reasons including, IIS Worker Process idle timeout shutdowns, any configuration change in web.config, any code updates (either a new BIN directory DLL, or any code updates in APP_CODE in web projects), IIS Health Monitoring deciding to recyle the worker process, or more drastic things like Web Server or IIS restarts. It happens more frequently than you might think.
The first thing to deal with is idle time timeouts which can be set for IIS and which cause IIS to recycle the Application if no activity occurs against it. You can get around this issue by pinging the server with an HTTP hit within the timeout period. You can do this externally (I use my own West Wind Web Monitor for this sort of thing, but there are other ways like writing a scheduled task that accesses the HTTP link) or even from within the application itself. You'll notice that the class above has a PingServer method which as part of each processing cycle also explicitly access the local site to force it to stay alive.
Along the same lines you can also call something like PingServer when the application shuts down. For example, you can do the following in Application_End():
void Application_End(object sender, EventArgs e)
{
if (App.Scheduler != null)
{
App.Scheduler.Dispose();
App.Scheduler = null;
}
// Force the App to be restarted immediately
new Scheduler().PingServer();
}
This will in effect cause the current AppDomain to continue to shut down, but ASP.NET will immediately spin up a new one for the new request that has now hit the server and reactivates the server.
So this can HELP keep any background processing running, but it's not a 100% sure thing. Unless you have an outside pinging mechanism that hits the site there's no way for example, for the application itself restart it self when the Web server comes back up. Actually - I think this might be possible in IIS 7 with a module defined at the root Web level, but I haven't tried it.
As I mentioned at the start, this is not a typical scenario and ASP.NET doesn't make exactly the best background processing platform - it's not designed for that. But I've run into this situation several times now with several different customers who are stuck with the Web Server as the implementation vehicle with no option to deploy a more traditional Windows Service and this approach works well for the quick and dirty scenario.
Thinking a little further on this it seems to me that this sort of processing scenario might be a good fit for a Windows WorkFlow application - but the issue there too is how could one host the Workflow in such a way that it's always active within the context of the Web Server?
The Voices of Reason
# re: Forcing an ASP.NET Application to 'stay alive'
Another question, how can I at least get back my Session variables when I get to RE-START my application?
Thanks!
# re: Forcing an ASP.NET Application to 'stay alive'
If you want persistent session state use the SessionState Service or SQL Server sessions which persist across App restarts. In Process Sessions (default) cannot survive AppDomain restarts.
# re: Forcing an ASP.NET Application to 'stay alive'
What do you think?
# re: Forcing an ASP.NET Application to 'stay alive'
http://weblogs.asp.net/scottgu/archive/2009/09/15/auto-start-asp-net-applications-vs-2010-and-net-4-0-series.aspx
I'm using a hybrid approach, using both ideas to have kick off my scheduler from my autoStartProvider (PreWarmCache.Preload) instead of global.asax. This guarantees its going to be executed even when my apppool is recycle and no one has hit the website yet. Its awesome.
# re: Forcing an ASP.NET Application to 'stay alive'
# re: Forcing an ASP.NET Application to 'stay alive'
# re: Forcing an ASP.NET Application to 'stay alive'
# re: Forcing an ASP.NET Application to 'stay alive'
# re: Forcing an ASP.NET Application to 'stay alive'
# re: Forcing an ASP.NET Application to 'stay alive'
I'd like to suggest Abidar (http://abidar.codeplex.com/), developed by Keyvan Nayyeri and Dave Burke (http://nayyeri.net/abidar-asp-net-task-scheduling-framework).
It's a generic task scheduler library paremetrized using a XML file where tasks schedule is stored (see Abidar.TestSite/Config/Tasks.config).
I'm not sure if it would be necessary to include something like "PingServer()" in an endless loop. Abidar adopts System.Timers to schedule task execution (see Abidar/Component/Task.cs).
Do System.Timers used by Abidar maintain ASP.NET application alive and fire tasks scheduled normally, independently of the interative use of Web pages?
# re: Forcing an ASP.NET Application to 'stay alive'
# re: Forcing an ASP.NET Application to 'stay alive'
The value you load from "App.Configuration.PingUrl".
Would this just be the URL of any page of my web site? If not, what would I use?
For example, I tried pinging my site:
http://localhost/STSimpleCall/STSimpleCallWeb.aspx
# re: Forcing an ASP.NET Application to 'stay alive'
I used your code and it works great.
One thing I noticed is when the site is closed the thread is still running. I can see this by looking at the web log since I requested a URL to see what was happening. I would have thought the thread would have died on its own like in .NET 1.1 but it appears .NET 2.0 will keep it running.
# re: Forcing an ASP.NET Application to 'stay alive'
I get all sorts of errors:
Class 'Scheduler' must implement 'Sub Dispose()' for interface 'System.IDisposable'
Type 'AutoResetEvent' is not defined.
etc
So I see that you mention some variables that you declare elsewhere, but im unsure whether they are even required for my situation.
In short: I dont see what is part of your implementation is specific and what part is generic....
# re: Forcing an ASP.NET Application to 'stay alive'
void Application_Start(Object sender, EventArgs e) { ... // Override application automatic closing timeout // // Set the path of the config file. string configPath = ""; // Get the Web application configuration object Configuration config = WebConfigurationManager.OpenWebConfiguration(configPath); // Get the section related object HostingEnvironmentSection configSection = (HostingEnvironmentSection)config.GetSection("system.web/hostingEnvironment"); // Display ShutdownTimeout property ...some kind of record/display: "Actual shutdown Timeout " + configSection.ShutdownTimeout.ToString()); // Set ShutdownTimeout property configSection.ShutdownTimeout = TimeSpan.FromHours(12); ...some kind of record/display: "Actual shutdown Timeout " + configSection.ShutdownTimeout.ToString()); // ...and put also the thread in cache for major execution security if (Context.Cache.Get("my_thread") == null) Context.Cache.Insert("my_thread", new my_thred_constructor(), null, Cache.NoAbsoluteExpiration, new TimeSpan(24, 0, 0), CacheItemPriority.NotRemovable, null); else my_thread: "Thread already running..."); ... }
...and the thread constructor:
class my_Thred { public my_thred() { Thread my_thread = new Thread(new ThreadStart(my_thread_start)); my_thread.Start(); } private static void my_thread_start() { .....
# re: Forcing an ASP.NET Application to 'stay alive'
# re: Forcing an ASP.NET Application to 'stay alive'
I have a web site (www.MTRIG.com) where I need to download stock quotes end of day, and I want to do this in a background when the first user hits the site after 4:00. Since my site is not a VPS server at the moment I do not have access to scheduled tasks so this background thread idea works great, again!
Thanks Rick, the best .NET Blog on the web!
# re: Forcing an ASP.NET Application to 'stay alive'
I believe your approach will be the best solution for me. I am using Quartz.Net in my asp.net application and we are ending up with IIS recycling and it stops working.
We hosted our application in external hosting provider, so we cannot play with config file explained http://weblogs.asp.net/scottgu/auto-start-asp-net-applications-vs-2010-and-net-4-0-series
Let me try your approach and solve my problem.
# re: Forcing an ASP.NET Application to 'stay alive'
http://quartznet.sourceforge.net/
I don't know much about it and how it integrates for ASP.NET but...
# re: Forcing an ASP.NET Application to 'stay alive'
Anyways, you either put a similar program on your home computer and get it to ping the web server or use one of those monitoring services. Some offer free plans but the frequency of the free ones would be outside of the web service timeout. For example, once a day is not frequent enough.
# re: Forcing an ASP.NET Application to 'stay alive'
# re: Forcing an ASP.NET Application to 'stay alive'
How can I fix this?
# re: Forcing an ASP.NET Application to 'stay alive'
# re: Forcing an ASP.NET Application to 'stay alive'
The advantage of using the latter one is that one can have custom checks as well - like queries about the health of the site.
I really do not believe that this function should be part of the application in any way as it is a configuration not a software function.
Cheers,
Adam
Senior .NET developer, architect
http://www.teamleadnet.com
ps: I have used the wget method and it worked like a charm
# re: Forcing an ASP.NET Application to 'stay alive'
Quick question : My site's connection string relies on the URL of the site. So its essentially a multi-tenant app and I pick up the tenants conn string using the site url. I tried to use PreWarmUp.PreLoad so that the app gets ..well... warmed up even before the first hit comes in. Now the problem is i dont know how to get the current sites URL ... and i cant hit it with a relative path since that would not give me the actual URL and hence the connection string wotn be found.
Any pointers would be appreciated.
# re: Forcing an ASP.NET Application to 'stay alive'