What's wrong
README.md:37 lists "✅ Thread-safe operations". However, Navigation/Services/NavigationStack.cs mutates _items (a List<T>) and _currentIndex with no synchronization:
NavigateTo (≈43-70)
GoBack/GoForward (≈73-100)
Clear (≈103-110)
RestoreState (≈171-180)
SimpleUndoRedoProvider (≈234-311) has the same problem with _undoStack/_redoStack.
Reproduction
var nav = new Navigation<NavigationItem>(new SimpleUndoRedoProvider());
Parallel.For(0, 2000, i => nav.NavigateTo(new NavigationItem("p" + i, "P" + i)));
This failed in 50 of 50 runs with AggregateException → ArgumentException: Destination array was not long enough.... The exception comes from the [.. _items] snapshot (NavigationStack.cs ≈51/65) racing a concurrent List<T> resize. Even in runs where nothing throws, interleaved RemoveRange/Add/_currentIndex updates can leave the index pointing outside the history, or leave history entries lost.
Why it matters
Consumers who rely on the documented guarantee can get crashes or silently wrong back/forward state. This is most likely when navigation is driven from multiple UI and background threads.
Suggested fix
Choose one:
- Make it true: add a private lock around every read and write of
_items/_currentIndex, including snapshots, GetHistory and LoadStateAsync. Raise NavigationChanged outside the lock. Add the same protection to SimpleUndoRedoProvider.
- Drop the claim: document
Navigation<T> as single-threaded (callers must synchronize) and remove the README bullet.
Acceptance criteria
The parallel repro above completes without throwing, and ends with History.Count == 2000 and a valid CurrentIndex. Alternatively, the README no longer claims thread safety.
What's wrong
README.md:37lists "✅ Thread-safe operations". However,Navigation/Services/NavigationStack.csmutates_items(aList<T>) and_currentIndexwith no synchronization:NavigateTo(≈43-70)GoBack/GoForward(≈73-100)Clear(≈103-110)RestoreState(≈171-180)SimpleUndoRedoProvider(≈234-311) has the same problem with_undoStack/_redoStack.Reproduction
This failed in 50 of 50 runs with
AggregateException→ArgumentException: Destination array was not long enough.... The exception comes from the[.. _items]snapshot (NavigationStack.cs ≈51/65) racing a concurrentList<T>resize. Even in runs where nothing throws, interleavedRemoveRange/Add/_currentIndexupdates can leave the index pointing outside the history, or leave history entries lost.Why it matters
Consumers who rely on the documented guarantee can get crashes or silently wrong back/forward state. This is most likely when navigation is driven from multiple UI and background threads.
Suggested fix
Choose one:
_items/_currentIndex, including snapshots,GetHistoryandLoadStateAsync. RaiseNavigationChangedoutside the lock. Add the same protection toSimpleUndoRedoProvider.Navigation<T>as single-threaded (callers must synchronize) and remove the README bullet.Acceptance criteria
The parallel repro above completes without throwing, and ends with
History.Count == 2000and a validCurrentIndex. Alternatively, the README no longer claims thread safety.