public Task ClusterOnDatabaseChanged(string databaseName, long index, string type, ClusterDatabaseChangeType changeType, object changeState) { return(HandleClusterDatabaseChanged(databaseName, index, type, changeType, changeState)); }
private async Task HandleClusterDatabaseChanged(string databaseName, long index, string type, ClusterDatabaseChangeType changeType, object _) { ForTestingPurposes?.BeforeHandleClusterDatabaseChanged?.Invoke(_serverStore); if (PreventWakeUpIdleDatabase(databaseName, type)) { return; } using (await _disposing.ReaderLockAsync(_serverStore.ServerShutdown)) { try { if (_serverStore.ServerShutdown.IsCancellationRequested) { return; } // response to changed database. // if disabled, unload DatabaseTopology topology; using (_serverStore.ContextPool.AllocateOperationContext(out TransactionOperationContext context)) using (context.OpenReadTransaction()) using (var rawRecord = _serverStore.Cluster.ReadRawDatabaseRecord(context, databaseName)) { if (rawRecord == null) { // was removed, need to make sure that it isn't loaded UnloadDatabase(databaseName, dbRecordIsNull: true); return; } if (ShouldDeleteDatabase(context, databaseName, rawRecord)) { return; } topology = rawRecord.Topology; if (topology.RelevantFor(_serverStore.NodeTag) == false) { return; } if (rawRecord.IsDisabled || rawRecord.DatabaseState == DatabaseStateStatus.RestoreInProgress) { UnloadDatabase(databaseName); return; } } if (changeType == ClusterDatabaseChangeType.RecordRestored) { // - a successful restore operation ends when we successfully restored // the database files and saved the updated the database record // - this is the first time that the database was loaded so there is no need to call // StateChanged after the database was restored // - the database will be started on demand return; } if (DatabasesCache.TryGetValue(databaseName, out var task) == false) { // if the database isn't loaded, but it is relevant for this node, we need to create // it. This is important so things like replication will start pumping, and that // configuration changes such as running periodic backup will get a chance to run, which // they wouldn't unless the database is loaded / will have a request on it. task = TryGetOrCreateResourceStore(databaseName, ignoreBeenDeleted: true); } var database = await task; switch (changeType) { case ClusterDatabaseChangeType.RecordChanged: database.StateChanged(index); if (type == ClusterStateMachine.SnapshotInstalled) { database.NotifyOnPendingClusterTransaction(index, changeType); } break; case ClusterDatabaseChangeType.ValueChanged: database.ValueChanged(index); break; case ClusterDatabaseChangeType.PendingClusterTransactions: case ClusterDatabaseChangeType.ClusterTransactionCompleted: database.DatabaseGroupId = topology.DatabaseTopologyIdBase64; database.NotifyOnPendingClusterTransaction(index, changeType); break; default: ThrowUnknownClusterDatabaseChangeType(changeType); break; } // if deleted, unload / deleted and then notify leader that we removed it } catch (AggregateException ae) when(nameof(DeleteDatabase).Equals(ae.InnerException.Data["Source"])) { // in the process of being deleted } catch (AggregateException ae) when(ae.InnerException is DatabaseDisabledException) { // the db is already disabled when we try to disable it } catch (DatabaseDisabledException) { // the database was disabled while we were trying to execute an action (e.g. PendingClusterTransactions) } catch (ObjectDisposedException) { // the server is disposed when we are trying to access to database } catch (DatabaseConcurrentLoadTimeoutException e) { var title = $"Concurrent load timeout of '{databaseName}' database"; var message = $"Failed to load database '{databaseName}' concurrently with other databases within {_serverStore.Configuration.Databases.ConcurrentLoadTimeout.AsTimeSpan}. " + "Database load will be attempted on next request accessing it. If you see this on regular basis you might consider adjusting the following configuration options: " + $"{RavenConfiguration.GetKey(x => x.Databases.ConcurrentLoadTimeout)} and {RavenConfiguration.GetKey(x => x.Databases.MaxConcurrentLoads)}"; if (_logger.IsInfoEnabled) { _logger.Info(message, e); } _serverStore.NotificationCenter.Add(AlertRaised.Create(databaseName, title, message, AlertType.ConcurrentDatabaseLoadTimeout, NotificationSeverity.Warning, details: new ExceptionDetails(e))); throw; } catch (Exception e) { var title = $"Failed to digest change of type '{changeType}' for database '{databaseName}' at index {index}"; if (_logger.IsInfoEnabled) { _logger.Info(title, e); } _serverStore.NotificationCenter.Add(AlertRaised.Create(databaseName, title, e.Message, AlertType.DeletionError, NotificationSeverity.Error, details: new ExceptionDetails(e))); throw; } } }
private static void ThrowUnknownClusterDatabaseChangeType(ClusterDatabaseChangeType type) { throw new InvalidOperationException($"Unknown cluster database change type: {type}"); }