Skip to content

Commit 1fe7d48

Browse files
JusterZhuclaude
andcommitted
fix: use synchronous save on window close to prevent state loss
Window close handler used SafeFireAndForgetSave (Task.Run), which creates a race: the process may exit before the thread-pool write completes, silently losing window size/position/maximized state on next launch. Changed to synchronous ConfigService.Save() which completes in <1ms for the tiny (<2KB) config file. Also promoted Save() from private to public and added it to IConfigService for this exact shutdown scenario. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent d6dfbe8 commit 1fe7d48

3 files changed

Lines changed: 19 additions & 5 deletions

File tree

src/App.axaml.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public override void OnFrameworkInitializationCompleted()
5454
if (config.WindowMaximized)
5555
mainWindow.WindowState = WindowState.Maximized;
5656

57-
// Save window state on close
57+
// Save window state on close (synchronous to guarantee completion before exit)
5858
var configService = ConfigServiceSingleton.Instance;
5959
mainWindow.Closing += (_, _) =>
6060
{
@@ -69,8 +69,15 @@ public override void OnFrameworkInitializationCompleted()
6969
config.WindowHeight = mainWindow.Height;
7070
}
7171

72-
// Fire-and-forget save (exceptions logged to Trace, not lost)
73-
ConfigService.SafeFireAndForgetSave(configService);
72+
// Synchronous save: config is tiny (<2KB), completes in <1ms.
73+
// Must NOT be fire-and-forget — the process exits after Close and
74+
// a Task.Run might not complete in time.
75+
try { configService.Save(); }
76+
catch (Exception ex)
77+
{
78+
System.Diagnostics.Trace.WriteLine(
79+
$"[GeneralUpdate.Tools] Config save on close failed: {ex.Message}");
80+
}
7481
};
7582

7683
desktop.MainWindow = mainWindow;

src/Configuration/ConfigService.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,12 @@ public void Load()
9898
}
9999
}
100100

101-
/// <summary>Synchronous save for internal use during Load().</summary>
102-
private void Save()
101+
/// <summary>
102+
/// Synchronous save. Blocks the calling thread until the file is written.
103+
/// Suitable for shutdown/flush scenarios where fire-and-forget would risk
104+
/// the process exiting before the write completes.
105+
/// </summary>
106+
public void Save()
103107
{
104108
_saveLock.Wait();
105109
try

src/Configuration/IConfigService.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ public interface IConfigService
1616
/// <summary>Load configuration from disk. Called once at startup.</summary>
1717
Task LoadAsync();
1818

19+
/// <summary>Persist current configuration to disk synchronously. Safe for shutdown.</summary>
20+
void Save();
21+
1922
/// <summary>Persist current configuration to disk (with automatic backup).</summary>
2023
Task SaveAsync();
2124

0 commit comments

Comments
 (0)