Skip to content

Concurrent NavigateTo throws ArgumentException and corrupts history, despite README's "Thread-safe operations" #57

Description

@matt-edmondson

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions