Repository navigation
Darling's viewer silently replaces a corrupt settings file with defaults, on a click #2434
Description
Activity
- added a commit that references this issue
on Aug 21, 2026 Fixed on
devby #2439. The premise held in full —Debug.WriteLinereally does carry[Conditional("DEBUG")], so in a shipped build the failure was not merely unlogged but unreportable; neitherSavemerged; and the load-then-save pairs are where the issue said they were.There were three stores, not two, and the third is the worst.
ViewerServerStore— the monitored-server registry — had the identical defect with an extra edge:catch (Exception ex) { /* A corrupt or unreadable registry must never block startup — begin empty and log. */ return new List<ViewerServerEntry>(); }
Beginning empty is defensible; the next
Save()writing that empty list over the registry is not. So one favourite toggle against an unreadable file replaced the server list. That store was found because the guard was written as a category rather than to the two names in this issue — a guard covering two of three would have been exactly the scenario-shaped fix it exists to prevent.The guard is shared, not duplicated:
SettingsFileGuardmoved toPerformanceMonitor.Common/Services/, referenced by Lite and the viewer, stillnet10.0with no WPF and no logger. Two members added for the whole-object shape;Read/RootForWritekeep Lite's merge semantics unchanged, and all nine of #2425's pins still pass against the moved guard.Two things the issue could not have known, both caught in the doing:
ViewerLogger.Logdrops anything enqueued beforeInitialize, unlike Lite'sAppLogger— which #2428 had relied on.App.OnStartupreads the settings file for the theme beforeViewerLogger.Initialize(), so reporting from the store alone would have dropped the earliest and most useful diagnostic.Initializemoved ahead of the theme read.And the registry's root is a JSON array, so the object-shaped read had to stop applying the "root must be an object" rule — that rule belongs to the merge path. Borrowing it would have quarantined a copy of every healthy registry on every toggle, which is a new bug wearing the fix's clothes. There is a control test for that exact one-line mistake.
Proved in Release specifically, so
Debug.WriteLineis genuinely absent: dev 3 passed / 10 failed on identical bodies, and the full committed file does not compile against dev at all — 13 errors, because the contract does not exist there.Two residues split out: #2444 (per-key reads, so a message can name the offending key — the viewer's whole-object round trip has the same limitation one level up) and the note in #2444 about the sample-key extractor constraint.
- added 3 commits that reference this issue
on Aug 24, 2026
#2425 fixed this in Lite. The Darling viewer has the same defect in both of its JSON settings stores, and it is worse there: it loses the file on an ordinary click, with no diagnostic at all in a shipped build.
Both stores swallow the difference between "no file" and "corrupt file"
Darling/PerformanceMonitor.Darling.Viewer/ViewerAppSettings.csand.../ViewerPreferences.cshave the identical shape:Debug.WriteLinecompiles out of a Release build, so in the viewer anyone actually runs there is no record whatsoever — not a log line, not a dialog, nothing.ViewerPreferences.Load's comment is explicit that this is by design ("the viewer writes no application log of its own"), which is exactly the reasoning #2425 found to be wrong in Lite: the absence of a place to report to is a reason to add one, not a reason to stay silent.And Save destroys the file, on a click nobody would call a save
Neither
Savemerges. Both serialize the whole in-memory object over the file:Lite's pre-#2425 writers at least parsed the existing document first, so a corrupt settings.json made the save fail rather than overwrite — the user had to delete the file themselves to lose it. The viewer has no such accident protecting it. Two handlers in
MainWindow.xaml.csdo load-then-save directly:and the Overview sort selector at ~L1086 does the same. So on a viewer whose
viewer-settings.jsonhas a trailing comma in it, changing the time-display dropdown once silently replaces every setting in the file with a default.SettingsWindow.xaml.csL1509 (_appSettingsStore.Save(_appSettings)) is the same story from the Settings window.ViewerControlPlaneMigrationis fed from_appSettingsStore.Load()too (MainWindow.ServerManagement.csL564), so a corrupt file also silently feeds the migration defaults rather than the operator's configured control plane.What to do
The Lite shape ports over, and most of the thinking is already done in
Lite/Services/SettingsFileGuard.cs:viewer-settings.json.unreadable-<timestamp>before the firstSavereplaces it, and refuse the write if even the copy cannot be made.ViewerLoggerexists — theDebug.WriteLinecomment predates it, and is worth re-checking rather than trusted.Whether
SettingsFileGuardshould move to a shared project or be mirrored is a real decision, not an obvious one: Lite's file is aJsonNodedocument merged key by key, while the viewer's two are whole-objectJsonSerializerround-trips, so only the read/classify/quarantine half is common. The quarantine half is the half that matters.Found by the review bot on #2428. Related: #2425, #2433.