backup-scheduler: don't replay missed daily slots on startup
The wake loop's daily-times branch was firing one missed slot per minute
on service restart. With a schedule like "00:00,04:00,08:00,12:00,16:00,
20:00" and a restart at 19:00, the wrapper would catch up by firing 5
backups at 1-minute intervals -- each a full-world zip of the same
in-memory world. Pure waste: the live world hasn't diverged across the
missed clock slots, the snapshots would be near-identical, and the rapid
save-off/save-on churn impacts player experience.
Source comment already documented the intended behaviour ("Doesn't catch
up if the server was off when a slot passed -- daily/interval backups
don't need replay logic.") but the code didn't implement it.
Fix: in Start(), pre-populate _firedToday with all times <= now so the
wake loop only fires future slots for the rest of today.
Tested live on glados: switched schedule to clock-aligned daily-times
after deploying the fix, zero catch-up backups fired in the 90s window
after restart (vs 5+ before the fix).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -37,11 +37,28 @@ public sealed class BackupScheduler : IDisposable
|
||||
public void Start()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_config.BackupSchedule)) return;
|
||||
if (Parse(_config.BackupSchedule) == default)
|
||||
var parsed = Parse(_config.BackupSchedule);
|
||||
if (parsed == default)
|
||||
{
|
||||
_log($"[backup-scheduler] Invalid backupSchedule '{_config.BackupSchedule}'. Expected 'HH:mm', 'HH:mm,HH:mm', or 'every Nh'/'every Nm'. Disabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
// No-replay startup: mark all of today's already-passed slots as fired so
|
||||
// the wake loop only fires future slots. Without this, a daily-times
|
||||
// schedule like "00:00,04:00,...,20:00" on a service restart at 19:00
|
||||
// would catch up by firing 5 backups at 1-minute intervals (one per
|
||||
// missed slot), each producing a full-world zip of the SAME world state.
|
||||
// For full-snapshot backups that's pure waste -- the live world hasn't
|
||||
// diverged across missed slots.
|
||||
if (parsed.Times is not null)
|
||||
{
|
||||
var nowTime = TimeOnly.FromDateTime(DateTime.Now);
|
||||
_lastFireDay = DateOnly.FromDateTime(DateTime.Now);
|
||||
foreach (var t in parsed.Times)
|
||||
if (t <= nowTime) _firedToday.Add(t);
|
||||
}
|
||||
|
||||
_cts?.Cancel();
|
||||
_cts = new CancellationTokenSource();
|
||||
_loop = Task.Run(() => RunAsync(_cts.Token));
|
||||
|
||||
Reference in New Issue
Block a user