using System;
using System.Threading;
using System.Web;
using StackExchange.Profiling;
using StackExchange.Profiling.Internal;
namespace Umbraco.Web.Logging
{
///
/// This is a custom MiniProfiler WebRequestProfilerProvider (which is generally the default) that allows
/// us to profile items during app startup - before an HttpRequest is created
///
///
/// Once the boot phase is changed to BootPhase.BootRequest then the base class (default) provider will handle all
/// profiling data and this sub class no longer performs any logic.
///
internal class WebProfilerProvider : AspNetRequestProvider
{
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim();
private MiniProfiler _startupProfiler;
private int _first;
private volatile BootPhase _bootPhase;
public WebProfilerProvider()
{
// booting...
_bootPhase = BootPhase.Boot;
}
///
/// Indicates the boot phase.
///
private enum BootPhase
{
Boot = 0, // boot phase (before the 1st request even begins)
BootRequest = 1, // request boot phase (during the 1st request)
Booted = 2 // done booting
}
public void BeginBootRequest()
{
_locker.EnterWriteLock();
try
{
if (_bootPhase != BootPhase.Boot)
throw new InvalidOperationException("Invalid boot phase.");
_bootPhase = BootPhase.BootRequest;
// assign the profiler to be the current MiniProfiler for the request
// is's already active, starting and all
HttpContext.Current.Items[":mini-profiler:"] = _startupProfiler;
}
finally
{
_locker.ExitWriteLock();
}
}
public void EndBootRequest()
{
_locker.EnterWriteLock();
try
{
if (_bootPhase != BootPhase.BootRequest)
throw new InvalidOperationException("Invalid boot phase.");
_bootPhase = BootPhase.Booted;
_startupProfiler = null;
}
finally
{
_locker.ExitWriteLock();
}
}
///
/// Starts a new MiniProfiler.
///
///
/// This is called when WebProfiler calls MiniProfiler.Start() so,
/// - as a result of WebRuntime starting the WebProfiler, and
/// - assuming profiling is enabled, on every BeginRequest that should be profiled,
/// - except for the very first one which is the boot request.
///
public override MiniProfiler Start(string profilerName, MiniProfilerBaseOptions options)
{
var first = Interlocked.Exchange(ref _first, 1) == 0;
if (first == false) return base.Start(profilerName, options);
_startupProfiler = new MiniProfiler("StartupProfiler", options);
CurrentProfiler = _startupProfiler;
return _startupProfiler;
}
///
/// Gets the current profiler.
///
///
/// If the boot phase is not Booted, then this will return the startup profiler (this), otherwise
/// returns the base class
///
public override MiniProfiler CurrentProfiler
{
get
{
// if not booting then just use base (fast)
// no lock, _bootPhase is volatile
if (_bootPhase == BootPhase.Booted)
return base.CurrentProfiler;
// else
try
{
var current = base.CurrentProfiler;
return current ?? _startupProfiler;
}
catch
{
return _startupProfiler;
}
}
}
}
}