IIS app pool recycle + quartz scheduling
Asked Answered
N

4

15

I'm running a web app on IIS 7.5 and it needs to recycle occasionally (otherwise memory usage gets out of handing, something i'm looking into!).

When it recycles, it is effectively not running until another request comes in, which quartz is not going to run.

Is there any way to have IIS automatically bring up 1 work process immediately after recycling the app pool to ensure quartz is always online?

Nilla answered 21/6, 2012 at 14:40 Comment(4)
Shouldn't you host quartz in Windows Service instead?Enameling
@JakubKonecki, that would be my plan b, its just more effort than hosting it within the app, since the scheduler only interacts with the app.Nilla
should have been plan A ;-) haacked.com/archive/2011/10/16/…Enameling
Have a look at my answer on Quartz.net scheduler doesn't fire jobs/triggers once deployed. Hope this helps.Culprit
Q
14

Yes!

http://weblogs.asp.net/scottgu/archive/2009/09/15/auto-start-asp-net-applications-vs-2010-and-net-4-0-series.aspx details it quite nicely, basically you need to:

  1. Edit C:\Windows\System32\inetsrv\config\applicationHost.config to include:

    <applicationPools> 
        <add name="MyAppWorkerProcess" managedRuntimeVersion="v4.0" startMode="AlwaysRunning" /> 
    </applicationPools>
    
  2. Declare what should be run as the "warm-up" for your site

    <sites> 
        <site name="MySite" id="1"> 
            <application path="/" serviceAutoStartEnabled="true" serviceAutoStartProvider="PreWarmMyCache" />
        </site> 
    </sites>
    <serviceAutoStartProviders> 
        <add name="PreWarmMyCache" type="PreWarmCache, MyAssembly" /> 
    </serviceAutoStartProviders> 
    
  3. Configure your application with whatever "warm-up" logic you would like:

    public class PreWarmCache : System.Web.Hosting.IProcessHostPreloadClient {
        public void Preload(string[] parameters) { 
            // Perform initialization and cache loading logic here... 
        } 
    } 
    

Note: If all you need is for the w3wp.exe process to be present I believe only step 1 is necessary. If you also need other items (like certain things to be loaded into memory) then step 2 and 3 would also be used.

Quern answered 21/6, 2012 at 16:19 Comment(3)
Sorry for bumping an old topic, but where exactly this warm up class should be located in mvc4 project?Plagio
@StephenS. Thanks but it did not worked for me as indicated on Cannot keep alive Web Application on IIS after Recycling or Restarting. Any help pls?Amused
For others like me who try to use notepad++ or other editor to edit the applicationHost.config, this answer on SO helped (basically, use notepad): linkBenuecongo
T
2

Starting with IIS 8.0, there is an option to simulate a request to the root page, thus a full application initialization: Application Pool advanced settings -> Preload enabled = true.

Of course, startMode should be AlwaysRunning.

More details about how to enable this feature can be found here.

Toxin answered 25/11, 2016 at 15:6 Comment(1)
I found the preload option was not under the application pool setting but instead if you right click the website -> manage -> settings. Then it is there.Amused
Y
0

I took a crack at this problem. While Stephen's answer will keep the app running, in a Spring.Net environment, the framework won't kick off and Quartz will not run. I put together an implementation of IProcessHostPreloadClient that will fire off a real request to the application in order to get all the machinery running. This is also posted on my blog:

public class Preloader : System.Web.Hosting.IProcessHostPreloadClient
{
    public void Preload(string[] parameters)
    {
        var uris = System.Configuration.ConfigurationManager
                         .AppSettings["AdditionalStartupUris"];
        StartupApplication(AllUris(uris));
    }

    public void StartupApplication(IEnumerable<Uri> uris)
    {
        new System.Threading.Thread(o =>
        {
            System.Threading.Thread.Sleep(500);
            foreach (var uri in (IEnumerable<Uri>)o) {
                var client = new System.Net.WebClient();
                client.DownloadStringAsync(uris.First());
            }
        }).Start(uris);
    }

    public IEnumerable<Uri> AllUris(string userConfiguration)
    {
        if (userConfiguration == null)
            return GuessedUris();
        return AllUris(userConfiguration.Split(' ')).Union(GuessedUris());
    }

    private IEnumerable<Uri> GuessedUris()
    {
        string path = System.Web.HttpRuntime.AppDomainAppVirtualPath;
        if (path != null)
            yield return new Uri("http://localhost" + path);
    }

    private IEnumerable<Uri> AllUris(params string[] configurationParts)
    {
        return configurationParts
            .Select(p => ParseConfiguration(p))
            .Where(p => p.Item1)
            .Select(p => ToUri(p.Item2))
            .Where(u => u != null);
    }

    private Uri ToUri(string value)
    {
        try {
            return new Uri(value);
        }
        catch (UriFormatException) {
            return null;
        }
    }

    private Tuple<bool, string> ParseConfiguration(string part)
    {
        return new Tuple<bool, string>(IsRelevant(part), ParsePart(part));
    }

    private string ParsePart(string part)
    {
        // We expect IPv4 or MachineName followed by |
        var portions = part.Split('|');
        return portions.Last();
    }

    private bool IsRelevant(string part)
    {
        var portions = part.Split('|');
        return
            portions.Count() == 1 ||
            portions[0] == System.Environment.MachineName ||
            HostIpAddresses().Any(a => a == portions[0]);
    }

    private IEnumerable<string> HostIpAddresses()
    {
        var adaptors = System.Net.NetworkInformation
                             .NetworkInterface.GetAllNetworkInterfaces();
        return adaptors
                .Where(a => a.OperationalStatus == 
                            System.Net.NetworkInformation.OperationalStatus.Up)
                .SelectMany(a => a.GetIPProperties().UnicastAddresses)
                .Where(a => a.Address.AddressFamily == 
                            System.Net.Sockets.AddressFamily.InterNetwork)
                .Select(a => a.Address.ToString());
    }
}
Yorgen answered 12/1, 2013 at 0:34 Comment(0)
C
-2

Or you could simply modify the "Application_Start" Method in Global.asax to ensure that Quortz is running.

Cornerwise answered 7/8, 2013 at 12:49 Comment(1)
Application_Start is only called during the first request after the recycle or startup, this is not the desired behaviour.Nilla

© 2022 - 2024 — McMap. All rights reserved.