private void ScheduleNextBackup(PeriodicBackup periodicBackup)
        {
            try
            {
                _serverStore.ConcurrentBackupsCounter.FinishBackup();

                periodicBackup.RunningTask         = null;
                periodicBackup.RunningBackupTaskId = null;
                periodicBackup.CancelToken         = null;
                periodicBackup.RunningBackupStatus = null;

                if (periodicBackup.HasScheduledBackup() && _cancellationToken.IsCancellationRequested == false)
                {
                    var newBackupTimer = GetTimer(periodicBackup.Configuration, periodicBackup.BackupStatus);
                    periodicBackup.UpdateTimer(newBackupTimer, discardIfDisabled: true);
                }
            }
            catch (Exception e)
            {
                var message = $"Failed to schedule next backup for task: '{periodicBackup.Configuration.Name}'";
                if (_logger.IsOperationsEnabled)
                {
                    _logger.Operations(message, e);
                }

                _database.NotificationCenter.Add(AlertRaised.Create(
                                                     _database.Name,
                                                     "Couldn't schedule next backup",
                                                     message,
                                                     AlertType.PeriodicBackup,
                                                     NotificationSeverity.Warning,
                                                     details: new ExceptionDetails(e)));
            }
        }
Beispiel #2
0
        private void RunBackupThread(PeriodicBackup periodicBackup, BackupTask backupTask, Action <IOperationProgress> onProgress, TaskCompletionSource <IOperationResult> tcs)
        {
            try
            {
                Thread.CurrentThread.Priority = ThreadPriority.BelowNormal;
                NativeMemory.EnsureRegistered();

                using (_database.PreventFromUnloading())
                {
                    tcs.SetResult(backupTask.RunPeriodicBackup(onProgress));
                }
            }
            catch (OperationCanceledException)
            {
                tcs.SetCanceled();
            }
            catch (Exception e)
            {
                if (_logger.IsOperationsEnabled)
                {
                    _logger.Operations($"Failed to run the backup thread: '{periodicBackup.Configuration.Name}'", e);
                }

                tcs.SetException(e);
            }
            finally
            {
                try
                {
                    _serverStore.ConcurrentBackupsSemaphore.Release();

                    periodicBackup.RunningTask         = null;
                    periodicBackup.RunningBackupTaskId = null;
                    periodicBackup.CancelToken         = null;
                    periodicBackup.RunningBackupStatus = null;

                    if (periodicBackup.HasScheduledBackup() && _cancellationToken.IsCancellationRequested == false)
                    {
                        var newBackupTimer = GetTimer(periodicBackup.Configuration, periodicBackup.BackupStatus);
                        periodicBackup.UpdateTimer(newBackupTimer, discardIfDisabled: true);
                    }
                }
                catch (Exception e)
                {
                    var msg = $"Failed to schedule next backup for backup thread: '{periodicBackup.Configuration.Name}'";
                    if (_logger.IsOperationsEnabled)
                    {
                        _logger.Operations(msg, e);
                    }

                    _database.NotificationCenter.Add(AlertRaised.Create(
                                                         _database.Name,
                                                         "Couldn't schedule next backup.",
                                                         msg,
                                                         AlertType.PeriodicBackup,
                                                         NotificationSeverity.Warning,
                                                         details: new ExceptionDetails(e)));
                }
            }
        }
Beispiel #3
0
        public BackupTask(
            ServerStore serverStore,
            DocumentDatabase database,
            PeriodicBackup periodicBackup,
            bool isFullBackup,
            bool backupToLocalFolder,
            long operationId,
            PathSetting tempBackupPath,
            Logger logger,
            CancellationToken databaseShutdownCancellationToken)
        {
            _serverStore          = serverStore;
            _database             = database;
            _startTime            = periodicBackup.StartTime;
            _periodicBackup       = periodicBackup;
            _configuration        = periodicBackup.Configuration;
            _previousBackupStatus = periodicBackup.BackupStatus;
            _isFullBackup         = isFullBackup;
            _backupToLocalFolder  = backupToLocalFolder;
            _operationId          = operationId;
            _tempBackupPath       = tempBackupPath;
            _logger = logger;
            _databaseShutdownCancellationToken = databaseShutdownCancellationToken;

            TaskCancelToken = new OperationCancelToken(_databaseShutdownCancellationToken);
            _backupResult   = GenerateBackupResult();
        }
        private void RunBackupThread(PeriodicBackup periodicBackup, BackupTask backupTask, Action <IOperationProgress> onProgress, TaskCompletionSource <IOperationResult> tcs)
        {
            try
            {
                Thread.CurrentThread.Priority = ThreadPriority.BelowNormal;
                NativeMemory.EnsureRegistered();

                using (_database.PreventFromUnloading())
                {
                    tcs.SetResult(backupTask.RunPeriodicBackup(onProgress));
                }
            }
            catch (OperationCanceledException)
            {
                tcs.SetCanceled();
            }
            catch (Exception e)
            {
                if (_logger.IsOperationsEnabled)
                {
                    _logger.Operations($"Failed to run the backup thread: '{periodicBackup.Configuration.Name}'", e);
                }

                tcs.SetException(e);
            }
            finally
            {
                ScheduleNextBackup(periodicBackup);
            }
        }
Beispiel #5
0
        private bool ShouldRunBackupAfterTimerCallback(NextBackup backupInfo, out PeriodicBackup periodicBackup)
        {
            if (_periodicBackups.TryGetValue(backupInfo.TaskId, out periodicBackup) == false)
            {
                // periodic backup doesn't exist anymore
                return(false);
            }

            DatabaseTopology topology;

            using (_serverStore.ContextPool.AllocateOperationContext(out TransactionOperationContext context))
                using (context.OpenReadTransaction())
                    using (var rawRecord = _serverStore.Cluster.ReadRawDatabaseRecord(context, _database.Name))
                    {
                        if (rawRecord == null)
                        {
                            return(false);
                        }

                        topology = rawRecord.GetTopology();
                    }

            var taskStatus = GetTaskStatus(topology, periodicBackup.Configuration);

            return(taskStatus == TaskStatus.ActiveByCurrentNode);
        }
Beispiel #6
0
        private Task <IOperationResult> StartBackupThread(PeriodicBackup periodicBackup, BackupTask backupTask, Action <IOperationProgress> onProgress)
        {
            var tcs = new TaskCompletionSource <IOperationResult>(TaskCreationOptions.RunContinuationsAsynchronously);

            PoolOfThreads.GlobalRavenThreadPool.LongRunning(x => RunBackupThread(periodicBackup, backupTask, onProgress, tcs), null, $"Backup task {periodicBackup.Configuration.Name} for database '{_database.Name}'");
            return(tcs.Task);
        }
Beispiel #7
0
        private void StartBackupTaskAndRescheduleIfNeeded(PeriodicBackup periodicBackup, NextBackup currentBackup)
        {
            try
            {
                CreateBackupTask(periodicBackup, currentBackup.IsFull, currentBackup.DateTime);
            }
            catch (BackupDelayException e)
            {
                if (_logger.IsInfoEnabled)
                {
                    _logger.Info($"Backup task will be retried in {(int)e.DelayPeriod.TotalSeconds} seconds.", e);
                }

                // we'll retry in one minute
                var backupTaskDetails = new NextBackup
                {
                    IsFull   = currentBackup.IsFull,
                    TaskId   = periodicBackup.Configuration.TaskId,
                    DateTime = DateTime.UtcNow.Add(e.DelayPeriod),
                    TimeSpan = e.DelayPeriod
                };

                var timer = new Timer(TimerCallback, backupTaskDetails, backupTaskDetails.TimeSpan, Timeout.InfiniteTimeSpan);
                periodicBackup.UpdateTimer(timer);
            }
        }
Beispiel #8
0
        private void UpdatePeriodicBackup(long taskId,
                                          PeriodicBackupConfiguration newConfiguration,
                                          TaskStatus taskState)
        {
            Debug.Assert(taskId == newConfiguration.TaskId);

            var backupStatus = GetBackupStatus(taskId, inMemoryBackupStatus: null);

            if (_periodicBackups.TryGetValue(taskId, out var existingBackupState) == false)
            {
                var newPeriodicBackup = new PeriodicBackup(_inactiveRunningPeriodicBackupsTasks)
                {
                    Configuration = newConfiguration
                };

                var periodicBackup = _periodicBackups.GetOrAdd(taskId, newPeriodicBackup);
                if (periodicBackup != newPeriodicBackup)
                {
                    newPeriodicBackup.Dispose();
                }

                if (taskState == TaskStatus.ActiveByCurrentNode)
                {
                    periodicBackup.UpdateTimer(GetTimer(newConfiguration, backupStatus));
                }

                return;
            }

            var previousConfiguration = existingBackupState.Configuration;

            existingBackupState.Configuration = newConfiguration;

            if (taskState != TaskStatus.ActiveByCurrentNode)
            {
                // this node isn't responsible for the backup task
                existingBackupState.DisableFutureBackups();
                return;
            }

            if (existingBackupState.RunningTask != null)
            {
                // a backup is already running
                // the next one will be re-scheduled by the backup task
                return;
            }

            if (previousConfiguration.HasBackupFrequencyChanged(newConfiguration) == false &&
                existingBackupState.HasScheduledBackup())
            {
                // backup frequency hasn't changed
                // and we have a scheduled backup
                return;
            }

            existingBackupState.UpdateTimer(GetTimer(newConfiguration, backupStatus));
        }
Beispiel #9
0
        private void UpdatePeriodicBackups(long taskId,
                                           PeriodicBackupConfiguration newConfiguration,
                                           TaskStatus taskState)
        {
            Debug.Assert(taskId == newConfiguration.TaskId);

            var backupStatus = GetBackupStatus(taskId, inMemoryBackupStatus: null);

            if (_periodicBackups.TryGetValue(taskId, out PeriodicBackup existingBackupState) == false)
            {
                var newPeriodicBackup = new PeriodicBackup
                {
                    Configuration = newConfiguration
                };

                if (taskState == TaskStatus.ActiveByCurrentNode)
                {
                    newPeriodicBackup.BackupTimer = GetTimer(newConfiguration, backupStatus);
                }

                _periodicBackups.TryAdd(taskId, newPeriodicBackup);
                return;
            }

            if (existingBackupState.Configuration.Equals(newConfiguration))
            {
                // the username/password for the cloud backups might have changed,
                // and it will be reloaded on the next backup re-scheduling
                existingBackupState.Configuration = newConfiguration;
                if (taskState == TaskStatus.ActiveByCurrentNode)
                {
                    existingBackupState.BackupTimer = GetTimer(newConfiguration, backupStatus);
                }
                return;
            }

            // the backup configuration changed
            existingBackupState.DisableFutureBackups();
            TryAddInactiveRunningPeriodicBackups(existingBackupState.RunningTask);
            _periodicBackups.TryRemove(taskId, out _);

            var periodicBackup = new PeriodicBackup
            {
                Configuration = newConfiguration
            };

            if (taskState == TaskStatus.ActiveByCurrentNode)
            {
                periodicBackup.BackupTimer = GetTimer(newConfiguration, backupStatus);
            }

            _periodicBackups.TryAdd(taskId, periodicBackup);
        }
Beispiel #10
0
        private bool ShouldRunBackupAfterTimerCallback(BackupTaskDetails backupInfo, out PeriodicBackup periodicBackup)
        {
            if (_periodicBackups.TryGetValue(backupInfo.TaskId, out periodicBackup) == false)
            {
                // periodic backup doesn't exist anymore
                return(false);
            }

            if (periodicBackup.Disposed)
            {
                // this periodic backup was canceled
                return(false);
            }

            var databaseRecord = GetDatabaseRecord();
            var taskStatus     = GetTaskStatus(databaseRecord, periodicBackup.Configuration);

            return(taskStatus == TaskStatus.ActiveByCurrentNode);
        }
Beispiel #11
0
        private void CreateBackupTask(PeriodicBackup periodicBackup, BackupTaskDetails backupDetails)
        {
            periodicBackup.RunningTask = Task.Run(async() =>
            {
                periodicBackup.BackupStatus = GetBackupStatus(periodicBackup.Configuration.TaskId, periodicBackup.BackupStatus);

                try
                {
                    await RunPeriodicBackup(periodicBackup.Configuration,
                                            periodicBackup.BackupStatus, backupDetails.IsFullBackup);
                }
                finally
                {
                    if (_cancellationToken.IsCancellationRequested == false &&
                        periodicBackup.Disposed == false)
                    {
                        periodicBackup.BackupTimer.Dispose();
                        periodicBackup.BackupTimer = GetTimer(periodicBackup.Configuration, periodicBackup.BackupStatus);
                    }
                }
            }, _database.DatabaseShutdown);
        }
Beispiel #12
0
        public BackupTask(
            ServerStore serverStore,
            DocumentDatabase database,
            PeriodicBackup periodicBackup,
            bool isFullBackup,
            bool backupToLocalFolder,
            long operationId,
            PathSetting tempBackupPath,
            Logger logger,
            CancellationToken databaseShutdownCancellationToken,
            PeriodicBackupRunner.TestingStuff forTestingPurposes = null)
        {
            _serverStore          = serverStore;
            _database             = database;
            _periodicBackup       = periodicBackup;
            _configuration        = periodicBackup.Configuration;
            _isServerWide         = _configuration.Name?.StartsWith(ServerWideBackupConfiguration.NamePrefix, StringComparison.OrdinalIgnoreCase) ?? false;
            _isBackupEncrypted    = IsBackupEncrypted(_database, _configuration);
            _previousBackupStatus = periodicBackup.BackupStatus;
            _isFullBackup         = isFullBackup;
            _backupToLocalFolder  = backupToLocalFolder;
            _operationId          = operationId;
            _tempBackupPath       = tempBackupPath;
            _logger = logger;
            _databaseShutdownCancellationToken = databaseShutdownCancellationToken;
            _forTestingPurposes = forTestingPurposes;

            TaskCancelToken = new OperationCancelToken(_databaseShutdownCancellationToken);
            _backupResult   = GenerateBackupResult();

            _retentionPolicyParameters = new RetentionPolicyBaseParameters
            {
                RetentionPolicy   = _configuration.RetentionPolicy,
                DatabaseName      = _database.Name,
                IsFullBackup      = _isFullBackup,
                OnProgress        = AddInfo,
                CancellationToken = TaskCancelToken.Token
            };
        }
        private long CreateBackupTask(PeriodicBackup periodicBackup, bool isFullBackup, DateTime startTimeInUtc)
        {
            using (periodicBackup.UpdateBackupTask())
            {
                if (periodicBackup.Disposed)
                {
                    throw new InvalidOperationException("Backup task was already disposed");
                }

                if (periodicBackup.RunningTask != null)
                {
                    return(periodicBackup.RunningBackupTaskId ?? -1);
                }

                if (_serverStore.Server.CpuCreditsBalance.BackgroundTasksAlertRaised.IsRaised())
                {
                    throw new BackupDelayException(
                              $"Failed to start Backup Task: '{periodicBackup.Configuration.Name}'. " +
                              $"The task cannot run because the CPU credits allocated to this machine are nearing exhaustion.")
                          {
                              DelayPeriod = _serverStore.Configuration.Server.CpuCreditsExhaustionBackupDelay.AsTimeSpan
                          };
                }

                if (LowMemoryNotification.Instance.LowMemoryState)
                {
                    throw new BackupDelayException(
                              $"Failed to start Backup Task: '{periodicBackup.Configuration.Name}'. " +
                              $"The task cannot run because the server is in low memory state.")
                          {
                              DelayPeriod = _serverStore.Configuration.Backup.LowMemoryBackupDelay.AsTimeSpan
                          };
                }

                if (LowMemoryNotification.Instance.DirtyMemoryState.IsHighDirty)
                {
                    throw new BackupDelayException(
                              $"Failed to start Backup Task: '{periodicBackup.Configuration.Name}'. " +
                              $"The task cannot run because the server is in high dirty memory state.")
                          {
                              DelayPeriod = _serverStore.Configuration.Backup.LowMemoryBackupDelay.AsTimeSpan
                          };
                }

                _serverStore.ConcurrentBackupsCounter.StartBackup(periodicBackup.Configuration.Name);

                try
                {
                    var backupStatus        = periodicBackup.BackupStatus = GetBackupStatus(periodicBackup.Configuration.TaskId, periodicBackup.BackupStatus);
                    var backupToLocalFolder = PeriodicBackupConfiguration.CanBackupUsing(periodicBackup.Configuration.LocalSettings);

                    // check if we need to do a new full backup
                    if (backupStatus.LastFullBackup == null ||                                // no full backup was previously performed
                        backupStatus.NodeTag != _serverStore.NodeTag ||                       // last backup was performed by a different node
                        backupStatus.BackupType != periodicBackup.Configuration.BackupType || // backup type has changed
                        backupStatus.LastEtag == null ||                                      // last document etag wasn't updated
                        backupToLocalFolder && BackupTask.DirectoryContainsBackupFiles(backupStatus.LocalBackup.BackupDirectory, IsFullBackupOrSnapshot) == false)
                    // the local folder already includes a full backup or snapshot
                    {
                        isFullBackup = true;
                    }

                    var operationId    = _database.Operations.GetNextOperationId();
                    var backupTypeText = GetBackupTypeText(isFullBackup, periodicBackup.Configuration.BackupType);

                    periodicBackup.StartTimeInUtc = startTimeInUtc;
                    var backupTask = new BackupTask(
                        _serverStore,
                        _database,
                        periodicBackup,
                        isFullBackup,
                        backupToLocalFolder,
                        operationId,
                        _tempBackupPath,
                        _logger,
                        _cancellationToken.Token);

                    periodicBackup.RunningBackupTaskId = operationId;
                    periodicBackup.CancelToken         = backupTask.TaskCancelToken;
                    var backupTaskName = $"{backupTypeText} backup task: '{periodicBackup.Configuration.Name}'. Database: '{_database.Name}'";

                    var task = _database.Operations.AddOperation(
                        null,
                        backupTaskName,
                        Operations.Operations.OperationType.DatabaseBackup,
                        taskFactory: onProgress => StartBackupThread(periodicBackup, backupTask, onProgress),
                        id: operationId,
                        token: backupTask.TaskCancelToken);

                    periodicBackup.RunningTask = task;
                    task.ContinueWith(_ => backupTask.TaskCancelToken.Dispose());

                    return(operationId);
                }
                catch (Exception e)
                {
                    // we failed to START the backup, need to update the status anyway
                    // in order to reschedule the next full/incremental backup
                    periodicBackup.BackupStatus.Version++;
                    periodicBackup.BackupStatus.Error = new Error
                    {
                        Exception = e.ToString(),
                        At        = DateTime.UtcNow
                    };

                    if (isFullBackup)
                    {
                        periodicBackup.BackupStatus.LastFullBackupInternal = startTimeInUtc;
                    }
                    else
                    {
                        periodicBackup.BackupStatus.LastIncrementalBackupInternal = startTimeInUtc;
                    }

                    BackupTask.SaveBackupStatus(periodicBackup.BackupStatus, _database, _logger);
                    ScheduleNextBackup(periodicBackup);

                    var message = $"Failed to start the backup task: '{periodicBackup.Configuration.Name}'";
                    if (_logger.IsOperationsEnabled)
                    {
                        _logger.Operations(message, e);
                    }

                    _database.NotificationCenter.Add(AlertRaised.Create(
                                                         _database.Name,
                                                         message,
                                                         "The next backup will be rescheduled",
                                                         AlertType.PeriodicBackup,
                                                         NotificationSeverity.Error,
                                                         details: new ExceptionDetails(e)));

                    throw;
                }
            }
        }
Beispiel #14
0
        private long CreateBackupTask(PeriodicBackup periodicBackup, bool isFullBackup)
        {
            using (periodicBackup.UpdateBackupTask())
            {
                try
                {
                    if (periodicBackup.Disposed)
                    {
                        throw new InvalidOperationException("Backup task was already disposed");
                    }

                    if (periodicBackup.RunningTask != null)
                    {
                        return(periodicBackup.RunningBackupTaskId ?? -1);
                    }

                    var backupStatus        = periodicBackup.BackupStatus = GetBackupStatus(periodicBackup.Configuration.TaskId, periodicBackup.BackupStatus);
                    var backupToLocalFolder = PeriodicBackupConfiguration.CanBackupUsing(periodicBackup.Configuration.LocalSettings);

                    // check if we need to do a new full backup
                    if (backupStatus.LastFullBackup == null ||                                // no full backup was previously performed
                        backupStatus.NodeTag != _serverStore.NodeTag ||                       // last backup was performed by a different node
                        backupStatus.BackupType != periodicBackup.Configuration.BackupType || // backup type has changed
                        backupStatus.LastEtag == null ||                                      // last document etag wasn't updated
                        backupToLocalFolder && BackupTask.DirectoryContainsBackupFiles(backupStatus.LocalBackup.BackupDirectory, IsFullBackupOrSnapshot) == false)
                    // the local folder already includes a full backup or snapshot
                    {
                        isFullBackup = true;
                    }

                    var operationId    = _database.Operations.GetNextOperationId();
                    var backupTypeText = GetBackupTypeText(isFullBackup, periodicBackup.Configuration.BackupType);

                    periodicBackup.StartTime = SystemTime.UtcNow;
                    var backupTask = new BackupTask(
                        _serverStore,
                        _database,
                        periodicBackup,
                        isFullBackup,
                        backupToLocalFolder,
                        operationId,
                        _tempBackupPath,
                        _logger,
                        _cancellationToken.Token);

                    periodicBackup.RunningBackupTaskId = operationId;
                    periodicBackup.CancelToken         = backupTask.TaskCancelToken;
                    var backupTaskName = $"{backupTypeText} backup task: '{periodicBackup.Configuration.Name}'";

                    var task = _database.Operations.AddOperation(
                        null,
                        backupTaskName,
                        Operations.Operations.OperationType.DatabaseBackup,
                        taskFactory: onProgress => Task.Run(async() =>
                    {
                        try
                        {
                            using (_database.PreventFromUnloading())
                            {
                                return(await backupTask.RunPeriodicBackup(onProgress));
                            }
                        }
                        finally
                        {
                            periodicBackup.RunningTask         = null;
                            periodicBackup.RunningBackupTaskId = null;
                            periodicBackup.CancelToken         = null;
                            periodicBackup.RunningBackupStatus = null;

                            if (periodicBackup.HasScheduledBackup() &&
                                _cancellationToken.IsCancellationRequested == false)
                            {
                                var newBackupTimer = GetTimer(periodicBackup.Configuration, periodicBackup.BackupStatus);
                                periodicBackup.UpdateTimer(newBackupTimer, discardIfDisabled: true);
                            }
                        }
                    }, backupTask.TaskCancelToken.Token),
                        id: operationId,
                        token: backupTask.TaskCancelToken);

                    periodicBackup.RunningTask = task;
                    task.ContinueWith(_ => backupTask.TaskCancelToken.Dispose());

                    return(operationId);
                }
                catch (Exception e)
                {
                    var message = $"Failed to start the backup task: '{periodicBackup.Configuration.Name}'";
                    if (_logger.IsOperationsEnabled)
                    {
                        _logger.Operations(message, e);
                    }

                    _database.NotificationCenter.Add(AlertRaised.Create(
                                                         _database.Name,
                                                         $"Periodic Backup task: '{periodicBackup.Configuration.Name}'",
                                                         message,
                                                         AlertType.PeriodicBackup,
                                                         NotificationSeverity.Error,
                                                         details: new ExceptionDetails(e)));

                    throw;
                }
            }
        }
Beispiel #15
0
        private long CreateBackupTask(PeriodicBackup periodicBackup, bool isFullBackup, DateTime startTimeInUtc)
        {
            using (periodicBackup.UpdateBackupTask())
            {
                if (periodicBackup.Disposed)
                {
                    throw new InvalidOperationException("Backup task was already disposed");
                }

                if (periodicBackup.RunningTask != null)
                {
                    return(periodicBackup.RunningBackupTaskId ?? -1);
                }

                if (_serverStore.Server.CpuCreditsBalance.BackgroundTasksAlertRaised.IsRaised())
                {
                    throw new BackupDelayException(
                              $"Failed to start Backup Task: '{periodicBackup.Configuration.Name}'. " +
                              $"The task cannot run because the CPU credits allocated to this machine are nearing exhaustion.")
                          {
                              DelayPeriod = _serverStore.Configuration.Server.CpuCreditsExhaustionBackupDelay.AsTimeSpan
                          };
                }

                if (LowMemoryNotification.Instance.LowMemoryState)
                {
                    throw new BackupDelayException(
                              $"Failed to start Backup Task: '{periodicBackup.Configuration.Name}'. " +
                              $"The task cannot run because the server is in low memory state.")
                          {
                              DelayPeriod = _serverStore.Configuration.Backup.LowMemoryBackupDelay.AsTimeSpan
                          };
                }

                if (_serverStore.ConcurrentBackupsSemaphore.Wait(0) == false)
                {
                    throw new BackupDelayException(
                              $"Failed to start Backup Task: '{periodicBackup.Configuration.Name}'. " +
                              $"The task exceeds the maximum number of concurrent backup tasks configured. " +
                              $"Current value of Backup.MaxNumberOfConcurrentBackups is: {_serverStore.Configuration.Backup.MaxNumberOfConcurrentBackups:#,#;;0}")
                          {
                              DelayPeriod = TimeSpan.FromMinutes(1)
                          };
                }

                try
                {
                    var backupStatus        = periodicBackup.BackupStatus = GetBackupStatus(periodicBackup.Configuration.TaskId, periodicBackup.BackupStatus);
                    var backupToLocalFolder = PeriodicBackupConfiguration.CanBackupUsing(periodicBackup.Configuration.LocalSettings);

                    // check if we need to do a new full backup
                    if (backupStatus.LastFullBackup == null ||                                // no full backup was previously performed
                        backupStatus.NodeTag != _serverStore.NodeTag ||                       // last backup was performed by a different node
                        backupStatus.BackupType != periodicBackup.Configuration.BackupType || // backup type has changed
                        backupStatus.LastEtag == null ||                                      // last document etag wasn't updated
                        backupToLocalFolder && BackupTask.DirectoryContainsBackupFiles(backupStatus.LocalBackup.BackupDirectory, IsFullBackupOrSnapshot) == false)
                    // the local folder already includes a full backup or snapshot
                    {
                        isFullBackup = true;
                    }

                    var operationId    = _database.Operations.GetNextOperationId();
                    var backupTypeText = GetBackupTypeText(isFullBackup, periodicBackup.Configuration.BackupType);

                    periodicBackup.StartTimeInUtc = startTimeInUtc;
                    var backupTask = new BackupTask(
                        _serverStore,
                        _database,
                        periodicBackup,
                        isFullBackup,
                        backupToLocalFolder,
                        operationId,
                        _tempBackupPath,
                        _logger,
                        _cancellationToken.Token);

                    periodicBackup.RunningBackupTaskId = operationId;
                    periodicBackup.CancelToken         = backupTask.TaskCancelToken;
                    var backupTaskName = $"{backupTypeText} backup task: '{periodicBackup.Configuration.Name}'. Database: '{_database.Name}'";

                    var task = _database.Operations.AddOperation(
                        null,
                        backupTaskName,
                        Operations.Operations.OperationType.DatabaseBackup,
                        taskFactory: onProgress => StartBackupThread(periodicBackup, backupTask, onProgress),
                        id: operationId,
                        token: backupTask.TaskCancelToken);

                    periodicBackup.RunningTask = task;
                    task.ContinueWith(_ => backupTask.TaskCancelToken.Dispose());

                    return(operationId);
                }
                catch (Exception e)
                {
                    // releasing the semaphore because we failed to start the backup task
                    _serverStore.ConcurrentBackupsSemaphore.Release();

                    var message = $"Failed to start the backup task: '{periodicBackup.Configuration.Name}'";
                    if (_logger.IsOperationsEnabled)
                    {
                        _logger.Operations(message, e);
                    }

                    _database.NotificationCenter.Add(AlertRaised.Create(
                                                         _database.Name,
                                                         $"Periodic Backup task: '{periodicBackup.Configuration.Name}'",
                                                         message,
                                                         AlertType.PeriodicBackup,
                                                         NotificationSeverity.Error,
                                                         details: new ExceptionDetails(e)));

                    throw;
                }
            }
        }
        private void UpdatePeriodicBackup(long taskId,
                                          PeriodicBackupConfiguration newConfiguration,
                                          TaskStatus taskState)
        {
            Debug.Assert(taskId == newConfiguration.TaskId);

            var backupStatus = GetBackupStatus(taskId, inMemoryBackupStatus: null);

            if (_periodicBackups.TryGetValue(taskId, out var existingBackupState) == false)
            {
                var newPeriodicBackup = new PeriodicBackup(_inactiveRunningPeriodicBackupsTasks)
                {
                    Configuration = newConfiguration
                };

                var periodicBackup = _periodicBackups.GetOrAdd(taskId, newPeriodicBackup);
                if (periodicBackup != newPeriodicBackup)
                {
                    newPeriodicBackup.Dispose();
                }

                if (taskState == TaskStatus.ActiveByCurrentNode)
                {
                    periodicBackup.UpdateTimer(GetTimer(newConfiguration, backupStatus));
                }

                return;
            }

            var previousConfiguration = existingBackupState.Configuration;

            existingBackupState.Configuration = newConfiguration;

            switch (taskState)
            {
            case TaskStatus.Disabled:
            case TaskStatus.ActiveByOtherNode:
                // the task is disabled or this node isn't responsible for the backup task
                existingBackupState.DisableFutureBackups();
                return;

            case TaskStatus.ClusterDown:
                // this node cannot connect to cluster, the task will continue on this node
                return;

            case TaskStatus.ActiveByCurrentNode:
                // a backup is already running, the next one will be re-scheduled by the backup task if needed
                if (existingBackupState.RunningTask != null)
                {
                    return;
                }

                // backup frequency hasn't changed, and we have a scheduled backup
                if (previousConfiguration.HasBackupFrequencyChanged(newConfiguration) == false && existingBackupState.HasScheduledBackup())
                {
                    return;
                }

                existingBackupState.UpdateTimer(GetTimer(newConfiguration, backupStatus));
                return;

            default:
                throw new ArgumentOutOfRangeException(nameof(taskState), taskState, null);
            }
        }
Beispiel #17
0
        private long CreateBackupTask(PeriodicBackup periodicBackup, bool isFullBackup)
        {
            if (periodicBackup.UpdateBackupTaskSemaphore.Wait(0) == false)
            {
                return(periodicBackup.RunningBackupTaskId ?? -1);
            }

            try
            {
                if (periodicBackup.RunningTask != null)
                {
                    return(periodicBackup.RunningBackupTaskId ?? -1);
                }

                var backupStatus        = periodicBackup.BackupStatus = GetBackupStatus(periodicBackup.Configuration.TaskId, periodicBackup.BackupStatus);
                var backupToLocalFolder = PeriodicBackupConfiguration.CanBackupUsing(periodicBackup.Configuration.LocalSettings);

                // check if we need to do a new full backup
                if (backupStatus.LastFullBackup == null ||                                // no full backup was previously performed
                    backupStatus.NodeTag != _serverStore.NodeTag ||                       // last backup was performed by a different node
                    backupStatus.BackupType != periodicBackup.Configuration.BackupType || // backup type has changed
                    backupStatus.LastEtag == null ||                                      // last document etag wasn't updated
                    backupToLocalFolder && BackupTask.DirectoryContainsBackupFiles(backupStatus.LocalBackup.BackupDirectory, IsFullBackupOrSnapshot) == false)
                // the local folder already includes a full backup or snapshot
                {
                    isFullBackup = true;
                }

                var operationId    = _database.Operations.GetNextOperationId();
                var backupTypeText = GetBackupTypeText(isFullBackup, periodicBackup.Configuration.BackupType);

                periodicBackup.StartTime = SystemTime.UtcNow;
                var backupTask = new BackupTask(
                    _serverStore,
                    _database,
                    periodicBackup,
                    isFullBackup,
                    backupToLocalFolder,
                    operationId,
                    _tempBackupPath,
                    _logger,
                    _cancellationToken.Token);

                periodicBackup.RunningBackupTaskId = operationId;
                periodicBackup.CancelToken         = backupTask.TaskCancelToken;
                var backupTaskName = $"{backupTypeText} backup task: '{periodicBackup.Configuration.Name}'";

                var task = _database.Operations.AddOperation(
                    null,
                    backupTaskName,
                    Operations.Operations.OperationType.DatabaseBackup,
                    taskFactory: onProgress => Task.Run(async() =>
                {
                    try
                    {
                        return(await backupTask.RunPeriodicBackup(onProgress));
                    }
                    finally
                    {
                        periodicBackup.RunningTask         = null;
                        periodicBackup.RunningBackupTaskId = null;
                        periodicBackup.CancelToken         = null;
                        periodicBackup.RunningBackupStatus = null;

                        if (periodicBackup.HasScheduledBackup() &&
                            _cancellationToken.IsCancellationRequested == false)
                        {
                            var newBackupTimer = GetTimer(periodicBackup.Configuration, periodicBackup.BackupStatus);
                            periodicBackup.UpdateTimer(newBackupTimer, discardIfDisabled: true);
                        }
                    }
                }, backupTask.TaskCancelToken.Token),
                    id: operationId,
                    token: backupTask.TaskCancelToken);

                periodicBackup.RunningTask = task;
                task.ContinueWith(_ => backupTask.TaskCancelToken.Dispose());

                return(operationId);
            }
            finally
            {
                periodicBackup.UpdateBackupTaskSemaphore.Release();
            }
        }