Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

Forcing an ASP.NET Application to 'stay alive'


:P
On this page:

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?

this post created and published with Markdown Monster
Posted in .NET  ASP.NET  

The Voices of Reason


 

Manuel Abadia
May 10, 2007

# re: Forcing an ASP.NET Application to 'stay alive'

Have you take a look at Quartz.net? It is a scheduling framework:

http://quartznet.sourceforge.net/

I don't know much about it and how it integrates for ASP.NET but...

Abdu
May 10, 2007

# re: Forcing an ASP.NET Application to 'stay alive'

I would say using an external pinging method is best. You never know when your ISP might reset the IIS service, recycle the pool, reboot the box or something drastic. I have a colocated box and I wrote my own monitor program which ran through a scheduler on the same box. It would get a web page and if the page wasn't retrieved, it would try again and if same problem persisted, the monitor would kill the web service and start it. Sometimes a restart would get stuck so then I kill the process.

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.

Michael Teper
May 11, 2007

# re: Forcing an ASP.NET Application to 'stay alive'

I wrote about this 4 years ago (http://www.angrycoder.com/article.aspx?cid=5&y=2003&m=4&d=11). I think your implementation is cleaner though...

ASP.NET Forums
June 07, 2007

# continious script - ASP.NET Forums


Steven Smith
July 10, 2007

# Steven Smith : Update Cache in Background Thread

Peter Bromberg recently wrote an article on refreshing the ASP.NET cache , which referenced my ASP.NET Caching Best Practices article on MSDN . In my original article, I lamented the general uselessness of the CacheItemRemovedCallback feature, and wished

Aaron
July 12, 2007

# re: Forcing an ASP.NET Application to 'stay alive'

Good stuff. Thanks for confirming that this works. Out of curiousity, what did you use to implement the functionalty of checking the POP3 mailbox?

Rick Strahl
August 24, 2007

# re: Forcing an ASP.NET Application to 'stay alive'

For this particular project we needed to use something free - OpenPop. Works Ok but it has a really clunky interface. For more robust scenarios I've had good luck with aspNetPop3 (http://www.aspnetpop3.com/) from Dave Wanta.

Mike
February 10, 2008

# re: Forcing an ASP.NET Application to 'stay alive'

Great article. I'll use it to monitor folders and delete files that are old. This is easier than writing a windows service.

# MaxGXL Benefits of Glutathione » MaxGXL Glutathione

For those over the age of 45, we recommend that they have their blood fat levels tested on a regular basis as part of their general health management. If your reading is above 6. 5 mmol/ L, your risk of heart disease is about four times greater than of a person with a cholesterol level of 4mmol/ L.

Patrick
April 18, 2008

# re: Forcing an ASP.NET Application to 'stay alive'

Rick,

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.

# http://www.abdominal-belt.com

thanks so much. Good writing

Alex
January 28, 2009

# re: Forcing an ASP.NET Application to 'stay alive'

thanks Rick! My app also checks emails on a timely basis, and the background thread "died" when the worker process got killed. Thanks!

Patrick
February 27, 2009

# re: Forcing an ASP.NET Application to 'stay alive'

Just a note that I found another use for this thread concept.

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!

Grant
October 07, 2009

# re: Forcing an ASP.NET Application to 'stay alive'

This sounds just like what I'm looking for with regards to a way to keep my web site from being unloaded. However, I'm not clear on what I should use for the PING URL, i.e.,

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

Fred
October 13, 2009

# re: Forcing an ASP.NET Application to 'stay alive'

Could you perhaps post your code?

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....

mapick
October 27, 2009

# re: Forcing an ASP.NET Application to 'stay alive'

Hi there, I also used this in global.asax

    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()
        {
            .....

Julius Jude
December 18, 2009

# re: Forcing an ASP.NET Application to 'stay alive'

When APPLICATION_END gets triggered for whatever reason, can you not just FORCE a call to APPLICATION_START immediately?

Another question, how can I at least get back my Session variables when I get to RE-START my application?

Thanks!

Rick Strahl
December 18, 2009

# re: Forcing an ASP.NET Application to 'stay alive'

@Julius - just calling APPLICATION_START doesn't make the app stay alive. it'll still shut down.

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.

Julius Jude
December 21, 2009

# re: Forcing an ASP.NET Application to 'stay alive'

Can cookies be accessible in APPLICATION_START? I'm thinking of writing my session variables into cookies when APPLICATION_END triggers and retrieving them from the cookies when application re-starts.

What do you think?

Frank Laub
January 03, 2010

# re: Forcing an ASP.NET Application to 'stay alive'

Why not use a custom HttpModule for this? It does require a small bit of additional configuration in web.config, but it is guaranteed to stick around for as long as the server is running. Of course IIS might still decide to yank the whole worker process. I have a similar issue and I'm experimenting with this approach. If it's busted, I'll let ya know...

Rick Strahl
January 03, 2010

# re: Forcing an ASP.NET Application to 'stay alive'

@Frank - I fail to see what a module buys you in this scenario. The idea is that you keep the app alive 'from the outside' because you can ping in intervals. A module still depends on server hits, which if they are not coming in will still time you out...

Renato Fabiano
February 09, 2010

# re: Forcing an ASP.NET Application to 'stay alive'

Rick,

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?

Heaps of XAML
May 05, 2010

# Long running WCF and IIS

Long running WCF and IIS

# re: Forcing an ASP.NET Application to 'stay alive'

I believe that the best solution is either to use external providers (like host-tracker) or use a scheduled wget task.
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

Wagner Danda
July 17, 2012

# re: Forcing an ASP.NET Application to 'stay alive'

You should really check this new technology available with IIS 7.5:

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.

asif
August 07, 2012

# re: Forcing an ASP.NET Application to 'stay alive'

this implementation wont work or any implementation which pings in Multi role/load balancing situation. u never know on which server that request went.

Aslam Shareef
April 10, 2013

# re: Forcing an ASP.NET Application to 'stay alive'

Awesome post! Thanks for the ever-useful posts :)
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.

David
June 11, 2013

# re: Forcing an ASP.NET Application to 'stay alive'

Creative way of solving this issue! I find that for any website the slow loading once the website has been recycled is not acceptable to users! Why should a user face a long load time just because the site is unpopular? I tend to use an external ping service as suggested above - haven't had any issues since I started doing that!

Murali
June 20, 2014

# re: Forcing an ASP.NET Application to 'stay alive'

Thanks for writing this Great article!!

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.

Ron
September 23, 2014

# re: Forcing an ASP.NET Application to 'stay alive'

I tried this in my WebAPI application and it works perfect when I run the app from visual studio, but when I deploy it to QA or PROD I'm getting 404 errors. If I take the same url and run it from the browser it is fine.

How can I fix this?

Rick Strahl
September 24, 2014

# re: Forcing an ASP.NET Application to 'stay alive'

@Ron - you get 404 errors from what exactly? There's no reason that the response from an HTTP client wouldn't be same as from a browser unless the URL is incorrect. You may have to hardcode the URL explicitly rather than using relative or virtual paths to specify the URL.

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024