/// <summary> /// Initializes a new instance of the <see cref="CreatedPackageSchemaRepository" /> class. /// </summary> public CreatedPackageSchemaRepository( IUmbracoDatabaseFactory umbracoDatabaseFactory, IHostingEnvironment hostingEnvironment, IOptions <GlobalSettings> globalSettings, FileSystems fileSystems, IEntityXmlSerializer serializer, IDataTypeService dataTypeService, ILocalizationService localizationService, IFileService fileService, IMediaService mediaService, IMediaTypeService mediaTypeService, IContentService contentService, MediaFileManager mediaFileManager, IMacroService macroService, IContentTypeService contentTypeService, string?mediaFolderPath = null, string?tempFolderPath = null) { _umbracoDatabase = umbracoDatabaseFactory.CreateDatabase(); _hostingEnvironment = hostingEnvironment; _fileSystems = fileSystems; _serializer = serializer; _dataTypeService = dataTypeService; _localizationService = localizationService; _fileService = fileService; _mediaService = mediaService; _mediaTypeService = mediaTypeService; _contentService = contentService; _mediaFileManager = mediaFileManager; _macroService = macroService; _contentTypeService = contentTypeService; _xmlParser = new PackageDefinitionXmlParser(); _createdPackagesFolderPath = mediaFolderPath ?? Constants.SystemDirectories.CreatedPackages; _tempFolderPath = tempFolderPath ?? Constants.SystemDirectories.TempData + "/PackageFiles"; }
/// <summary> /// Initializes a new instance of the <see cref="MigrationContext"/> class. /// </summary> public MigrationContext(MigrationPlan plan, IUmbracoDatabase?database, ILogger <MigrationContext> logger) { Plan = plan; Database = database ?? throw new ArgumentNullException(nameof(database)); Logger = logger ?? throw new ArgumentNullException(nameof(logger)); _postMigrations.AddRange(plan.PostMigrationTypes); }
private void ObtainWriteLock() { IUmbracoDatabase?db = _parent._scopeAccessor.Value.AmbientScope?.Database; if (db is null) { throw new PanicException("Could not find a database"); } if (!db.InTransaction) { throw new InvalidOperationException( "SqlServerDistributedLockingMechanism requires a transaction to function."); } if (db.Transaction.IsolationLevel < IsolationLevel.ReadCommitted) { throw new InvalidOperationException( "A transaction with minimum ReadCommitted isolation level is required."); } const string query = @"UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id"; db.Execute("SET LOCK_TIMEOUT " + _timeout.TotalMilliseconds + ";"); var i = db.Execute(query, new { id = LockId }); if (i == 0) { // ensure we are actually locking! throw new ArgumentException($"LockObject with id={LockId} does not exist."); } }
public async Task <bool> AcquireLockAsync(int millisecondsTimeout) { if (!_dbFactory.Configured) { // if we aren't configured then we're in an install state, in which case we have no choice but to assume we can acquire return(true); } _logger.LogDebug("Acquiring lock..."); var tempId = Guid.NewGuid().ToString(); IUmbracoDatabase?db = null; try { db = _dbFactory.CreateDatabase(); _hasTable = db !.HasTable(Cms.Core.Constants.DatabaseSchema.Tables.KeyValue); if (!_hasTable) { _logger.LogDebug("The DB does not contain the required table so we must be in an install state. We have no choice but to assume we can acquire."); _acquireWhenTablesNotAvailable = true; return(true); } db !.BeginTransaction(IsolationLevel.Serializable); var result = InsertLockRecord(tempId, db); //we change the row to a random Id to signal other MainDom to shutdown if (result == RecordPersistenceType.Insert) { // if we've inserted, then there was no MainDom so we can instantly acquire InsertLockRecord(_lockId, db); // so update with our appdomain id _logger.LogDebug("Acquired with ID {LockId}", _lockId); return(true); } // if we've updated, this means there is an active MainDom, now we need to wait to // for the current MainDom to shutdown which also requires releasing our write lock } catch (Exception ex) { // unexpected _logger.LogError(ex, "Unexpected error, cannot acquire MainDom"); _errorDuringAcquiring = true; return(false); } finally { db?.CompleteTransaction(); db?.Dispose(); } return(await WaitForExistingAsync(tempId, millisecondsTimeout)); }
// Can always obtain a read lock (snapshot isolation in wal mode) // Mostly no-op just check that we didn't end up ReadUncommitted for real. private void ObtainReadLock() { IUmbracoDatabase?db = _parent._scopeAccessor.Value.AmbientScope?.Database; if (db is null) { throw new PanicException("no database was found"); } if (!db.InTransaction) { throw new InvalidOperationException("SqliteDistributedLockingMechanism requires a transaction to function."); } }
public bool HasSavedPropertyValues(string propertyTypeAlias) { IUmbracoDatabase?database = _scopeAccessor.AmbientScope?.Database; if (database is null) { throw new InvalidOperationException("A scope is required to query the database"); } Sql <ISqlContext> selectQuery = database.SqlContext.Sql() .SelectAll() .From <PropertyTypeDto>("m") .InnerJoin <PropertyDataDto>("p") .On <PropertyDataDto, PropertyTypeDto>((left, right) => left.PropertyTypeId == right.Id, "p", "m") .Where <PropertyTypeDto>(m => m.Alias == propertyTypeAlias, "m"); Sql <ISqlContext> hasValuesQuery = database.SqlContext.Sql() .SelectAnyIfExists(selectQuery); return(database.ExecuteScalar <bool>(hasValuesQuery)); }
// Only one writer is possible at a time // lock occurs for entire database as opposed to row/table. private void ObtainWriteLock() { IUmbracoDatabase?db = _parent._scopeAccessor.Value.AmbientScope?.Database; if (db is null) { throw new PanicException("no database was found"); } if (!db.InTransaction) { throw new InvalidOperationException( "SqliteDistributedLockingMechanism requires a transaction to function."); } var query = @$ "UPDATE umbracoLock SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id = {LockId}"; DbCommand command = db.CreateCommand(db.Connection, CommandType.Text, query); // imagine there is an existing writer, whilst elapsed time is < command timeout sqlite will busy loop // Important to note that if this value == 0 then Command.DefaultTimeout (30s) is used. // Math.Ceiling such that (0 < totalseconds < 1) is rounded up to 1. command.CommandTimeout = (int)Math.Ceiling(_timeout.TotalSeconds); try { var i = command.ExecuteNonQuery(); if (i == 0) { // ensure we are actually locking! throw new ArgumentException($"LockObject with id={LockId} does not exist."); } } catch (SqliteException ex) when(ex.IsBusyOrLocked()) { throw new DistributedWriteLockTimeoutException(LockId); } }
/// <summary> /// Gets a dictionary of key/values directly from the database, no scope, nothing. /// </summary> /// <remarks>Used by <see cref="CoreRuntimeBootstrapper" /> to determine the runtime state.</remarks> public static IReadOnlyDictionary <string, string?>?GetFromKeyValueTable( this IUmbracoDatabase?database, string keyPrefix) { if (database is null) { return(null); } // create the wildcard where clause ISqlSyntaxProvider sqlSyntax = database.SqlContext.SqlSyntax; var whereParam = sqlSyntax.GetStringColumnWildcardComparison( sqlSyntax.GetQuotedColumnName("key"), 0, TextColumnType.NVarchar); Sql <ISqlContext>?sql = database.SqlContext.Sql() .Select <KeyValueDto>() .From <KeyValueDto>() .Where(whereParam, keyPrefix + sqlSyntax.GetWildcardPlaceholder()); return(database.Fetch <KeyValueDto>(sql) .ToDictionary(x => x.Key, x => x.Value)); }
private bool _disposedValue = false; // To detect redundant calls protected virtual void Dispose(bool disposing) { if (!_disposedValue) { if (disposing) { lock (_locker) { _logger.LogDebug($"{nameof(SqlMainDomLock)} Disposing..."); // immediately cancel all sub-tasks, we don't want them to keep querying _cancellationTokenSource.Cancel(); _cancellationTokenSource.Dispose(); if (_dbFactory.Configured && _hasTable) { IUmbracoDatabase?db = null; try { db = _dbFactory.CreateDatabase(); db !.BeginTransaction(IsolationLevel.Serializable); // When we are disposed, it means we have released the MainDom lock // and called all MainDom release callbacks, in this case // if another maindom is actually coming online we need // to signal to the MainDom coming online that we have shutdown. // To do that, we update the existing main dom DB record with a suffixed "_updated" string. // Otherwise, if we are just shutting down, we want to just delete the row. if (_mainDomChanging) { _logger.LogDebug("Releasing MainDom, updating row, new application is booting."); var count = db.Execute($"UPDATE umbracoKeyValue SET [value] = [value] + '{UpdatedSuffix}' WHERE [key] = @key", new { key = MainDomKey }); } else { _logger.LogDebug("Releasing MainDom, deleting row, application is shutting down."); var count = db.Execute("DELETE FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey }); } } catch (Exception ex) { _logger.LogError(ex, "Unexpected error during dipsose."); } finally { try { db?.CompleteTransaction(); db?.Dispose(); } catch (Exception ex) { _logger.LogError(ex, "Unexpected error during dispose when completing transaction."); } } } } } _disposedValue = true; } }
private void ListeningLoop() { while (true) { // poll every couple of seconds // local testing shows the actual query to be executed from client/server is approx 300ms but would change depending on environment/IO Thread.Sleep(_globalSettings.Value.MainDomReleaseSignalPollingInterval); if (!_dbFactory.Configured) { // if we aren't configured, we just keep looping since we can't query the db continue; } lock (_locker) { // If cancellation has been requested we will just exit. Depending on timing of the shutdown, // we will have already flagged _mainDomChanging = true, or we're shutting down faster than // the other MainDom is taking to startup. In this case the db row will just be deleted and the // new MainDom will just take over. if (_cancellationTokenSource.IsCancellationRequested) { _logger.LogDebug("Task canceled, exiting loop"); return; } IUmbracoDatabase?db = null; try { db = _dbFactory.CreateDatabase(); if (!_hasTable) { // re-check if its still false, we don't want to re-query once we know its there since this // loop needs to use minimal resources _hasTable = db !.HasTable(Cms.Core.Constants.DatabaseSchema.Tables.KeyValue); if (!_hasTable) { // the Db does not contain the required table, we just keep looping since we can't query the db continue; } } // In case we acquired the main dom doing install when there was no database. We therefore have to insert our lockId now, but only handle this once. if (_acquireWhenTablesNotAvailable) { _acquireWhenTablesNotAvailable = false; InsertLockRecord(_lockId, db !); } db !.BeginTransaction(IsolationLevel.Serializable); if (!IsMainDomValue(_lockId, db)) { // we are no longer main dom, another one has come online, exit _mainDomChanging = true; _logger.LogDebug("Detected new booting application, releasing MainDom lock."); return; } } catch (Exception ex) { _logger.LogError(ex, "Unexpected error during listening."); // We need to keep on listening unless we've been notified by our own AppDomain to shutdown since // we don't want to shutdown resources controlled by MainDom inadvertently. We'll just keep listening otherwise. if (_cancellationTokenSource.IsCancellationRequested) { _logger.LogDebug("Task canceled, exiting loop"); return; } } finally { db?.CompleteTransaction(); db?.Dispose(); } } } }
public DatabaseSchemaCreator Create(IUmbracoDatabase?database) { return(new DatabaseSchemaCreator(database, _logger, _loggerFactory, _umbracoVersion, _eventAggregator, _installDefaultDataSettings)); }
public Task HandleAsync(RuntimeUnattendedInstallNotification notification, CancellationToken cancellationToken) { // unattended install is not enabled if (_unattendedSettings.Value.InstallUnattended == false) { return(Task.CompletedTask); } // no connection string set if (_databaseFactory.Configured == false) { return(Task.CompletedTask); } _runtimeState.DetermineRuntimeLevel(); if (_runtimeState.Reason == RuntimeLevelReason.InstallMissingDatabase) { _dbProviderFactoryCreator.CreateDatabase(_databaseFactory.ProviderName !, _databaseFactory.ConnectionString !); } bool connect; try { for (var i = 0; ;) { connect = _databaseFactory.CanConnect; if (connect || ++i == 5) { break; } _logger.LogDebug("Could not immediately connect to database, trying again."); Thread.Sleep(1000); } } catch (Exception ex) { _logger.LogInformation(ex, "Error during unattended install."); var innerException = new UnattendedInstallException("Unattended installation failed.", ex); _runtimeState.Configure(Core.RuntimeLevel.BootFailed, Core.RuntimeLevelReason.BootFailedOnException, innerException); return(Task.CompletedTask); } // could not connect to the database if (connect == false) { return(Task.CompletedTask); } IUmbracoDatabase?database = null; try { using (database = _databaseFactory.CreateDatabase()) { var hasUmbracoTables = database?.IsUmbracoInstalled() ?? false; // database has umbraco tables, assume Umbraco is already installed if (hasUmbracoTables) { return(Task.CompletedTask); } // all conditions fulfilled, do the install _logger.LogInformation("Starting unattended install."); database?.BeginTransaction(); DatabaseSchemaCreator creator = _databaseSchemaCreatorFactory.Create(database); creator.InitializeDatabaseSchema(); database?.CompleteTransaction(); _logger.LogInformation("Unattended install completed."); // Emit an event with EventAggregator that unattended install completed // Then this event can be listened for and create an unattended user _eventAggregator.Publish(new UnattendedInstallNotification()); } } catch (Exception ex) { _logger.LogInformation(ex, "Error during unattended install."); database?.AbortTransaction(); var innerException = new UnattendedInstallException( "The database configuration failed." + "\n Please check log file for additional information (can be found in '/Umbraco/Data/Logs/')", ex); _runtimeState.Configure(Core.RuntimeLevel.BootFailed, Core.RuntimeLevelReason.BootFailedOnException, innerException); } return(Task.CompletedTask); }