What's wrong
JsonFilePersistenceProvider.SaveStateAsync writes straight over the live file:
|
public async Task SaveStateAsync(INavigationState<T> state, CancellationToken cancellationToken = default) |
|
{ |
|
Ensure.NotNull(state); |
|
|
|
string? directory = Path.GetDirectoryName(_filePath); |
|
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) |
|
{ |
|
Directory.CreateDirectory(directory); |
|
} |
|
|
|
string json = JsonSerializer.Serialize(state, _jsonOptions); |
|
await File.WriteAllTextAsync(_filePath, json, cancellationToken).ConfigureAwait(false); |
|
} |
File.WriteAllTextAsync truncates the file before it writes. If the write is interrupted, the previously good state is already gone. That happens when:
- the
CancellationToken the method accepts is cancelled,
- the process dies mid-write, or
- the disk fills up.
LoadStateAsync then hits a JsonException and returns null, so the saved history is lost without any error. Meanwhile HasSavedStateAsync keeps reporting true.
Repro (net10.0)
var provider = new JsonFilePersistenceProvider<NavigationItem>(path);
var nav = new Navigation<NavigationItem>(null, provider);
for (int i = 0; i < 100_000; i++) nav.NavigateTo(new NavigationItem($"p{i}", $"Page {i}"));
await nav.SaveStateAsync(); // good save
nav.NavigateTo(new NavigationItem("last", "Last"));
using var cts = new CancellationTokenSource();
var t = nav.SaveStateAsync(cts.Token); cts.CancelAfter(1);
try { await t; } catch (OperationCanceledException) { }
var fresh = new Navigation<NavigationItem>(null, provider);
await fresh.LoadStateAsync();
Observed over three runs:
first save ok: 14166702 bytes
second save cancelled
file now: 2809856 bytes (696320 and 4956160 on the other runs)
LoadStateAsync -> False, Count=0, HasSavedState=True
Suggested fix / acceptance criteria
- Write to a temporary file in the same directory and flush it, then atomically replace the target (
File.Replace, or File.Move(tmp, path, overwrite: true)).
- Delete the temporary file if the write is cancelled or fails.
- Test: cancelling a save leaves the previously saved state loadable and unchanged, and leaves no temporary file behind.
- A successful save still produces the same JSON as today.
What's wrong
JsonFilePersistenceProvider.SaveStateAsyncwrites straight over the live file:Navigation/Navigation/Services/JsonFilePersistenceProvider.cs
Lines 43 to 55 in d4bd627
File.WriteAllTextAsynctruncates the file before it writes. If the write is interrupted, the previously good state is already gone. That happens when:CancellationTokenthe method accepts is cancelled,LoadStateAsyncthen hits aJsonExceptionand returnsnull, so the saved history is lost without any error. MeanwhileHasSavedStateAsynckeeps reportingtrue.Repro (net10.0)
Observed over three runs:
Suggested fix / acceptance criteria
File.Replace, orFile.Move(tmp, path, overwrite: true)).