Blocking requests to tenants being restarted, thus not allowing users to see temporary YSODs

This commit is contained in:
Lombiq 2016-03-19 19:54:46 +01:00 committed by Zoltán Lehóczky
parent 740039d6ef
commit 3dc6e6523f
3 changed files with 124 additions and 13 deletions

View File

@ -14,8 +14,11 @@ using Orchard.Logging;
using Orchard.Mvc;
using Orchard.Mvc.Extensions;
using Orchard.Utility.Extensions;
using Orchard.Utility;
using Orchard.Exceptions;
using System.Threading;
using System.Web;
using Orchard.Mvc;
namespace Orchard.Environment {
// All the event handlers that DefaultOrchardHost implements have to be declared in OrchardStarter.
@ -31,6 +34,7 @@ namespace Orchard.Environment {
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly static object _syncLock = new object();
private readonly static object _shellContextsWriteLock = new object();
private readonly NamedReaderWriterLock _shellActivationLock = new NamedReaderWriterLock();
private IEnumerable<ShellContext> _shellContexts;
private readonly ContextState<IList<ShellSettings>> _tenantsToRestart;
@ -267,10 +271,30 @@ namespace Orchard.Environment {
protected virtual void BeginRequest() {
BlockRequestsDuringSetup();
// Ensure all shell contexts are loaded, or need to be reloaded if
// extensions have changed
MonitorExtensions();
BuildCurrent();
Action ensureInitialized = () => {
// Ensure all shell contexts are loaded, or need to be reloaded if
// extensions have changed
MonitorExtensions();
BuildCurrent();
};
ShellSettings currentShellSettings = null;
var httpContext = _httpContextAccessor.Current();
if (httpContext != null) {
currentShellSettings = _runningShellTable.Match(httpContext);
}
if (currentShellSettings == null) {
ensureInitialized();
}
else {
_shellActivationLock.RunWithReadLock(currentShellSettings.Name, () => {
ensureInitialized();
});
}
// StartUpdatedShells can cause a writer shell activation lock so it should run outside the reader lock.
StartUpdatedShells();
}
@ -328,19 +352,21 @@ namespace Orchard.Environment {
}
// reload the shell as its settings have changed
else {
// dispose previous context
shellContext.Shell.Terminate();
_shellActivationLock.RunWithWriteLock(settings.Name, () => {
// dispose previous context
shellContext.Shell.Terminate();
var context = _shellContextFactory.CreateShellContext(settings);
var context = _shellContextFactory.CreateShellContext(settings);
// Activate and register modified context.
// Forcing enumeration with ToArray() so a lazy execution isn't causing issues by accessing the disposed shell context.
_shellContexts = _shellContexts.Where(shell => shell.Settings.Name != settings.Name).Union(new[] { context }).ToArray();
// Activate and register modified context.
// Forcing enumeration with ToArray() so a lazy execution isn't causing issues by accessing the disposed shell context.
_shellContexts = _shellContexts.Where(shell => shell.Settings.Name != settings.Name).Union(new[] { context }).ToArray();
shellContext.Dispose();
context.Shell.Activate();
shellContext.Dispose();
context.Shell.Activate();
_runningShellTable.Update(settings);
_runningShellTable.Update(settings);
});
}
}

View File

@ -699,6 +699,7 @@
<Compile Include="Messaging\Services\IMessagingChannel.cs" />
<Compile Include="IWorkContextAccessor.cs" />
<Compile Include="Utility\Extensions\VirtualPathProviderExtensions.cs" />
<Compile Include="Utility\NamedReaderWriterLock.cs" />
<Compile Include="Utility\ReflectionHelper.cs" />
<Compile Include="Validation\PathValidation.cs" />
<Compile Include="Wcf\OrchardDependencyInjectionServiceBehavior.cs" />

View File

@ -0,0 +1,84 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Orchard.Utility {
/// <summary>
/// Provides locking similar to <see cref="ReaderWriterLockSlim"/> but
/// </summary>
/// <remarks>
/// Taken from http://johnculviner.com/achieving-named-lock-locker-functionality-in-c-4-0/ and adapted a bit. Namely:
/// - <see cref="GetOrAdd"/> uses a new ReaderWriterLockSlim to overcome possible concurrency issues where the factory delegate could run multiple times.
/// - Implemented <see cref="IDisposable"/>.
/// </remarks>
public class NamedReaderWriterLock : IDisposable {
private readonly ConcurrentDictionary<string, ReaderWriterLockSlim> _lockDictonary = new ConcurrentDictionary<string, ReaderWriterLockSlim>();
public ReaderWriterLockSlim GetLock(string name) {
return _lockDictonary.GetOrAdd(name, new ReaderWriterLockSlim());
}
public TResult RunWithReadLock<TResult>(string name, Func<TResult> body) {
var rwLock = GetLock(name);
try {
rwLock.EnterReadLock();
return body();
}
finally {
rwLock.ExitReadLock();
}
}
public void RunWithReadLock(string name, Action body) {
var rwLock = GetLock(name);
try {
rwLock.EnterReadLock();
body();
}
finally {
rwLock.ExitReadLock();
}
}
public TResult RunWithWriteLock<TResult>(string name, Func<TResult> body) {
var rwLock = GetLock(name);
try {
rwLock.EnterWriteLock();
return body();
}
finally {
rwLock.ExitWriteLock();
}
}
public void RunWithWriteLock(string name, Action body) {
var rwLock = GetLock(name);
try {
rwLock.EnterWriteLock();
body();
}
finally {
rwLock.ExitWriteLock();
}
}
public void RemoveLock(string name) {
ReaderWriterLockSlim o;
_lockDictonary.TryRemove(name, out o);
}
/// <summary>
/// Disposes all the internal <see cref="ReaderWriterLockSlim"/> objects. Only call this if you're sure that no concurrent code executes
/// any other instance method of this class!
/// </summary>
public void Dispose() {
foreach (var lockSlim in _lockDictonary.Values) {
lockSlim.Dispose();
}
}
}
}