public async Task ExecuteTasksAsync(DeferredTaskContext context) { for (var i = 0; i < _deferredTaskState.Tasks.Count; i++) { var task = _deferredTaskState.Tasks[i]; try { await task(context); } catch (Exception e) { _logger.LogError(e, "An error occured while processing a deferred task"); } } _deferredTaskState.Tasks.Clear(); }
public async Task <string> ExecuteAsync(string executionId, RecipeDescriptor recipeDescriptor) { await _eventBus.NotifyAsync <IRecipeEventHandler>(x => x.RecipeExecutingAsync(executionId, recipeDescriptor)); try { var parsersForFileExtension = _recipeOptions .RecipeFileExtensions .Where(rfx => Path.GetExtension(rfx.Key) == Path.GetExtension(recipeDescriptor.RecipeFileInfo.PhysicalPath)); using (var stream = recipeDescriptor.RecipeFileInfo.CreateReadStream()) { RecipeResult result = new RecipeResult { ExecutionId = executionId }; List <RecipeStepResult> stepResults = new List <RecipeStepResult>(); foreach (var parserForFileExtension in parsersForFileExtension) { var recipeParser = _recipeParsers.First(x => x.GetType() == parserForFileExtension.Value); await recipeParser.ProcessRecipeAsync(stream, (recipe, recipeStep) => { // TODO, create Result prior to run stepResults.Add(new RecipeStepResult { ExecutionId = executionId, RecipeName = recipeDescriptor.Name, StepId = recipeStep.Id, StepName = recipeStep.Name }); return(Task.CompletedTask); }); } result.Steps = stepResults; await _recipeStore.UpdateAsync(result); } using (var stream = recipeDescriptor.RecipeFileInfo.CreateReadStream()) { foreach (var parserForFileExtension in parsersForFileExtension) { var recipeParser = _recipeParsers.First(x => x.GetType() == parserForFileExtension.Value); await recipeParser.ProcessRecipeAsync(stream, async (recipe, recipeStep) => { var shellContext = _orchardHost.GetOrCreateShellContext(_shellSettings); using (var scope = shellContext.CreateServiceScope()) { if (!shellContext.IsActivated) { var eventBus = scope.ServiceProvider.GetService <IEventBus>(); await eventBus.NotifyAsync <IOrchardShellEvents>(x => x.ActivatingAsync()); await eventBus.NotifyAsync <IOrchardShellEvents>(x => x.ActivatedAsync()); shellContext.IsActivated = true; } var recipeStepExecutor = scope.ServiceProvider.GetRequiredService <IRecipeStepExecutor>(); if (_applicationLifetime.ApplicationStopping.IsCancellationRequested) { throw new OrchardException(T["Recipe cancelled, application is restarting"]); } await recipeStepExecutor.ExecuteAsync(executionId, recipeStep); } // The recipe execution might have invalidated the shell by enabling new features, // so the deferred tasks need to run on an updated shell context if necessary. shellContext = _orchardHost.GetOrCreateShellContext(_shellSettings); using (var scope = shellContext.CreateServiceScope()) { var deferredTaskEngine = scope.ServiceProvider.GetService <IDeferredTaskEngine>(); // The recipe might have added some deferred tasks to process if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks) { var taskContext = new DeferredTaskContext(scope.ServiceProvider); await deferredTaskEngine.ExecuteTasksAsync(taskContext); } } }); } } await _eventBus.NotifyAsync <IRecipeEventHandler>(x => x.RecipeExecutedAsync(executionId, recipeDescriptor)); return(executionId); } catch (Exception) { await _eventBus.NotifyAsync <IRecipeEventHandler>(x => x.ExecutionFailedAsync(executionId, recipeDescriptor)); throw; } }
public async Task <string> SetupInternalAsync(SetupContext context) { string executionId; if (_logger.IsEnabled(LogLevel.Information)) { _logger.LogInformation("Running setup for tenant '{0}'.", _shellSettings.Name); } // Features to enable for Setup string[] hardcoded = { "OrchardCore.Commons", "OrchardCore.Features", "OrchardCore.Recipes", "OrchardCore.Scripting" }; 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.Configuration); if (string.IsNullOrEmpty(shellSettings.DatabaseProvider)) { shellSettings.DatabaseProvider = context.DatabaseProvider; shellSettings.ConnectionString = context.DatabaseConnectionString; shellSettings.TablePrefix = context.DatabaseTablePrefix; } // Creating a standalone environment based on a "minimum shell descriptor". // 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 // It is used to initialize the database before the recipe is run. var shellDescriptor = new ShellDescriptor { Features = context.EnabledFeatures.Select(id => new ShellFeature { Id = id }).ToList() }; using (var shellContext = await _shellContextFactory.CreateDescribedContextAsync(shellSettings, shellDescriptor)) { using (var scope = shellContext.EnterServiceScope()) { var store = scope.ServiceProvider.GetRequiredService <IStore>(); try { await store.InitializeAsync(); } catch (Exception e) { // Tables already exist or database was not found // The issue is that the user creation needs the tables to be present, // if the user information is not valid, the next POST will try to recreate the // tables. The tables should be rolled back if one of the steps is invalid, // unless the recipe is executing? _logger.LogError("An error occurred while initializing the datastore.", e); context.Errors.Add("DatabaseProvider", T["An error occurred while initializing the datastore: {0}", e.Message]); return(null); } // Create the "minimum shell descriptor" await scope .ServiceProvider .GetService <IShellDescriptorManager>() .UpdateShellDescriptorAsync(0, shellContext.Blueprint.Descriptor.Features, shellContext.Blueprint.Descriptor.Parameters); var deferredTaskEngine = scope.ServiceProvider.GetService <IDeferredTaskEngine>(); if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks) { var taskContext = new DeferredTaskContext(scope.ServiceProvider); await deferredTaskEngine.ExecuteTasksAsync(taskContext); } } executionId = Guid.NewGuid().ToString("n"); // Create a new scope for the recipe thread to prevent race issues with other scoped // services from the request. using (var scope = shellContext.EnterServiceScope()) { var recipeExecutor = scope.ServiceProvider.GetService <IRecipeExecutor>(); // Right now we run the recipe in the same thread, later use polling from the setup screen // to query the current execution. //await Task.Run(async () => //{ await recipeExecutor.ExecuteAsync(executionId, context.Recipe, new { SiteName = context.SiteName, AdminUsername = context.AdminUsername, AdminEmail = context.AdminEmail, AdminPassword = context.AdminPassword, DatabaseProvider = context.DatabaseProvider, DatabaseConnectionString = context.DatabaseConnectionString, DatabaseTablePrefix = context.DatabaseTablePrefix }); //}); } } // Reloading the shell context as the recipe has probably updated its features using (var shellContext = await _orchardHost.CreateShellContextAsync(shellSettings)) { using (var scope = shellContext.EnterServiceScope()) { var hasErrors = false; Action <string, string> reportError = (key, message) => { hasErrors = true; context.Errors[key] = message; }; // Invoke modules to react to the setup event var setupEventHandlers = scope.ServiceProvider.GetServices <ISetupEventHandler>(); var logger = scope.ServiceProvider.GetRequiredService <ILogger <SetupService> >(); await setupEventHandlers.InvokeAsync(x => x.Setup( context.SiteName, context.AdminUsername, context.AdminEmail, context.AdminPassword, context.DatabaseProvider, context.DatabaseConnectionString, context.DatabaseTablePrefix, reportError ), logger); if (hasErrors) { return(executionId); } var deferredTaskEngine = scope.ServiceProvider.GetService <IDeferredTaskEngine>(); if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks) { var taskContext = new DeferredTaskContext(scope.ServiceProvider); await deferredTaskEngine.ExecuteTasksAsync(taskContext); } } } // Update the shell state shellSettings.State = TenantState.Running; _orchardHost.UpdateShellSettings(shellSettings); return(executionId); }
private async Task ExecuteStepAsync(RecipeExecutionContext recipeStep) { var shellContext = _orchardHost.GetOrCreateShellContext(_shellSettings); using (var scope = shellContext.EnterServiceScope()) { if (!shellContext.IsActivated) { var tenantEvents = scope.ServiceProvider .GetServices <IModularTenantEvents>(); foreach (var tenantEvent in tenantEvents) { tenantEvent.ActivatingAsync().Wait(); } shellContext.IsActivated = true; foreach (var tenantEvent in tenantEvents) { tenantEvent.ActivatedAsync().Wait(); } } var recipeStepHandlers = scope.ServiceProvider.GetServices <IRecipeStepHandler>(); var scriptingManager = scope.ServiceProvider.GetRequiredService <IScriptingManager>(); scriptingManager.GlobalMethodProviders.Add(_environmentMethodProvider); // Substitutes the script elements by their actual values EvaluateScriptNodes(recipeStep, scriptingManager); foreach (var recipeStepHandler in recipeStepHandlers) { if (Logger.IsEnabled(LogLevel.Information)) { Logger.LogInformation("Executing recipe step '{0}'.", recipeStep.Name); } await _recipeEventHandlers.InvokeAsync(e => e.RecipeStepExecutingAsync(recipeStep), Logger); await recipeStepHandler.ExecuteAsync(recipeStep); await _recipeEventHandlers.InvokeAsync(e => e.RecipeStepExecutedAsync(recipeStep), Logger); if (Logger.IsEnabled(LogLevel.Information)) { Logger.LogInformation("Finished executing recipe step '{0}'.", recipeStep.Name); } } } // The recipe execution might have invalidated the shell by enabling new features, // so the deferred tasks need to run on an updated shell context if necessary. shellContext = _orchardHost.GetOrCreateShellContext(_shellSettings); using (var scope = shellContext.EnterServiceScope()) { var deferredTaskEngine = scope.ServiceProvider.GetService <IDeferredTaskEngine>(); // The recipe might have added some deferred tasks to process if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks) { var taskContext = new DeferredTaskContext(scope.ServiceProvider); await deferredTaskEngine.ExecuteTasksAsync(taskContext); } } }
public async Task <string> SetupInternalAsync(SetupContext context) { string executionId; if (_logger.IsEnabled(LogLevel.Information)) { _logger.LogInformation("Running setup for tenant '{0}'.", _shellSettings.Name); } // Features to enable for Setup string[] hardcoded = { "Orchard.Hosting" // shortcut for built-in features }; 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); if (string.IsNullOrEmpty(shellSettings.DatabaseProvider)) { shellSettings.DatabaseProvider = context.DatabaseProvider; shellSettings.ConnectionString = context.DatabaseConnectionString; shellSettings.TablePrefix = context.DatabaseTablePrefix; } // Creating a standalone environment based on a "minimum shell descriptor". // 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 // It is used to initialize the database before the recipe is run. using (var shellContext = _orchardHost.CreateShellContext(shellSettings)) { using (var scope = shellContext.CreateServiceScope()) { executionId = CreateTenantData(context, shellContext); var store = scope.ServiceProvider.GetRequiredService <IStore>(); await store.InitializeAsync(); // Create the "minimum shell descriptor" await scope .ServiceProvider .GetService <IShellDescriptorManager>() .UpdateShellDescriptorAsync( 0, shellContext.Blueprint.Descriptor.Features, shellContext.Blueprint.Descriptor.Parameters); // Apply all migrations for the newly initialized tenant var dataMigrationManager = scope.ServiceProvider.GetService <IDataMigrationManager>(); await dataMigrationManager.UpdateAllFeaturesAsync(); var deferredTaskEngine = scope.ServiceProvider.GetService <IDeferredTaskEngine>(); if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks) { var taskContext = new DeferredTaskContext(scope.ServiceProvider); await deferredTaskEngine.ExecuteTasksAsync(taskContext); } // Invoke modules to react to the setup event var eventBus = scope.ServiceProvider.GetService <IEventBus>(); await eventBus.NotifyAsync <ISetupEventHandler>(x => x.Setup( context.SiteName, context.AdminUsername, context.AdminEmail, context.AdminPassword, context.DatabaseProvider, context.DatabaseConnectionString, context.DatabaseTablePrefix) ); if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks) { var taskContext = new DeferredTaskContext(scope.ServiceProvider); await deferredTaskEngine.ExecuteTasksAsync(taskContext); } } } shellSettings.State = TenantState.Running; _runningShellRouterTable.Remove(shellSettings.Name); _orchardHost.UpdateShellSettings(shellSettings); return(executionId); }
public async Task Invoke(HttpContext httpContext) { // Ensure all ShellContext are loaded and available. await _shellHost.InitializeAsync(); var shellSettings = _runningShellTable.Match(httpContext); // We only serve the next request if the tenant has been resolved. if (shellSettings != null) { if (shellSettings.State == TenantState.Initializing) { httpContext.Response.Headers.Add(HeaderNames.RetryAfter, "10"); httpContext.Response.StatusCode = StatusCodes.Status503ServiceUnavailable; await httpContext.Response.WriteAsync("The requested tenant is currently initializing."); return; } var hasPendingTasks = false; // We need to get a scope and the ShellContext that created it var(scope, shellContext) = await _shellHost.GetScopeAndContextAsync(shellSettings); using (scope) { // Register the shell context as a custom feature httpContext.Features.Set(shellContext); if (!shellContext.IsActivated) { var semaphore = _semaphores.GetOrAdd(shellSettings.Name, (name) => new SemaphoreSlim(1)); await semaphore.WaitAsync(); try { // The tenant gets activated here if (!shellContext.IsActivated) { using (var activatingScope = await _shellHost.GetScopeAsync(shellSettings)) { var tenantEvents = activatingScope.ServiceProvider.GetServices <IModularTenantEvents>(); foreach (var tenantEvent in tenantEvents) { await tenantEvent.ActivatingAsync(); } foreach (var tenantEvent in tenantEvents.Reverse()) { await tenantEvent.ActivatedAsync(); } } shellContext.IsActivated = true; } } finally { semaphore.Release(); _semaphores.TryRemove(shellSettings.Name, out semaphore); } } await _next.Invoke(httpContext); var deferredTaskEngine = scope.ServiceProvider.GetService <IDeferredTaskEngine>(); hasPendingTasks = deferredTaskEngine?.HasPendingTasks ?? false; } // Create a new scope only if there are pending tasks if (hasPendingTasks) { using (var pendingScope = await _shellHost.GetScopeAsync(shellSettings)) { if (pendingScope != null) { var deferredTaskEngine = pendingScope.ServiceProvider.GetService <IDeferredTaskEngine>(); var context = new DeferredTaskContext(pendingScope.ServiceProvider); await deferredTaskEngine.ExecuteTasksAsync(context); } } } } }
public async Task <string> SetupInternalAsync(SetupContext context) { string executionId; if (_logger.IsEnabled(LogLevel.Information)) { _logger.LogInformation("Running setup for tenant '{0}'.", _shellSettings.Name); } // Features to enable for Setup string[] hardcoded = { "Orchard.Hosting" // shortcut for built-in features }; 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); if (string.IsNullOrEmpty(shellSettings.DatabaseProvider)) { shellSettings.DatabaseProvider = context.DatabaseProvider; shellSettings.ConnectionString = context.DatabaseConnectionString; shellSettings.TablePrefix = context.DatabaseTablePrefix; } // Creating a standalone environment based on a "minimum shell descriptor". // 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 // It is used to initialize the database before the recipe is run. using (var shellContext = _orchardHost.CreateShellContext(shellSettings)) { using (var scope = shellContext.CreateServiceScope()) { executionId = CreateTenantData(context, shellContext); var store = scope.ServiceProvider.GetRequiredService <IStore>(); try { await store.InitializeAsync(); } catch { // Tables already exist or database was not found // The issue is that the user creation needs the tables to be present, // if the user information is not valid, the next POST will try to recreate the // tables. The tables should be rollbacked if one of the steps is invalid, // unless the recipe is executing? } // Create the "minimum shell descriptor" await scope .ServiceProvider .GetService <IShellDescriptorManager>() .UpdateShellDescriptorAsync( 0, shellContext.Blueprint.Descriptor.Features, shellContext.Blueprint.Descriptor.Parameters); // Apply all migrations for the newly initialized tenant var dataMigrationManager = scope.ServiceProvider.GetService <IDataMigrationManager>(); await dataMigrationManager.UpdateAllFeaturesAsync(); var deferredTaskEngine = scope.ServiceProvider.GetService <IDeferredTaskEngine>(); if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks) { var taskContext = new DeferredTaskContext(scope.ServiceProvider); await deferredTaskEngine.ExecuteTasksAsync(taskContext); } bool hasErrors = false; Action <string, string> reportError = (key, message) => { hasErrors = true; context.Errors[key] = message; }; // Invoke modules to react to the setup event var eventBus = scope.ServiceProvider.GetService <IEventBus>(); await eventBus.NotifyAsync <ISetupEventHandler>(x => x.Setup( context.SiteName, context.AdminUsername, context.AdminEmail, context.AdminPassword, context.DatabaseProvider, context.DatabaseConnectionString, context.DatabaseTablePrefix, reportError )); if (hasErrors) { // TODO: check why the tables creation is not reverted var session = scope.ServiceProvider.GetService <YesSql.Core.Services.ISession>(); session.Cancel(); return(executionId); } if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks) { var taskContext = new DeferredTaskContext(scope.ServiceProvider); await deferredTaskEngine.ExecuteTasksAsync(taskContext); } } } shellSettings.State = TenantState.Running; _orchardHost.UpdateShellSettings(shellSettings); return(executionId); }
public async Task <string> ExecuteAsync(string executionId, RecipeDescriptor recipeDescriptor) { await _eventBus.NotifyAsync <IRecipeEventHandler>(x => x.RecipeExecutingAsync(executionId, recipeDescriptor)); try { RecipeResult result = new RecipeResult { ExecutionId = executionId }; List <RecipeStepResult> stepResults = new List <RecipeStepResult>(); using (StreamReader file = File.OpenText(recipeDescriptor.RecipeFileInfo.PhysicalPath)) { using (var reader = new JsonTextReader(file)) { VariablesMethodProvider variablesMethodProvider = null; // Go to Steps, then iterate. while (reader.Read()) { if (reader.Path == "variables") { reader.Read(); var variables = JObject.Load(reader); variablesMethodProvider = new VariablesMethodProvider(variables); } if (reader.Path == "steps" && reader.TokenType == JsonToken.StartArray) { int stepId = 0; while (reader.Read() && reader.Depth > 1) { if (reader.Depth == 2) { var child = JToken.Load(reader); var recipeStep = new RecipeStepDescriptor { Id = (stepId++).ToString(), Name = child.Value <string>("name"), Step = child }; var shellContext = _orchardHost.GetOrCreateShellContext(_shellSettings); using (var scope = shellContext.CreateServiceScope()) { if (!shellContext.IsActivated) { var eventBus = scope.ServiceProvider.GetService <IEventBus>(); await eventBus.NotifyAsync <IOrchardShellEvents>(x => x.ActivatingAsync()); await eventBus.NotifyAsync <IOrchardShellEvents>(x => x.ActivatedAsync()); shellContext.IsActivated = true; } var recipeStepExecutor = scope.ServiceProvider.GetRequiredService <IRecipeStepExecutor>(); var scriptingManager = scope.ServiceProvider.GetRequiredService <IScriptingManager>(); if (variablesMethodProvider != null) { variablesMethodProvider.ScriptingManager = scriptingManager; scriptingManager.GlobalMethodProviders.Add(variablesMethodProvider); } if (_applicationLifetime.ApplicationStopping.IsCancellationRequested) { throw new OrchardException(T["Recipe cancelled, application is restarting"]); } EvaluateJsonTree(scriptingManager, recipeStep.Step); await recipeStepExecutor.ExecuteAsync(executionId, recipeStep); } // The recipe execution might have invalidated the shell by enabling new features, // so the deferred tasks need to run on an updated shell context if necessary. shellContext = _orchardHost.GetOrCreateShellContext(_shellSettings); using (var scope = shellContext.CreateServiceScope()) { var recipeStepExecutor = scope.ServiceProvider.GetRequiredService <IRecipeStepExecutor>(); var deferredTaskEngine = scope.ServiceProvider.GetService <IDeferredTaskEngine>(); // The recipe might have added some deferred tasks to process if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks) { var taskContext = new DeferredTaskContext(scope.ServiceProvider); await deferredTaskEngine.ExecuteTasksAsync(taskContext); } } } } } } } } await _eventBus.NotifyAsync <IRecipeEventHandler>(x => x.RecipeExecutedAsync(executionId, recipeDescriptor)); return(executionId); } catch (Exception) { await _eventBus.NotifyAsync <IRecipeEventHandler>(x => x.ExecutionFailedAsync(executionId, recipeDescriptor)); throw; } }
private async Task ExecuteStepAsync(RecipeExecutionContext recipeStep) { IServiceScope scope; ShellContext shellContext; IServiceProvider serviceProvider; if (recipeStep.RecipeDescriptor.RequireNewScope) { (scope, shellContext) = await _shellHost.GetScopeAndContextAsync(_shellSettings); serviceProvider = scope.ServiceProvider; } else { (scope, shellContext) = (null, null); serviceProvider = _httpContextAccessor.HttpContext.RequestServices; } using (scope) { if (recipeStep.RecipeDescriptor.RequireNewScope && !shellContext.IsActivated) { using (var activatingScope = shellContext.CreateScope()) { var tenantEvents = activatingScope.ServiceProvider.GetServices <IModularTenantEvents>(); foreach (var tenantEvent in tenantEvents) { await tenantEvent.ActivatingAsync(); } foreach (var tenantEvent in tenantEvents.Reverse()) { await tenantEvent.ActivatedAsync(); } } shellContext.IsActivated = true; } var recipeStepHandlers = serviceProvider.GetServices <IRecipeStepHandler>(); var scriptingManager = serviceProvider.GetRequiredService <IScriptingManager>(); scriptingManager.GlobalMethodProviders.Add(_environmentMethodProvider); // Substitutes the script elements by their actual values EvaluateScriptNodes(recipeStep, scriptingManager); foreach (var recipeStepHandler in recipeStepHandlers) { if (Logger.IsEnabled(LogLevel.Information)) { Logger.LogInformation("Executing recipe step '{RecipeName}'.", recipeStep.Name); } await _recipeEventHandlers.InvokeAsync(e => e.RecipeStepExecutingAsync(recipeStep), Logger); await recipeStepHandler.ExecuteAsync(recipeStep); await _recipeEventHandlers.InvokeAsync(e => e.RecipeStepExecutedAsync(recipeStep), Logger); if (Logger.IsEnabled(LogLevel.Information)) { Logger.LogInformation("Finished executing recipe step '{RecipeName}'.", recipeStep.Name); } } } // E.g if we run migrations defined in a recipe. if (!recipeStep.RecipeDescriptor.RequireNewScope) { return; } // The recipe execution might have invalidated the shell by enabling new features, // so the deferred tasks need to run on an updated shell context if necessary. using (var localScope = await _shellHost.GetScopeAsync(_shellSettings)) { var deferredTaskEngine = localScope.ServiceProvider.GetService <IDeferredTaskEngine>(); // The recipe might have added some deferred tasks to process if (deferredTaskEngine != null && deferredTaskEngine.HasPendingTasks) { var taskContext = new DeferredTaskContext(localScope.ServiceProvider); await deferredTaskEngine.ExecuteTasksAsync(taskContext); } } }
public async Task Invoke(HttpContext httpContext) { // Ensure all tenants are loaded and available. _platoHost.Initialize(); // Get ShellSettings for current tenant var shellSettings = _runningShellTable.Match(httpContext); // Register shell settings as a custom feature httpContext.Features[typeof(ShellSettings)] = shellSettings; // Only serve the next request if the tenant has been resolved. if (shellSettings != null) { var hasDeferredTasks = false; var shellContext = _platoHost.GetOrCreateShellContext(shellSettings); using (var scope = shellContext.CreateServiceScope()) { // Mimic the services provided by our host and tenant httpContext.RequestServices = scope.ServiceProvider; // Ensure the tenant is activated if (!shellContext.IsActivated) { lock (shellSettings) { // Activate the tenant if (!shellContext.IsActivated) { // BuildPipeline ensures we always rebuild routes for new tenants httpContext.Items["BuildPipeline"] = true; shellContext.IsActivated = true; // Activate message broker subscriptions var subscribers = scope.ServiceProvider.GetServices <IBrokerSubscriber>(); foreach (var subscriber in subscribers) { subscriber.Subscribe(); } // Activate background tasks var backgroundTaskManager = scope.ServiceProvider.GetService <IBackgroundTaskManager>(); backgroundTaskManager?.StartTasks(); } } } // Invoke the next middleware in pipeline await _next.Invoke(httpContext); // At the end determine if we need to process deferred tasks var deferredTaskManager = scope.ServiceProvider.GetService <IDeferredTaskManager>(); hasDeferredTasks = deferredTaskManager?.Process(httpContext) ?? false; } // Execute deferred tasks (if any) within a new scope // once the entire response has been sent to the client. if (hasDeferredTasks) { httpContext.Response.OnCompleted(async() => { using (var scope = shellContext.CreateServiceScope()) { var context = new DeferredTaskContext(scope.ServiceProvider); var deferredTaskManager = scope.ServiceProvider.GetService <IDeferredTaskManager>(); await deferredTaskManager.ExecuteTaskAsync(context); } }); } } }
public async Task Invoke(HttpContext httpContext) { // Ensure all tenants are loaded and available. _platoHost.Initialize(); // Attempt to match the request to a specific tenant var shellSettings = _runningShellTable.Match(httpContext, _logger); // Register shell settings on current context httpContext.SetShellSettings(shellSettings); // Only serve the next request if the tenant has been resolved. if (shellSettings != null) { // If the shell is disabled or invalid redirect to host 404 page if (shellSettings.State == TenantState.Disabled || shellSettings.State == TenantState.Invalid) { httpContext.Response.StatusCode = StatusCodes.Status302Found; httpContext.Response.Headers.Add(HeaderNames.Location, StatusCodePagePaths.NotFound); return; } var hasDeferredTasks = false; var shellContext = _platoHost.GetOrCreateShellContext(shellSettings); using (var scope = shellContext.CreateServiceScope()) { // Mimic the services provided by our host and tenant httpContext.RequestServices = scope.ServiceProvider; // Ensure the tenant is activated if (!shellContext.IsActivated) { lock (shellSettings) { // Activate the tenant if (!shellContext.IsActivated) { // BuildPipeline ensures we always rebuild routes for new tenants httpContext.Items["BuildPipeline"] = true; shellContext.IsActivated = true; // Activate message broker subscriptions var subscribers = scope.ServiceProvider.GetServices <IBrokerSubscriber>(); foreach (var subscriber in subscribers) { subscriber.Subscribe(); } // Activate background tasks var backgroundTaskManager = scope.ServiceProvider.GetService <IBackgroundTaskManager>(); backgroundTaskManager?.StartTasks(scope.ServiceProvider); } } } // Invoke the next middle ware in pipeline await _next.Invoke(httpContext); // At the end determine if we need to process deferred tasks var deferredTaskManager = scope.ServiceProvider.GetService <IDeferredTaskManager>(); hasDeferredTasks = deferredTaskManager?.Process(httpContext) ?? false; } // Execute deferred tasks (if any) within a new scope // once the entire response has been sent to the client. if (hasDeferredTasks) { httpContext.Response.OnCompleted(async() => { using (var scope = shellContext.CreateServiceScope()) { var context = new DeferredTaskContext(scope.ServiceProvider); var deferredTaskManager = scope.ServiceProvider.GetService <IDeferredTaskManager>(); await deferredTaskManager.ExecuteTaskAsync(context); } }); } } }
public async Task Invoke(HttpContext httpContext) { // Ensure all ShellContext are loaded and available. _orchardHost.Initialize(); var shellSettings = _runningShellTable.Match(httpContext); // We only serve the next request if the tenant has been resolved. if (shellSettings != null) { var shellContext = _orchardHost.GetOrCreateShellContext(shellSettings); var hasPendingTasks = false; using (var scope = shellContext.EnterServiceScope()) { // Register the shell context as a custom feature. httpContext.Features.Set(shellContext); if (!shellContext.IsActivated) { var semaphore = _semaphores.GetOrAdd(shellSettings.Name, (name) => new SemaphoreSlim(1)); await semaphore.WaitAsync(); try { // The tenant gets activated here if (!shellContext.IsActivated) { using (var activatingScope = shellContext.EnterServiceScope()) { var tenantEvents = activatingScope.ServiceProvider.GetServices <IModularTenantEvents>(); foreach (var tenantEvent in tenantEvents) { await tenantEvent.ActivatingAsync(); } httpContext.Items["BuildPipeline"] = true; foreach (var tenantEvent in tenantEvents.Reverse()) { await tenantEvent.ActivatedAsync(); } } shellContext.IsActivated = true; } } finally { semaphore.Release(); _semaphores.TryRemove(shellSettings.Name, out semaphore); } } await _next.Invoke(httpContext); var deferredTaskEngine = scope.ServiceProvider.GetService <IDeferredTaskEngine>(); hasPendingTasks = deferredTaskEngine?.HasPendingTasks ?? false; } // Create a new scope only if there are pending tasks if (hasPendingTasks) { shellContext = _orchardHost.GetOrCreateShellContext(shellSettings); using (var scope = shellContext.EnterServiceScope()) { var deferredTaskEngine = scope.ServiceProvider.GetService <IDeferredTaskEngine>(); var context = new DeferredTaskContext(scope.ServiceProvider); await deferredTaskEngine.ExecuteTasksAsync(context); } } } }
public async Task Invoke(HttpContext httpContext) { _host.Initialize(); var engineSettings = _runningEngineTable.Match(httpContext); if (engineSettings != null) { var engineContext = _host.GetOrCreateEngineContext(engineSettings); var hasPendingTasks = false; using (var scope = engineContext.EnterServiceScope()) { httpContext.Features.Set(engineContext); // 把租户前缀加到 cookie 里 httpContext.Response.Cookies.Append("tenant_prefix", engineSettings.RequestUrlPrefix ?? ""); httpContext.Response.Cookies.Append("tenant_host", engineSettings.RequestUrlHost ?? ""); if (!engineContext.IsActivated) { var semaphore = _semaphores.GetOrAdd(engineSettings.Name, (name) => new SemaphoreSlim(1)); await semaphore.WaitAsync(); try { if (!engineContext.IsActivated) { using (var activatingScope = engineContext.EnterServiceScope()) { var tenantEvents = activatingScope.ServiceProvider.GetServices <IModuleTenantEvents>(); foreach (var tenantEvent in tenantEvents) { await tenantEvent.ActivatingAsync(); } httpContext.Items["BuildPipeline"] = true; foreach (var tenantEvent in tenantEvents.Reverse()) { await tenantEvent.ActivatedAsync(); } } engineContext.IsActivated = true; } } finally { semaphore.Release(); _semaphores.TryRemove(engineSettings.Name, out semaphore); } } await _next.Invoke(httpContext); var deferredTaskEngine = scope.ServiceProvider.GetService <IDeferredTaskEngine>(); hasPendingTasks = deferredTaskEngine?.HasPendingTasks ?? false; } if (hasPendingTasks) { engineContext = _host.GetOrCreateEngineContext(engineSettings); using (var scope = engineContext.EnterServiceScope()) { var deferredTaskEngine = scope.ServiceProvider.GetService <IDeferredTaskEngine>(); var context = new DeferredTaskContext(scope.ServiceProvider); await deferredTaskEngine.ExecuteTasksAsync(context); } } } }
private static async Task FlushAsync(DeferredTaskContext context, IEnumerable <IndexingTask> tasks) { var localQueue = new List <IndexingTask>(tasks); var serviceProvider = context.ServiceProvider; var dbConnectionAccessor = serviceProvider.GetService <IDbConnectionAccessor>(); var shellSettings = serviceProvider.GetService <ShellSettings>(); var logger = serviceProvider.GetService <ILogger <IndexingTaskManager> >(); var tablePrefix = shellSettings["TablePrefix"]; if (!String.IsNullOrEmpty(tablePrefix)) { tablePrefix += '_'; } var contentItemIds = new HashSet <string>(); // Remove duplicate tasks, only keep the last one for (var i = localQueue.Count; i > 0; i--) { var task = localQueue[i - 1]; if (contentItemIds.Contains(task.ContentItemId)) { localQueue.RemoveAt(i - 1); } else { contentItemIds.Add(task.ContentItemId); } } // At this point, content items ids should be unique in localQueue var ids = localQueue.Select(x => x.ContentItemId).ToArray(); var table = $"{tablePrefix}{nameof(IndexingTask)}"; using (var connection = dbConnectionAccessor.CreateConnection()) { await connection.OpenAsync(); using (var transaction = connection.BeginTransaction()) { var dialect = SqlDialectFactory.For(transaction.Connection); try { if (logger.IsEnabled(LogLevel.Debug)) { logger.LogDebug($"Updating indexing tasks: {String.Join(", ", tasks.Select(x => x.ContentItemId))}"); } // Page delete statements to prevent the limits from IN sql statements var pageSize = 100; var deleteCmd = $"delete from {dialect.QuoteForTableName(table)} where {dialect.QuoteForColumnName("ContentItemId")} {dialect.InOperator("@Ids")};"; do { var pageOfIds = ids.Take(pageSize).ToArray(); if (pageOfIds.Any()) { await transaction.Connection.ExecuteAsync(deleteCmd, new { Ids = pageOfIds }, transaction); ids = ids.Skip(pageSize).ToArray(); } } while (ids.Any()); var insertCmd = $"insert into {dialect.QuoteForTableName(table)} ({dialect.QuoteForColumnName("CreatedUtc")}, {dialect.QuoteForColumnName("ContentItemId")}, {dialect.QuoteForColumnName("Type")}) values (@CreatedUtc, @ContentItemId, @Type);"; await transaction.Connection.ExecuteAsync(insertCmd, localQueue, transaction); transaction.Commit(); } catch (Exception e) { logger.LogError(e, "An error occured while updating indexing tasks"); throw; } } } }