Merge pull request #5770 from OrchardCMS/feature/tenantstateinitializing

Changed the handling of the Initializing shell status.
This commit is contained in:
Sipke Schoorstra
2015-09-11 12:16:20 +01:00
11 changed files with 77 additions and 47 deletions

View File

@@ -44,12 +44,9 @@ namespace Orchard.Setup.Controllers {
public ActionResult Index() { public ActionResult Index() {
var initialSettings = _setupService.Prime(); var initialSettings = _setupService.Prime();
if(initialSettings.State == TenantState.Initializing)
return View("Initializing");
var recipes = _setupService.Recipes().ToList(); var recipes = _setupService.Recipes().ToList();
string recipeDescription = null; string recipeDescription = null;
if (recipes.Count > 0) { if (recipes.Count > 0) {
recipeDescription = recipes[0].Description; recipeDescription = recipes[0].Description;
} }

View File

@@ -137,9 +137,6 @@
<ItemGroup> <ItemGroup>
<Content Include="Views\Document.cshtml" /> <Content Include="Views\Document.cshtml" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Include="Views\Setup\Initializing.cshtml" />
</ItemGroup>
<PropertyGroup> <PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion> <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>

View File

@@ -69,6 +69,17 @@ namespace Orchard.Setup.Services {
} }
public string Setup(SetupContext context) { public string Setup(SetupContext context) {
var initialState = _shellSettings.State;
try {
return SetupInternal(context);
}
catch {
_shellSettings.State = initialState;
throw;
}
}
private string SetupInternal(SetupContext context) {
string executionId; string executionId;
Logger.Information("Running setup for tenant '{0}'.", _shellSettings.Name); Logger.Information("Running setup for tenant '{0}'.", _shellSettings.Name);
@@ -86,6 +97,9 @@ namespace Orchard.Setup.Services {
context.EnabledFeatures = hardcoded.Union(context.EnabledFeatures ?? Enumerable.Empty<string>()).Distinct().ToList(); context.EnabledFeatures = hardcoded.Union(context.EnabledFeatures ?? Enumerable.Empty<string>()).Distinct().ToList();
// Set shell state to "Initializing" so that subsequent HTTP requests are responded to with "Service Unavailable" while Orchard is setting up.
_shellSettings.State = TenantState.Initializing;
var shellSettings = new ShellSettings(_shellSettings); var shellSettings = new ShellSettings(_shellSettings);
if (String.IsNullOrEmpty(shellSettings.DataProvider)) { if (String.IsNullOrEmpty(shellSettings.DataProvider)) {
@@ -93,7 +107,7 @@ namespace Orchard.Setup.Services {
shellSettings.DataConnectionString = context.DatabaseConnectionString; shellSettings.DataConnectionString = context.DatabaseConnectionString;
shellSettings.DataTablePrefix = context.DatabaseTablePrefix; shellSettings.DataTablePrefix = context.DatabaseTablePrefix;
} }
shellSettings.EncryptionAlgorithm = "AES"; shellSettings.EncryptionAlgorithm = "AES";
// Randomly generated key. // Randomly generated key.
@@ -161,10 +175,7 @@ namespace Orchard.Setup.Services {
// Creating a standalone environment. // Creating a standalone environment.
// in theory this environment can be used to resolve any normal components by interface, and those // in theory this environment can be used to resolve any normal components by interface, and those
// components will exist entirely in isolation - no crossover between the safemode container currently in effect. // components will exist entirely in isolation - no crossover between the safemode container currently in effect.
using (var environment = _orchardHost.CreateStandaloneEnvironment(shellSettings, StandaloneEnvironmentOptions.RunningEnvironment)) {
// Set shell state to "Running" so that the proper shell context is created.
shellSettings.State = TenantState.Running;
using (var environment = _orchardHost.CreateStandaloneEnvironment(shellSettings)) {
try { try {
executionId = CreateTenantData(context, environment); executionId = CreateTenantData(context, environment);
} }
@@ -174,10 +185,7 @@ namespace Orchard.Setup.Services {
} }
} }
// Set shell state to "Initializing" so that subsequent HTTP requests are responded to with "Service Unavailable" while Orchard is setting up.
shellSettings.State = _shellSettings.State = TenantState.Initializing;
_shellSettingsManager.SaveSettings(shellSettings); _shellSettingsManager.SaveSettings(shellSettings);
return executionId; return executionId;
} }

View File

@@ -1 +0,0 @@
@T("Orchard is initializing. Please check back in a few minutes.")

View File

@@ -193,13 +193,12 @@ namespace Orchard.Commands {
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
private IContainer CreateHostContainer() { private IContainer CreateHostContainer() {
var hostContainer = OrchardStarter.CreateHostContainer(ContainerRegistrations); var hostContainer = OrchardStarter.CreateHostContainer(ContainerRegistrations);
var host = hostContainer.Resolve<IOrchardHost>(); var host = hostContainer.Resolve<IOrchardHost>();
host.Initialize(); host.Initialize();
return hostContainer; return hostContainer;
} }
private IWorkContextScope CreateStandaloneEnvironment(string tenant) { private IWorkContextScope CreateStandaloneEnvironment(string tenant) {
var host = _hostContainer.Resolve<IOrchardHost>(); var host = _hostContainer.Resolve<IOrchardHost>();
var tenantManager = _hostContainer.Resolve<IShellSettingsManager>(); var tenantManager = _hostContainer.Resolve<IShellSettingsManager>();
@@ -216,13 +215,12 @@ namespace Orchard.Commands {
return env; return env;
} }
else { else {
// In case of an unitiliazed site (no default settings yet), we create a default settings instance. // In case of an uninitialized site (no default settings yet), we create a default settings instance.
var settings = new ShellSettings { Name = ShellSettings.DefaultName, State = TenantState.Uninitialized }; var settings = new ShellSettings { Name = ShellSettings.DefaultName, State = TenantState.Uninitialized };
return host.CreateStandaloneEnvironment(settings); return host.CreateStandaloneEnvironment(settings);
} }
} }
protected void ContainerRegistrations(ContainerBuilder builder) { protected void ContainerRegistrations(ContainerBuilder builder) {
MvcSingletons(builder); MvcSingletons(builder);
@@ -234,14 +232,14 @@ namespace Orchard.Commands {
private CommandHostShellContainerRegistrations CreateShellRegistrations() { private CommandHostShellContainerRegistrations CreateShellRegistrations() {
return new CommandHostShellContainerRegistrations { return new CommandHostShellContainerRegistrations {
Registrations = shellBuilder => { Registrations = shellBuilder => {
shellBuilder.RegisterType<CommandHostVirtualPathMonitor>() shellBuilder.RegisterType<CommandHostVirtualPathMonitor>()
.As<IVirtualPathMonitor>() .As<IVirtualPathMonitor>()
.As<IVolatileProvider>() .As<IVolatileProvider>()
.InstancePerMatchingLifetimeScope("shell"); .InstancePerMatchingLifetimeScope("shell");
shellBuilder.RegisterType<CommandBackgroundService>() shellBuilder.RegisterType<CommandBackgroundService>()
.As<IBackgroundService>() .As<IBackgroundService>()
.InstancePerLifetimeScope(); .InstancePerLifetimeScope();
} }
}; };
} }

View File

@@ -382,7 +382,7 @@ namespace Orchard.Data.Migration.Interpreters {
} }
} }
finally { finally {
_sqlStatements.Clear(); _sqlStatements.Clear();
} }
} }

View File

@@ -1,7 +1,6 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System.Security.Cryptography;
using System.Threading.Tasks; using System.Threading.Tasks;
using Orchard.Caching; using Orchard.Caching;
using Orchard.Environment.Configuration; using Orchard.Environment.Configuration;
@@ -12,6 +11,8 @@ using Orchard.Environment.Descriptor;
using Orchard.Environment.Descriptor.Models; using Orchard.Environment.Descriptor.Models;
using Orchard.Localization; using Orchard.Localization;
using Orchard.Logging; using Orchard.Logging;
using Orchard.Mvc;
using Orchard.Mvc.Extensions;
using Orchard.Utility.Extensions; using Orchard.Utility.Extensions;
using Orchard.Exceptions; using Orchard.Exceptions;
@@ -26,12 +27,13 @@ namespace Orchard.Environment {
private readonly IExtensionLoaderCoordinator _extensionLoaderCoordinator; private readonly IExtensionLoaderCoordinator _extensionLoaderCoordinator;
private readonly IExtensionMonitoringCoordinator _extensionMonitoringCoordinator; private readonly IExtensionMonitoringCoordinator _extensionMonitoringCoordinator;
private readonly ICacheManager _cacheManager; private readonly ICacheManager _cacheManager;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly static object _syncLock = new object(); private readonly static object _syncLock = new object();
private readonly static object _shellContextsWriteLock = new object(); private readonly static object _shellContextsWriteLock = new object();
private IEnumerable<ShellContext> _shellContexts; private IEnumerable<ShellContext> _shellContexts;
private readonly ContextState<IList<ShellSettings>> _tenantsToRestart; private readonly ContextState<IList<ShellSettings>> _tenantsToRestart;
public DefaultOrchardHost( public DefaultOrchardHost(
IShellSettingsManager shellSettingsManager, IShellSettingsManager shellSettingsManager,
IShellContextFactory shellContextFactory, IShellContextFactory shellContextFactory,
@@ -40,7 +42,9 @@ namespace Orchard.Environment {
IExtensionLoaderCoordinator extensionLoaderCoordinator, IExtensionLoaderCoordinator extensionLoaderCoordinator,
IExtensionMonitoringCoordinator extensionMonitoringCoordinator, IExtensionMonitoringCoordinator extensionMonitoringCoordinator,
ICacheManager cacheManager, ICacheManager cacheManager,
IHostLocalRestart hostLocalRestart) { IHostLocalRestart hostLocalRestart,
IHttpContextAccessor httpContextAccessor) {
_shellSettingsManager = shellSettingsManager; _shellSettingsManager = shellSettingsManager;
_shellContextFactory = shellContextFactory; _shellContextFactory = shellContextFactory;
_runningShellTable = runningShellTable; _runningShellTable = runningShellTable;
@@ -49,6 +53,7 @@ namespace Orchard.Environment {
_extensionMonitoringCoordinator = extensionMonitoringCoordinator; _extensionMonitoringCoordinator = extensionMonitoringCoordinator;
_cacheManager = cacheManager; _cacheManager = cacheManager;
_hostLocalRestart = hostLocalRestart; _hostLocalRestart = hostLocalRestart;
_httpContextAccessor = httpContextAccessor;
_tenantsToRestart = new ContextState<IList<ShellSettings>>("DefaultOrchardHost.TenantsToRestart", () => new List<ShellSettings>()); _tenantsToRestart = new ContextState<IList<ShellSettings>>("DefaultOrchardHost.TenantsToRestart", () => new List<ShellSettings>());
@@ -87,13 +92,13 @@ namespace Orchard.Environment {
EndRequest(); EndRequest();
} }
IWorkContextScope IOrchardHost.CreateStandaloneEnvironment(ShellSettings shellSettings) { IWorkContextScope IOrchardHost.CreateStandaloneEnvironment(ShellSettings shellSettings, StandaloneEnvironmentOptions options) {
Logger.Debug("Creating standalone environment for tenant {0}", shellSettings.Name); Logger.Debug("Creating standalone environment for tenant {0}", shellSettings.Name);
MonitorExtensions(); MonitorExtensions();
BuildCurrent(); BuildCurrent();
var shellContext = CreateShellContext(shellSettings); var shellContext = CreateShellContext(shellSettings, options);
var workContext = shellContext.LifetimeScope.CreateWorkContextScope(); var workContext = shellContext.LifetimeScope.CreateWorkContextScope();
return new StandaloneEnvironmentWorkContextScopeWrapper(workContext, shellContext); return new StandaloneEnvironmentWorkContextScopeWrapper(workContext, shellContext);
} }
@@ -138,7 +143,7 @@ namespace Orchard.Environment {
if (allSettings.Any()) { if (allSettings.Any()) {
Parallel.ForEach(allSettings, settings => { Parallel.ForEach(allSettings, settings => {
try { try {
var context = CreateShellContext(settings); var context = CreateShellContext(settings, StandaloneEnvironmentOptions.None);
ActivateShell(context); ActivateShell(context);
} }
catch (Exception ex) { catch (Exception ex) {
@@ -190,16 +195,14 @@ namespace Orchard.Environment {
/// <summary> /// <summary>
/// Creates a shell context based on shell settings. /// Creates a shell context based on shell settings.
/// </summary> /// </summary>
private ShellContext CreateShellContext(ShellSettings settings) { private ShellContext CreateShellContext(ShellSettings settings, StandaloneEnvironmentOptions options) {
switch (settings.State) { if (settings.State != TenantState.Uninitialized || options.Running) {
case TenantState.Uninitialized: Logger.Debug("Creating shell context for tenant {0}.", settings.Name);
case TenantState.Initializing: return _shellContextFactory.CreateShellContext(settings);
Logger.Debug("Creating shell context for tenant {0} setup.", settings.Name);
return _shellContextFactory.CreateSetupContext(settings);
default:
Logger.Debug("Creating shell context for tenant {0}.", settings.Name);
return _shellContextFactory.CreateShellContext(settings);
} }
Logger.Debug("Creating shell context for tenant {0} setup.", settings.Name);
return _shellContextFactory.CreateSetupContext(settings);
} }
private void SetupExtensions() { private void SetupExtensions() {
@@ -240,6 +243,8 @@ namespace Orchard.Environment {
} }
protected virtual void BeginRequest() { protected virtual void BeginRequest() {
BlockRequestsDuringSetup();
// Ensure all shell contexts are loaded, or need to be reloaded if // Ensure all shell contexts are loaded, or need to be reloaded if
// extensions have changed // extensions have changed
MonitorExtensions(); MonitorExtensions();
@@ -284,7 +289,7 @@ namespace Orchard.Environment {
// is this is a new tenant ? or is it a tenant waiting for setup ? // is this is a new tenant ? or is it a tenant waiting for setup ?
if (shellContext == null || settings.State == TenantState.Uninitialized) { if (shellContext == null || settings.State == TenantState.Uninitialized) {
// create the Shell // create the Shell
var context = CreateShellContext(settings); var context = CreateShellContext(settings, StandaloneEnvironmentOptions.None);
// activate the Shell // activate the Shell
ActivateShell(context); ActivateShell(context);
@@ -347,6 +352,25 @@ namespace Orchard.Environment {
_tenantsToRestart.GetState().Add(context.Settings); _tenantsToRestart.GetState().Add(context.Settings);
} }
private void BlockRequestsDuringSetup() {
var httpContext = _httpContextAccessor.Current();
if (httpContext.IsBackgroundContext())
return;
// Get the requested shell.
var runningShell = _runningShellTable.Match(httpContext);
if (runningShell == null)
return;
// If the requested shell is currently initializing, return a Service Unavailable HTTP status code.
if (runningShell.State == TenantState.Initializing) {
var response = httpContext.Response;
response.StatusCode = 503;
response.StatusDescription = "This tenant is currently initializing. Please try again later.";
response.Write("This tenant is currently initializing. Please try again later.");
}
}
// To be used from CreateStandaloneEnvironment(), also disposes the ShellContext LifetimeScope. // To be used from CreateStandaloneEnvironment(), also disposes the ShellContext LifetimeScope.
private class StandaloneEnvironmentWorkContextScopeWrapper : IWorkContextScope { private class StandaloneEnvironmentWorkContextScopeWrapper : IWorkContextScope {
private readonly ShellContext _shellContext; private readonly ShellContext _shellContext;

View File

@@ -95,7 +95,6 @@ namespace Orchard.Environment {
SafelyTerminate(() => _sweepGenerator.Terminate()); SafelyTerminate(() => _sweepGenerator.Terminate());
} }
private void SafelyTerminate(Action action) { private void SafelyTerminate(Action action) {
try { try {
action(); action();

View File

@@ -31,6 +31,6 @@ namespace Orchard.Environment {
/// Can be used to build an temporary self-contained instance of a shell's configured code. /// Can be used to build an temporary self-contained instance of a shell's configured code.
/// Services may be resolved from within this instance to configure and initialize its storage. /// Services may be resolved from within this instance to configure and initialize its storage.
/// </summary> /// </summary>
IWorkContextScope CreateStandaloneEnvironment(ShellSettings shellSettings); IWorkContextScope CreateStandaloneEnvironment(ShellSettings shellSettings, StandaloneEnvironmentOptions options = null);
} }
} }

View File

@@ -0,0 +1,7 @@
namespace Orchard.Environment {
public class StandaloneEnvironmentOptions {
public static readonly StandaloneEnvironmentOptions None = new StandaloneEnvironmentOptions();
public static readonly StandaloneEnvironmentOptions RunningEnvironment = new StandaloneEnvironmentOptions {Running = true};
public bool Running { get; set; }
}
}

View File

@@ -155,6 +155,7 @@
<Compile Include="Data\Migration\Schema\DropUniqueConstraintCommand.cs" /> <Compile Include="Data\Migration\Schema\DropUniqueConstraintCommand.cs" />
<Compile Include="Environment\Extensions\Models\LifecycleStatus.cs" /> <Compile Include="Environment\Extensions\Models\LifecycleStatus.cs" />
<Compile Include="Environment\ShellBuilders\ICompositionStrategy.cs" /> <Compile Include="Environment\ShellBuilders\ICompositionStrategy.cs" />
<Compile Include="Environment\StandaloneEnvironmentOptions.cs" />
<Compile Include="IBackgroundHttpContextFactory.cs" /> <Compile Include="IBackgroundHttpContextFactory.cs" />
<Compile Include="Mvc\Updater.cs" /> <Compile Include="Mvc\Updater.cs" />
<Compile Include="Recipes\Models\ConfigurationContext.cs" /> <Compile Include="Recipes\Models\ConfigurationContext.cs" />