You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A saved value that a semantic type rejects makes LoadOrCreate throw ArgumentException, and Get() then throws on every call for the rest of the process #315
A value that is well-formed JSON but fails a semantic type's validation doesn't raise JsonException. RoundTripStringJsonConverterFactory passes through the ArgumentException that the semantic type throws, so it escapes LoadOrCreate completely.
Get() makes it worse. InternalState is a Lazy<T> in the default ExecutionAndPublication mode, which caches the exception. After the first failure, every AppData<T>.Get(), QueueSave() and SaveIfRequired() for the rest of the process rethrows the same exception. Nothing on disk changes, so the next launch fails the same way. The app can't start until the user finds and deletes the settings file.
Repro (verified against current main, Linux, MockFileSystem)
privatesealedclassProbeData:AppData<ProbeData>{publicstringData{get;set;}="d";publicAbsoluteDirectoryPath?Path{get;set;}}// settings file on disk:// {"Data":"keep","Path":"C:/Users/me/Documents"}AppData<ProbeData>.LoadOrCreate();// throws System.ArgumentException:// Cannot convert "C:/Users/me/Documents" to AbsoluteDirectoryPathAppData<ProbeData>.Get();// throws System.ArgumentExceptionAppData<ProbeData>.Get();// throws System.ArgumentException again (cached by Lazy)
The file is left untouched, so every later launch fails in the same place.
Why it matters
The saved text is valid JSON, so any of these can trigger it without disk corruption:
A settings file holding a path is synced or copied between Windows and Linux/macOS (dotfiles, roaming profiles). A Windows drive-letter path isn't an AbsoluteDirectoryPath on Linux.
An app update tightens validation on a semantic string type, and values saved by the old version no longer pass.
A user hand-edits a value into something the type rejects.
In each case a settings problem becomes a startup crash loop, when the app should get recovered or default settings. JsonException is already handled, so the gap is only in which exception types count as "could not deserialize".
Suggested fix
Handle deserialization failures from converters the same way as JsonException. At minimum catch ArgumentException, FormatException and NotSupportedException from JsonSerializer.Deserialize (or catch Exception around the deserialize call alone, excluding OutOfMemoryException-class failures), and route them to the existing recovery path. A settings file that fails to deserialize is deleted outright and replaced with defaults, with no backup left to recover from #314 covers making that path archive the file rather than delete it.
Consider making InternalState recoverable: use LazyThreadSafetyMode.PublicationOnly or a manually locked field, so that one failed load doesn't poison Get() for the rest of the process.
Acceptance: with the settings file above, LoadOrCreate() returns an instance (defaults, or recovered content) without throwing, Get() succeeds, and a regression test covers a semantic-type validation failure alongside the existing corrupt-JSON tests.
What's wrong
AppData<T>.LoadOrCreate(AppDataStorage/AppData.cs) only recovers fromJsonException:A value that is well-formed JSON but fails a semantic type's validation doesn't raise
JsonException.RoundTripStringJsonConverterFactorypasses through theArgumentExceptionthat the semantic type throws, so it escapesLoadOrCreatecompletely.Get()makes it worse.InternalStateis aLazy<T>in the defaultExecutionAndPublicationmode, which caches the exception. After the first failure, everyAppData<T>.Get(),QueueSave()andSaveIfRequired()for the rest of the process rethrows the same exception. Nothing on disk changes, so the next launch fails the same way. The app can't start until the user finds and deletes the settings file.Repro (verified against current
main, Linux,MockFileSystem)The file is left untouched, so every later launch fails in the same place.
Why it matters
The saved text is valid JSON, so any of these can trigger it without disk corruption:
AbsoluteDirectoryPathon Linux.In each case a settings problem becomes a startup crash loop, when the app should get recovered or default settings.
JsonExceptionis already handled, so the gap is only in which exception types count as "could not deserialize".Suggested fix
JsonException. At minimum catchArgumentException,FormatExceptionandNotSupportedExceptionfromJsonSerializer.Deserialize(or catchExceptionaround the deserialize call alone, excludingOutOfMemoryException-class failures), and route them to the existing recovery path. A settings file that fails to deserialize is deleted outright and replaced with defaults, with no backup left to recover from #314 covers making that path archive the file rather than delete it.InternalStaterecoverable: useLazyThreadSafetyMode.PublicationOnlyor a manually locked field, so that one failed load doesn't poisonGet()for the rest of the process.Acceptance: with the settings file above,
LoadOrCreate()returns an instance (defaults, or recovered content) without throwing,Get()succeeds, and a regression test covers a semantic-type validation failure alongside the existing corrupt-JSON tests.