private void TimerCallback(bool fullBackup) { if (currentTask != null) return; // we have shared lock for both incremental and full backup. lock (this) { if (currentTask != null) return; currentTask = Task.Factory.StartNew(async () => { var documentDatabase = Database; if (documentDatabase == null) return; using (LogContext.WithDatabase(documentDatabase.Name)) { try { var dataDumper = new DataDumper(documentDatabase); var localBackupConfigs = exportConfigs; var localBackupStatus = exportStatus; if (localBackupConfigs == null) return; if (fullBackup == false) { var currentEtags = dataDumper.FetchCurrentMaxEtags(); // No-op if nothing has changed if (currentEtags.LastDocsEtag == localBackupStatus.LastDocsEtag && currentEtags.LastAttachmentsEtag == localBackupStatus.LastAttachmentsEtag && currentEtags.LastDocDeleteEtag == localBackupStatus.LastDocsDeletionEtag && currentEtags.LastAttachmentsDeleteEtag == localBackupStatus.LastAttachmentDeletionEtag) { return; } } var backupPath = localBackupConfigs.LocalFolderName ?? Path.Combine(documentDatabase.Configuration.DataDirectory, "PeriodicExport-Temp"); if (fullBackup) { // create filename for full dump backupPath = Path.Combine(backupPath, SystemTime.UtcNow.ToString("yyyy-MM-dd-HH-mm", CultureInfo.InvariantCulture) + ".ravendb-full-dump"); if (File.Exists(backupPath)) { var counter = 1; while (true) { backupPath = Path.Combine(Path.GetDirectoryName(backupPath), SystemTime.UtcNow.ToString("yyyy-MM-dd-HH-mm", CultureInfo.InvariantCulture) + " - " + counter + ".ravendb-full-dump"); if (File.Exists(backupPath) == false) break; counter++; } } } var smugglerOptions = (fullBackup) ? new SmugglerOptions() : new SmugglerOptions { StartDocsEtag = localBackupStatus.LastDocsEtag, StartAttachmentsEtag = localBackupStatus.LastAttachmentsEtag, StartDocsDeletionEtag = localBackupStatus.LastDocsDeletionEtag, StartAttachmentsDeletionEtag = localBackupStatus.LastAttachmentDeletionEtag, Incremental = true, ExportDeletions = true }; var exportResult = await dataDumper.ExportData(new SmugglerExportOptions { ToFile = backupPath }, smugglerOptions); if (fullBackup == false) { // No-op if nothing has changed if (exportResult.LastDocsEtag == localBackupStatus.LastDocsEtag && exportResult.LastAttachmentsEtag == localBackupStatus.LastAttachmentsEtag && exportResult.LastDocDeleteEtag == localBackupStatus.LastDocsDeletionEtag && exportResult.LastAttachmentsDeleteEtag == localBackupStatus.LastAttachmentDeletionEtag) { logger.Info( "Periodic export returned prematurely, nothing has changed since last export"); return; } } try { if (!localBackupConfigs.Disabled) { UploadToServer(exportResult.FilePath, localBackupConfigs, fullBackup); } } finally { // if user did not specify local folder we delete temporary file. if (String.IsNullOrEmpty(localBackupConfigs.LocalFolderName)) { IOExtensions.DeleteFile(exportResult.FilePath); } } if (fullBackup) { localBackupStatus.LastFullBackup = SystemTime.UtcNow; } else { localBackupStatus.LastAttachmentsEtag = exportResult.LastAttachmentsEtag; localBackupStatus.LastDocsEtag = exportResult.LastDocsEtag; localBackupStatus.LastDocsDeletionEtag = exportResult.LastDocDeleteEtag; localBackupStatus.LastAttachmentDeletionEtag = exportResult.LastAttachmentsDeleteEtag; localBackupStatus.LastBackup = SystemTime.UtcNow; } var ravenJObject = JsonExtensions.ToJObject(localBackupStatus); ravenJObject.Remove("Id"); var putResult = documentDatabase.Documents.Put(PeriodicExportStatus.RavenDocumentKey, null, ravenJObject, new RavenJObject(), null); // this result in exportStatus being refreshed localBackupStatus = exportStatus; if (localBackupStatus != null) { if (localBackupStatus.LastDocsEtag.IncrementBy(1) == putResult.ETag) // the last etag is with just us localBackupStatus.LastDocsEtag = putResult.ETag; // so we can skip it for the next time } } catch (ObjectDisposedException) { // shutting down, probably } catch (Exception e) { logger.ErrorException("Error when performing periodic export", e); Database.AddAlert(new Alert { AlertLevel = AlertLevel.Error, CreatedAt = SystemTime.UtcNow, Message = e.Message, Title = "Error in Periodic Export", Exception = e.ToString(), UniqueKey = "Periodic Export Error", }); } } }) .Unwrap(); currentTask.ContinueWith(_ => { currentTask = null; }); } }