What's wrong
Every QueueSave() resets SaveQueuedTime (AppDataStorage/AppData.cs:250), and SaveIfRequired() only saves once DateTime.UtcNow - SaveQueuedTime > SaveDebounceTime (IsDoubounceTimeElapsed, AppData.cs:474). Nothing caps how long a queued save can be put off, so as long as saves keep being queued less than 3 s apart, SaveIfRequired() never writes.
Why it matters
The README recommends QueueSave() for "frequent updates (e.g., UI-driven changes)" and calling SaveIfRequired() from a game loop or timer. That is exactly the pattern that starves the save:
- dragging a window or slider
- typing into a bound text field
- any app that queues a save each frame while something animates
Nothing reaches disk until input pauses for 3 s. A clean exit still flushes through the dispose-on-exit hook, but a crash, kill or power loss loses every change since the last idle gap. For a long editing session with no pause, that is all of it.
Reproduction
The loop below calls QueueSave() and SaveIfRequired() every 100 ms for 8 s:
var s = MySettings.Get();
var sw = Stopwatch.StartNew();
while (sw.Elapsed < TimeSpan.FromSeconds(8))
{
s.Theme = "dark";
s.QueueSave();
s.SaveIfRequired();
Thread.Sleep(100);
}
// file on disk still has Theme = "light"; 0 saves happened
Observed result: saves during 8s of continuous QueueSave: 0, and the file on disk is unchanged.
Suggested fix
Add a maximum wait alongside the debounce:
Acceptance criteria
- A test with continuous
QueueSave() + SaveIfRequired() calls, using a mockable clock or short intervals, shows a save happening within the max-wait bound.
- The existing behaviour is unchanged: a single queued save still waits for the debounce.
Related: #324 (wall-clock debounce). This issue is about starvation, not clock steps.
What's wrong
Every
QueueSave()resetsSaveQueuedTime(AppDataStorage/AppData.cs:250), andSaveIfRequired()only saves onceDateTime.UtcNow - SaveQueuedTime > SaveDebounceTime(IsDoubounceTimeElapsed,AppData.cs:474). Nothing caps how long a queued save can be put off, so as long as saves keep being queued less than 3 s apart,SaveIfRequired()never writes.Why it matters
The README recommends
QueueSave()for "frequent updates (e.g., UI-driven changes)" and callingSaveIfRequired()from a game loop or timer. That is exactly the pattern that starves the save:Nothing reaches disk until input pauses for 3 s. A clean exit still flushes through the dispose-on-exit hook, but a crash, kill or power loss loses every change since the last idle gap. For a long editing session with no pause, that is all of it.
Reproduction
The loop below calls
QueueSave()andSaveIfRequired()every 100 ms for 8 s:Observed result:
saves during 8s of continuous QueueSave: 0, and the file on disk is unchanged.Suggested fix
Add a maximum wait alongside the debounce:
FirstQueuedTimeonly whenHasQueuedSavegoes from false to true.SaveIfRequired()also save whennow - FirstQueuedTimeexceeds a cap, for example 10–30 s, and consider making the cap configurable next toSaveDebounceTime.Stopwatch.GetTimestamp()/Environment.TickCount64). That also covers the clock-step half of A change queued with QueueSave is never written when the wall clock steps backwards (or does not tick) after the previous save, including at process exit #324.Acceptance criteria
QueueSave()+SaveIfRequired()calls, using a mockable clock or short intervals, shows a save happening within the max-wait bound.Related: #324 (wall-clock debounce). This issue is about starvation, not clock steps.