Fix multiple backups running at once

- Backups now have a cooldown, default of 30 seconds
- Backups now have a lock, in the event of a TOC/TOU race condition, the lock will stop duplicate backups
This commit is contained in:
DrakiaXYZ
2025-10-26 10:17:26 -07:00
parent 7d065cfb16
commit 4e73778920
3 changed files with 71 additions and 41 deletions
@@ -1,6 +1,7 @@
{
"enabled": true,
"maxBackups": 15,
"backupCooldown": 30,
"directory": "./user/profiles/backups",
"backupInterval": {
"enabled": false,
@@ -13,6 +13,9 @@ public record BackupConfig : BaseConfig
[JsonPropertyName("maxBackups")]
public int MaxBackups { get; set; }
[JsonPropertyName("backupCooldown")]
public int BackupCooldown { get; set; }
[JsonPropertyName("directory")]
public string Directory { get; set; } = string.Empty;
@@ -21,6 +21,9 @@ public class BackupService
// Runs Init() every x minutes
protected Timer _backupIntervalTimer;
protected SemaphoreSlim BackupLock = new SemaphoreSlim(1, 1);
protected long LastBackupTimestamp;
protected readonly FileUtil FileUtil;
protected readonly JsonUtil JsonUtil;
protected readonly ISptLogger<BackupService> Logger;
@@ -78,7 +81,7 @@ public class BackupService
}
/// <summary>
/// Initializes the backup process. <br />
/// Run the backup process. <br />
/// This method orchestrates the profile backup service. Handles copying profiles to a backup directory and cleaning
/// up old backups if the number exceeds the configured maximum.
/// </summary>
@@ -89,6 +92,24 @@ public class BackupService
return;
}
// Make sure we don't back up too often by using a configurable Cooldown
var currentTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
if (currentTimestamp < LastBackupTimestamp + BackupConfig.BackupCooldown)
{
return;
}
LastBackupTimestamp = currentTimestamp;
// If the backup lock is already locked, skip backup. This is to stop TOC/TOU race conditions above
// Passing 0 is a non-blocking Wait, will return false if the lock can't be acquired
bool lockAcquired = await BackupLock.WaitAsync(0);
if (!lockAcquired)
{
return;
}
try
{
var targetDir = GenerateBackupTargetDir();
// Fetch all profiles in the profile directory.
@@ -147,6 +168,11 @@ public class BackupService
CleanBackups();
}
finally
{
BackupLock.Release();
}
}
/// <summary>
/// Check to see if the backup service is enabled via the config.