Пример #1
0
        private static async Task FlushAsync(ShellScope scope, IEnumerable <IndexingTask> tasks)
        {
            var localQueue = new List <IndexingTask>(tasks);

            var serviceProvider = scope.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)
                    {
                        transaction.Rollback();
                        logger.LogError(e, "An error occurred while updating indexing tasks");

                        throw;
                    }
                }
            }
        }
        /// <summary>
        /// Adds tenant level data access services.
        /// </summary>
        public static OrchardCoreBuilder AddDataAccess(this OrchardCoreBuilder builder)
        {
            builder.ConfigureServices(services =>
            {
                services.AddScoped <IDataMigrationManager, DataMigrationManager>();
                services.AddScoped <IModularTenantEvents, AutomaticDataMigrations>();

                // Adding supported databases
                services.TryAddDataProvider(name: "Sql Server", value: "SqlConnection", hasConnectionString: true, sampleConnectionString: "Server=localhost;Database=Orchard;User Id=username;Password=password", hasTablePrefix: true, isDefault: false);
                services.TryAddDataProvider(name: "Sqlite", value: "Sqlite", hasConnectionString: false, hasTablePrefix: false, isDefault: true);
                services.TryAddDataProvider(name: "MySql", value: "MySql", hasConnectionString: true, sampleConnectionString: "Server=localhost;Database=Orchard;Uid=username;Pwd=password", hasTablePrefix: true, isDefault: false);
                services.TryAddDataProvider(name: "Postgres", value: "Postgres", hasConnectionString: true, sampleConnectionString: "Server=localhost;Port=5432;Database=Orchard;User Id=username;Password=password", hasTablePrefix: true, isDefault: false);

                // Configuring data access

                services.AddSingleton <IStore>(sp =>
                {
                    var shellSettings = sp.GetService <ShellSettings>();

                    // Before the setup a 'DatabaseProvider' may be configured without a required 'ConnectionString'.
                    if (shellSettings.State == TenantState.Uninitialized || shellSettings["DatabaseProvider"] == null)
                    {
                        return(null);
                    }

                    IConfiguration storeConfiguration = new YesSql.Configuration();

                    switch (shellSettings["DatabaseProvider"])
                    {
                    case "SqlConnection":
                        storeConfiguration
                        .UseSqlServer(shellSettings["ConnectionString"], IsolationLevel.ReadUncommitted)
                        .UseBlockIdGenerator();
                        break;

                    case "Sqlite":
                        var shellOptions   = sp.GetService <IOptions <ShellOptions> >();
                        var option         = shellOptions.Value;
                        var databaseFolder = Path.Combine(option.ShellsApplicationDataPath, option.ShellsContainerName, shellSettings.Name);
                        var databaseFile   = Path.Combine(databaseFolder, "yessql.db");
                        Directory.CreateDirectory(databaseFolder);
                        storeConfiguration
                        .UseSqLite($"Data Source={databaseFile};Cache=Shared", IsolationLevel.ReadUncommitted)
                        .UseDefaultIdGenerator();
                        break;

                    case "MySql":
                        storeConfiguration
                        .UseMySql(shellSettings["ConnectionString"], IsolationLevel.ReadUncommitted)
                        .UseBlockIdGenerator();
                        break;

                    case "Postgres":
                        storeConfiguration
                        .UsePostgreSql(shellSettings["ConnectionString"], IsolationLevel.ReadUncommitted)
                        .UseBlockIdGenerator();
                        break;

                    default:
                        throw new ArgumentException("Unknown database provider: " + shellSettings["DatabaseProvider"]);
                    }

                    if (!string.IsNullOrWhiteSpace(shellSettings["TablePrefix"]))
                    {
                        storeConfiguration = storeConfiguration.SetTablePrefix(shellSettings["TablePrefix"] + "_");
                    }

                    var store   = StoreFactory.CreateAsync(storeConfiguration).GetAwaiter().GetResult();
                    var indexes = sp.GetServices <IIndexProvider>();

                    store.RegisterIndexes(indexes);

                    return(store);
                });

                services.AddScoped(sp =>
                {
                    var store = sp.GetService <IStore>();

                    if (store == null)
                    {
                        return(null);
                    }

                    var session = store.CreateSession();

                    var scopedServices = sp.GetServices <IScopedIndexProvider>();

                    session.RegisterIndexes(scopedServices.ToArray());

                    ShellScope.RegisterBeforeDispose(scope =>
                    {
                        return(session.CommitAsync());
                    });

                    return(session);
                });

                services.AddTransient <IDbConnectionAccessor, DbConnectionAccessor>();
            });

            return(builder);
        }
Пример #3
0
        internal void ReleaseInternal(ReleaseMode mode = ReleaseMode.Normal)
        {
            if (_released)
            {
                // Prevent infinite loops with circular dependencies
                return;
            }

            // A disabled shell still in use will be released by its last scope, as checked at the host level.
            if (mode == ReleaseMode.FromDependency && Settings.State == TenantState.Disabled && _refCount != 0)
            {
                return;
            }

            // When a tenant is changed and should be restarted, its shell context is replaced with a new one,
            // so that new request can't use it anymore. However some existing request might still be running and try to
            // resolve or use its services. We then call this method to count the remaining references and dispose it
            // when the number reached zero.

            ShellScope scope = null;

            lock (_synLock)
            {
                if (_released)
                {
                    return;
                }

                if (_dependents != null)
                {
                    foreach (var dependent in _dependents)
                    {
                        if (dependent.TryGetTarget(out var shellContext))
                        {
                            shellContext.ReleaseFromDependency();
                        }
                    }
                }

                if (mode != ReleaseMode.FromLastScope && ServiceProvider != null)
                {
                    // Before marking the shell as released, we create a new scope that will manage the shell state,
                    // so that we always use the same shell scope logic to check if the reference counter reached 0.
                    scope = new ShellScope(this);
                }

                _released = true;
            }

            if (mode == ReleaseMode.FromLastScope)
            {
                return;
            }

            if (scope != null)
            {
                // Use this scope to manage the shell state as usual.
                scope.TerminateShellAsync().GetAwaiter().GetResult();
                return;
            }

            Dispose();
        }
        public async Task UpdateAtomicAsync(Func <Task <TDocument> > updateAsync, Func <TDocument, Task> afterUpdateAsync = null)
        {
            if (_isDistributed)
            {
                try
                {
                    _ = await _distributedCache.GetStringAsync(_options.CacheIdKey);
                }
                catch
                {
                    await DocumentStore.CancelAsync();

                    throw new InvalidOperationException($"Can't update the '{typeof(TDocument).Name}' if not able to access the distributed cache");
                }
            }

            var delegates = ShellScope.GetOrCreateFeature <UpdateDelegates>();

            if (delegates.UpdateDelegateAsync == null ||
                !delegates.UpdateDelegateAsync.GetInvocationList().Contains(updateAsync))
            {
                delegates.UpdateDelegateAsync += () => updateAsync();
            }

            if (afterUpdateAsync != null &&
                (delegates.AfterUpdateDelegateAsync == null ||
                 !delegates.AfterUpdateDelegateAsync.GetInvocationList().Contains(afterUpdateAsync)))
            {
                delegates.AfterUpdateDelegateAsync += document => afterUpdateAsync(document);
            }

            DocumentStore.AfterCommitSuccess <TDocument>(async() =>
            {
                (var locker, var locked) = await _distributedLock.TryAcquireLockAsync(
                    _options.CacheKey + "_LOCK",
                    TimeSpan.FromMilliseconds(_options.LockTimeout),
                    TimeSpan.FromMilliseconds(_options.LockExpiration));

                if (!locked)
                {
                    return;
                }

                await using var acquiredLock = locker;

                TDocument document = null;
                foreach (var d in delegates.UpdateDelegateAsync.GetInvocationList())
                {
                    document = await((UpdateDelegate)d)();
                }

                document.Identifier ??= IdGenerator.GenerateId();

                await SetInternalAsync(document);

                if (delegates.AfterUpdateDelegateAsync != null)
                {
                    foreach (var d in delegates.AfterUpdateDelegateAsync.GetInvocationList())
                    {
                        await((AfterUpdateDelegate)d)(document);
                    }
                }
            });
        }
Пример #5
0
 /// <summary>
 /// Adds a Signal (if not already present) to be sent at the end of the shell scope.
 /// </summary>
 public static void DeferredSignalToken(this ISignal signal, string key)
 {
     ShellScope.AddDeferredSignal(key);
 }
        public async Task <(IEnumerable <IFeatureInfo>, IEnumerable <IFeatureInfo>)> UpdateFeaturesAsync(ShellDescriptor shellDescriptor,
                                                                                                         IEnumerable <IFeatureInfo> featuresToDisable, IEnumerable <IFeatureInfo> featuresToEnable, bool force)
        {
            var featureEventHandlers = ShellScope.Services.GetServices <IFeatureEventHandler>();

            var enabledFeatureIds = _extensionManager.GetFeatures()
                                    .Where(f => shellDescriptor.Features.Any(sf => sf.Id == f.Id))
                                    .Select(f => f.Id)
                                    .ToHashSet();

            var installedFeatureIds = enabledFeatureIds
                                      .Concat(shellDescriptor.Installed.Select(sf => sf.Id))
                                      .ToHashSet();

            var alwaysEnabledIds = _alwaysEnabledFeatures.Select(sf => sf.Id).ToArray();

            var allFeaturesToDisable = featuresToDisable
                                       .Where(f => !alwaysEnabledIds.Contains(f.Id))
                                       .SelectMany(feature => GetFeaturesToDisable(feature, enabledFeatureIds, force))
                                       .Distinct()
                                       .Reverse()
                                       .ToList();

            foreach (var feature in allFeaturesToDisable)
            {
                enabledFeatureIds.Remove(feature.Id);

                if (_logger.IsEnabled(LogLevel.Information))
                {
                    _logger.LogInformation("Disabling feature '{FeatureName}'", feature.Id);
                }

                await featureEventHandlers.InvokeAsync((handler, featureInfo) => handler.DisablingAsync(featureInfo), feature, _logger);
            }

            var allFeaturesToEnable = featuresToEnable
                                      .SelectMany(feature => GetFeaturesToEnable(feature, enabledFeatureIds, force))
                                      .Distinct()
                                      .ToList();

            foreach (var feature in allFeaturesToEnable)
            {
                if (_logger.IsEnabled(LogLevel.Information))
                {
                    _logger.LogInformation("Enabling feature '{FeatureName}'", feature.Id);
                }

                await featureEventHandlers.InvokeAsync((handler, featureInfo) => handler.EnablingAsync(featureInfo), feature, _logger);
            }

            var allFeaturesToInstall = allFeaturesToEnable
                                       .Where(f => !installedFeatureIds.Contains(f.Id))
                                       .ToList();

            foreach (var feature in allFeaturesToInstall)
            {
                if (_logger.IsEnabled(LogLevel.Information))
                {
                    _logger.LogInformation("Installing feature '{FeatureName}'", feature.Id);
                }

                await featureEventHandlers.InvokeAsync((handler, featureInfo) => handler.InstallingAsync(featureInfo), feature, _logger);
            }

            if (allFeaturesToEnable.Count > 0)
            {
                enabledFeatureIds.UnionWith(allFeaturesToEnable.Select(f => f.Id));
            }

            if (allFeaturesToDisable.Count > 0 || allFeaturesToEnable.Count > 0)
            {
                await _shellDescriptorManager.UpdateShellDescriptorAsync(
                    shellDescriptor.SerialNumber,
                    enabledFeatureIds.Select(id => new ShellFeature(id)).ToArray());

                ShellScope.AddDeferredTask(async scope =>
                {
                    var featureEventHandlers = scope.ServiceProvider.GetServices <IFeatureEventHandler>();
                    var logger = scope.ServiceProvider.GetRequiredService <ILogger <ShellFeaturesManager> >();

                    foreach (var feature in allFeaturesToInstall)
                    {
                        if (logger.IsEnabled(LogLevel.Information))
                        {
                            logger.LogInformation("Feature '{FeatureName}' was installed", feature.Id);
                        }

                        await featureEventHandlers.InvokeAsync((handler, featureInfo) => handler.InstalledAsync(featureInfo), feature, logger);
                    }

                    foreach (var feature in allFeaturesToEnable)
                    {
                        if (logger.IsEnabled(LogLevel.Information))
                        {
                            logger.LogInformation("Feature '{FeatureName}' was enabled", feature.Id);
                        }

                        await featureEventHandlers.InvokeAsync((handler, featureInfo) => handler.EnabledAsync(featureInfo), feature, logger);
                    }

                    foreach (var feature in allFeaturesToDisable)
                    {
                        if (logger.IsEnabled(LogLevel.Information))
                        {
                            logger.LogInformation("Feature '{FeatureName}' was disabled", feature.Id);
                        }

                        await featureEventHandlers.InvokeAsync((handler, featureInfo) => handler.DisabledAsync(featureInfo), feature, logger);
                    }
                });
            }

            return(allFeaturesToDisable, allFeaturesToEnable);
        }
Пример #7
0
 public void AfterCommitSuccess <T>(DocumentStoreCommitSuccessDelegate afterCommit)
 => ShellScope.RegisterBeforeDispose(scope => afterCommit());
Пример #8
0
 public BackgroundTaskEventContext(string name, ShellScope scope)
 {
     Name     = name;
     Tenant   = scope.ShellContext.Settings.Name;
     Services = scope.ServiceProvider;
 }