Skip to content

Instantly share code, notes, and snippets.

@davidfowl
Created November 5, 2012 07:33
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save davidfowl/4015810 to your computer and use it in GitHub Desktop.
Save davidfowl/4015810 to your computer and use it in GitHub Desktop.
Detect ASP.NET shutdown
internal class ShutdownDetector : IRegisteredObject, IDisposable
{
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
private Timer _checkAppPoolTimer;
private static readonly TimeSpan _appPoolCheckInterval = TimeSpan.FromSeconds(10);
public CancellationToken Token
{
get { return _cts.Token; }
}
public void Initialize()
{
try
{
HostingEnvironment.RegisterObject(this);
// Create a timer for detecting when the app pool has been requested for shutdown.
// Normally when the appdomain shuts down IRegisteredObject.Stop gets called
// but ASP.NET waits for requests to end before calling IRegisteredObject.Stop (This can be
// troublesome for some frameworks like SignalR that keep long running requests alive).
// This is a more aggresive check to see if the app domain is in the process of being shutdown and
// we trigger the same cts in that case.
if (HttpRuntime.UsingIntegratedPipeline &&
UnsafeIISMethods.CanDetectAppDomainRestart)
{
_checkAppPoolTimer = new Timer(_ =>
{
if (UnsafeIISMethods.RequestedAppDomainRestart)
{
// Trigger the cancellation token
_cts.Cancel(throwOnFirstException: false);
// Stop the timer as we don't need it anymore
_checkAppPoolTimer.Dispose();
}
},
state: null,
dueTime: _appPoolCheckInterval,
period: _appPoolCheckInterval);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
public void Stop(bool immediate)
{
try
{
_cts.Cancel(throwOnFirstException: false);
}
catch
{
// Swallow the exception as Stop should never throw
// TODO: Log exceptions
}
finally
{
HostingEnvironment.UnregisterObject(this);
}
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_cts.Dispose();
if (_checkAppPoolTimer != null)
{
_checkAppPoolTimer.Dispose();
}
}
}
private static class UnsafeIISMethods
{
private static Lazy<UnsafeIISMethodsWrapper> _iis = new Lazy<UnsafeIISMethodsWrapper>(() => new UnsafeIISMethodsWrapper());
public static bool RequestedAppDomainRestart
{
get
{
if (_iis.Value.CheckConfigChanged == null)
{
return false;
}
return !_iis.Value.CheckConfigChanged();
}
}
public static bool CanDetectAppDomainRestart
{
get
{
return _iis.Value.CheckConfigChanged != null;
}
}
private class UnsafeIISMethodsWrapper
{
public Func<bool> CheckConfigChanged { get; private set; }
public UnsafeIISMethodsWrapper()
{
// Private reflection to get the UnsafeIISMethods
var type = Type.GetType("System.Web.Hosting.UnsafeIISMethods, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
if (type == null)
{
return;
}
// This method can tell us if ASP.NET requested and app domain shutdown
MethodInfo methodInfo = type.GetMethod("MgdHasConfigChanged", BindingFlags.NonPublic | BindingFlags.Static);
if (methodInfo == null)
{
// Method signuature changed so just bail
return;
}
try
{
CheckConfigChanged = (Func<bool>)Delegate.CreateDelegate(typeof(Func<bool>), methodInfo);
}
catch
{
// We failed to create the delegate so we can't do the check
// reliably
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment