Files
Orchard/src/Orchard.Startup/Starter.cs
Renaud Paquay b01a0898a9 Refactor Orchard.WarmupStarter assembly
* Remove dependency on Orchard.Framework assembly
* Fix issue where a host initialization failure would result in
  '404' errors. We need to restart the host initialization and
  make sure new incoming requests are queued.
* Fix concurrency issue when multiple requests are pending for the
  host initialization to finish (only one request would notify
  of a potentially error, the other ones would return a '404').

--HG--
branch : 1.x
extra : transplant_source : %3Dz%E4%ADEq%91%9D%17%D2%10jut%A6%93%09t%7CR
2011-05-17 13:01:39 -07:00

106 lines
4.3 KiB
C#

using System;
using System.Threading;
using System.Web;
namespace Orchard.WarmupStarter {
public class Starter<T> where T : class {
private readonly Func<HttpApplication, T> _initialization;
private readonly Action<HttpApplication, T> _beginRequest;
private readonly Action<HttpApplication, T> _endRequest;
private readonly object _synLock = new object();
/// <summary>
/// The result of the initialization queued work item.
/// Set only when initialization has completed without errors.
/// </summary>
private volatile T _initializationResult;
/// <summary>
/// The (potential) error raised by the initialization thread. This is a "one-time"
/// error signal, so that we can restart the initialization once another request
/// comes in.
/// </summary>
private volatile Exception _error;
/// <summary>
/// The (potential) error from the previous initiazalition. We need to
/// keep this error active until the next initialization is finished,
/// so that we can keep reporting the error for all incoming requests.
/// </summary>
private volatile Exception _previousError;
public Starter(Func<HttpApplication, T> initialization, Action<HttpApplication, T> beginRequest, Action<HttpApplication, T> endRequest) {
_initialization = initialization;
_beginRequest = beginRequest;
_endRequest = endRequest;
}
public void OnApplicationStart(HttpApplication application) {
LaunchStartupThread(application);
}
public void OnBeginRequest(HttpApplication application) {
// Initialization resulted in an error
if (_error != null) {
// Save error for next requests and restart async initialization.
// Note: The reason we have to retry the initialization is that the
// application environment may change between requests,
// e.g. App_Data is made read-write for the AppPool.
bool restartInitialization = false;
lock (_synLock) {
if (_error != null) {
_previousError = _error;
_error = null;
restartInitialization = true;
}
}
if (restartInitialization) {
LaunchStartupThread(application);
}
}
// Previous initialization resulted in an error (and another initialization is running)
if (_previousError != null) {
throw new ApplicationException("Error during application initialization", _previousError);
}
// Only notify if the initialization has successfully completed
if (_initializationResult != null) {
_beginRequest(application, _initializationResult);
}
}
public void OnEndRequest(HttpApplication application) {
// Only notify if the initialization has successfully completed
if (_initializationResult != null) {
_endRequest(application, _initializationResult);
}
}
/// <summary>
/// Run the initialization delegate asynchronously in a queued work item
/// </summary>
public void LaunchStartupThread(HttpApplication application) {
// Make sure incoming requests are queued
WarmupHttpModule.SignalWarmupStart();
ThreadPool.QueueUserWorkItem(
state => {
try {
var result = _initialization(application);
_initializationResult = result;
}
catch (Exception e) {
lock (_synLock) {
_error = e;
_previousError = null;
}
}
finally {
// Execute pending requests as the initialization is over
WarmupHttpModule.SignalWarmupDone();
}
});
}
}
}