#18162: Fixing Orchard.WarmupStarter location

Work Item: 18162

--HG--
branch : 1.x
rename : src/Orchard.Startup/Orchard.WarmupStarter.csproj => src/Orchard.WarmupStarter/Orchard.WarmupStarter.csproj
rename : src/Orchard.Startup/Properties/AssemblyInfo.cs => src/Orchard.WarmupStarter/Properties/AssemblyInfo.cs
rename : src/Orchard.Startup/Starter.cs => src/Orchard.WarmupStarter/Starter.cs
rename : src/Orchard.Startup/WarmupHttpModule.cs => src/Orchard.WarmupStarter/WarmupHttpModule.cs
rename : src/Orchard.Startup/WarmupUtility.cs => src/Orchard.WarmupStarter/WarmupUtility.cs
This commit is contained in:
ldhertert
2011-12-02 15:47:36 -08:00
parent e2aad34299
commit 44a1f77efc
7 changed files with 3 additions and 3 deletions

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{5C0D5249-AEF5-4BB6-8F5F-057B91AC2D7A}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Orchard.WarmupStarter</RootNamespace>
<AssemblyName>Orchard.WarmupStarter</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Web" />
</ItemGroup>
<ItemGroup>
<Compile Include="Starter.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="WarmupHttpModule.cs" />
<Compile Include="WarmupUtility.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -0,0 +1,39 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Orchard.WarmupStarter")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyProduct("Orchard")]
[assembly: AssemblyCopyright("Copyright © Outercurve Foundation 2011")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("02319f2f-22ec-448d-b4f2-568fb8502242")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.3.0")]
[assembly: AssemblyFileVersion("1.3.0")]
// Enable web application to call this assembly in Full Trust
[assembly: AllowPartiallyTrustedCallers]

View File

@@ -0,0 +1,105 @@
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();
}
});
}
}
}

View File

@@ -0,0 +1,159 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Web;
namespace Orchard.WarmupStarter {
public class WarmupHttpModule : IHttpModule {
private HttpApplication _context;
private static object _synLock = new object();
private static IList<Action> _awaiting = new List<Action>();
public void Init(HttpApplication context) {
_context = context;
context.AddOnBeginRequestAsync(BeginBeginRequest, EndBeginRequest, null);
}
public void Dispose() {
}
private static bool InWarmup() {
lock (_synLock) {
return _awaiting != null;
}
}
/// <summary>
/// Warmup code is about to start: Any new incoming request is queued
/// until "SignalWarmupDone" is called.
/// </summary>
public static void SignalWarmupStart() {
lock (_synLock) {
if (_awaiting == null) {
_awaiting = new List<Action>();
}
}
}
/// <summary>
/// Warmup code just completed: All pending requests in the "_await" queue are processed,
/// and any new incoming request is now processed immediately.
/// </summary>
public static void SignalWarmupDone() {
IList<Action> temp;
lock (_synLock) {
temp = _awaiting;
_awaiting = null;
}
if (temp != null) {
foreach (var action in temp) {
action();
}
}
}
/// <summary>
/// Enqueue or directly process action depending on current mode.
/// </summary>
private void Await(Action action) {
Action temp = action;
lock (_synLock) {
if (_awaiting != null) {
temp = null;
_awaiting.Add(action);
}
}
if (temp != null) {
temp();
}
}
private IAsyncResult BeginBeginRequest(object sender, EventArgs e, AsyncCallback cb, object extradata) {
// host is available, process every requests, or file is processed
if (!InWarmup() || WarmupUtility.DoBeginRequest(_context)) {
var asyncResult = new DoneAsyncResult(extradata);
cb(asyncResult);
return asyncResult;
}
else {
// this is the "on hold" execution path
var asyncResult = new WarmupAsyncResult(cb, extradata);
Await(asyncResult.Completed);
return asyncResult;
}
}
private static void EndBeginRequest(IAsyncResult ar) {
}
/// <summary>
/// AsyncResult for "on hold" request (resumes when "Completed()" is called)
/// </summary>
private class WarmupAsyncResult : IAsyncResult {
private readonly EventWaitHandle _eventWaitHandle = new AutoResetEvent(false/*initialState*/);
private readonly AsyncCallback _cb;
private readonly object _asyncState;
private bool _isCompleted;
public WarmupAsyncResult(AsyncCallback cb, object asyncState) {
_cb = cb;
_asyncState = asyncState;
_isCompleted = false;
}
public void Completed() {
_isCompleted = true;
_eventWaitHandle.Set();
_cb(this);
}
bool IAsyncResult.CompletedSynchronously {
get { return false; }
}
bool IAsyncResult.IsCompleted {
get { return _isCompleted; }
}
object IAsyncResult.AsyncState {
get { return _asyncState; }
}
WaitHandle IAsyncResult.AsyncWaitHandle {
get { return _eventWaitHandle; }
}
}
/// <summary>
/// Async result for "ok to process now" requests
/// </summary>
private class DoneAsyncResult : IAsyncResult {
private readonly object _asyncState;
private static readonly WaitHandle _waitHandle = new ManualResetEvent(true/*initialState*/);
public DoneAsyncResult(object asyncState) {
_asyncState = asyncState;
}
bool IAsyncResult.CompletedSynchronously {
get { return true; }
}
bool IAsyncResult.IsCompleted {
get { return true; }
}
WaitHandle IAsyncResult.AsyncWaitHandle {
get { return _waitHandle; }
}
object IAsyncResult.AsyncState {
get { return _asyncState; }
}
}
}
}

View File

@@ -0,0 +1,71 @@
using System;
using System.IO;
using System.Text;
using System.Web;
using System.Web.Hosting;
namespace Orchard.WarmupStarter {
public static class WarmupUtility {
public static readonly string WarmupFilesPath = "~/App_Data/Warmup/";
/// <summary>
/// return true to put request on hold (until call to Signal()) - return false to allow pipeline to execute immediately
/// </summary>
/// <param name="httpApplication"></param>
/// <returns></returns>
public static bool DoBeginRequest(HttpApplication httpApplication) {
// use the url as it was requested by the client
// the real url might be different if it has been translated (proxy, load balancing, ...)
var url = ToUrlString(httpApplication.Request);
var virtualFileCopy = WarmupUtility.EncodeUrl(url.Trim('/'));
var localCopy = Path.Combine(HostingEnvironment.MapPath(WarmupFilesPath), virtualFileCopy);
if (File.Exists(localCopy)) {
// result should not be cached, even on proxies
httpApplication.Response.Cache.SetExpires(DateTime.UtcNow.AddDays(-1));
httpApplication.Response.Cache.SetValidUntilExpires(false);
httpApplication.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
httpApplication.Response.Cache.SetCacheability(HttpCacheability.NoCache);
httpApplication.Response.Cache.SetNoStore();
httpApplication.Response.WriteFile(localCopy);
httpApplication.Response.End();
return true;
}
// there is no local copy and the file exists
// serve the static file
if (File.Exists(httpApplication.Request.PhysicalPath)) {
return true;
}
return false;
}
public static string ToUrlString(HttpRequest request) {
return string.Format("{0}://{1}{2}", request.Url.Scheme, request.Headers["Host"], request.RawUrl);
}
public static string EncodeUrl(string url) {
if (String.IsNullOrWhiteSpace(url)) {
throw new ArgumentException("url can't be empty");
}
var sb = new StringBuilder();
foreach (var c in url.ToLowerInvariant()) {
// only accept alphanumeric chars
if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) {
sb.Append(c);
}
// otherwise encode them in UTF8
else {
sb.Append("_");
foreach (var b in Encoding.UTF8.GetBytes(new[] { c })) {
sb.Append(b.ToString("X"));
}
}
}
return sb.ToString();
}
}
}