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
AppData.WriteText (AppDataStorage/AppData.cs:142-167) aims to be a safe write. It writes .tmp, copies the main file to .bk, deletes the main file, moves .tmp into place, then deletes .bk. That protects against the process crashing, but not against a power loss or OS crash:
Nothing is flushed to disk.FileSystem.File.WriteAllText(tempFilePath, text) only hands the data to the OS page cache. The Delete/Move/Delete calls that follow change metadata only. NTFS and ext4 journal metadata but not file data, so the rename and deletes can reach disk before the .tmp contents do. A crash in that window leaves <name>.json zero-length (or NUL-filled), and .bk is already gone.
The destination is deleted before the move, which bypasses ext4's auto_da_alloc safety net. ext4 flushes data when a file is renamed over an existing file. Here the move lands on a path that no longer exists, so that safety net never triggers.
An empty file is treated as "no settings".ReadText (AppData.cs:176-203) only runs the .tmp/.bk recovery path on FileNotFoundException. For a file that exists but is empty, it returns "". AppData<T>.LoadOrCreate then calls newAppData.Save() when string.IsNullOrEmpty(jsonString), which overwrites the file with defaults. Nothing is logged and nothing is thrown.
Failure scenario
The app saves: Save(), or QueueSave() followed by the debounced or process-exit flush. WriteText completes and deletes .bk.
Within the OS writeback interval (typically a few seconds), the machine loses power, blue-screens, or the laptop battery dies. The exit-time flush right before shutdown is a common point to be in this window.
After reboot, <type>.json exists with length 0 or NUL bytes, and there is no .bk or .tmp.
Write the temp file through a stream and call Flush(flushToDisk: true) before any metadata change.
Replace atomically instead of delete-then-move: use File.Replace(temp, filePath, bk) when the destination exists, or File.Move(temp, filePath, overwrite: true). The destination is then never missing, and ext4's rename-over heuristic applies.
What's wrong
AppData.WriteText(AppDataStorage/AppData.cs:142-167) aims to be a safe write. It writes.tmp, copies the main file to.bk, deletes the main file, moves.tmpinto place, then deletes.bk. That protects against the process crashing, but not against a power loss or OS crash:FileSystem.File.WriteAllText(tempFilePath, text)only hands the data to the OS page cache. TheDelete/Move/Deletecalls that follow change metadata only. NTFS and ext4 journal metadata but not file data, so the rename and deletes can reach disk before the.tmpcontents do. A crash in that window leaves<name>.jsonzero-length (or NUL-filled), and.bkis already gone.auto_da_allocsafety net. ext4 flushes data when a file is renamed over an existing file. Here the move lands on a path that no longer exists, so that safety net never triggers.ReadText(AppData.cs:176-203) only runs the.tmp/.bkrecovery path onFileNotFoundException. For a file that exists but is empty, it returns"".AppData<T>.LoadOrCreatethen callsnewAppData.Save()whenstring.IsNullOrEmpty(jsonString), which overwrites the file with defaults. Nothing is logged and nothing is thrown.Failure scenario
Save(), orQueueSave()followed by the debounced or process-exit flush.WriteTextcompletes and deletes.bk.<type>.jsonexists with length 0 or NUL bytes, and there is no.bkor.tmp.LoadOrCreategets"", saves defaults over the file, and returns defaults. All user settings are lost without trace. The NUL-filled case goes to theJsonExceptiondelete path tracked in A settings file that fails to deserialize is deleted outright and replaced with defaults, with no backup left to recover from #314 and ends the same way.Suggested fix
Flush(flushToDisk: true)before any metadata change.File.Replace(temp, filePath, bk)when the destination exists, orFile.Move(temp, filePath, overwrite: true). The destination is then never missing, and ext4's rename-over heuristic applies.ReadText/LoadOrCreate, treat an existing file that is empty or whitespace-only as unreadable, not as "no file". Try the.tmp/.bkcandidates, and archive rather than overwrite, consistent with A settings file that fails to deserialize is deleted outright and replaced with defaults, with no backup left to recover from #314.Acceptance criteria
MockFileSystemtest where the main file exists but is zero-length and a valid.bkexists loads the backup's values, not defaults.WriteTextflushes the temp file to disk before replacing the destination.Related but distinct: #314 (deleting the file on
JsonException) and #310 (closed; process-crash recovery ordering between.tmpand.bk).