Skip to content

v0.2.23

Latest

Choose a tag to compare

@zhuxiujia zhuxiujia released this 25 Sep 06:19

Fixes the same class of bug that 63d5df6 addressed in SyncVec::iter(): an unconditional
promote() on a read path publishes a fresh snapshot and retires the previous one, so the
retired allocation is kept alive until the container is dropped.

Fixed

  • SyncHashMap::iter(), SyncIndexMap::iter(), SyncBtreeMap::iter() now publish a fresh
    snapshot only when dirty has un-published writes (amended), exactly like
    SyncVec::iter(). Previously every call retired a whole snapshot and paid an O(n) clone
    under the lock just to throw the previous snapshot away.
  • SyncVec::get_uncheck() now uses the same lazy path as get(): lock-free on a hit,
    promotion only when the index is still pending in dirty.
  • clear() on all four containers only promotes when dirty was non-empty.
  • IterMut::drop() only promotes when an element was actually handed out, so dropping an
    unused iter_mut() no longer retires a snapshot.

Notes

  • Writes that completed before a call are still visible; as with Go's sync.Map, a reader
    that misses the amended flag may observe a snapshot that predates a concurrent write.
  • No public API changes.

Tests

  • Added a test-only AtomicSnapshot::retired_len() and 21 unit tests across the four
    containers asserting that repeated iter() / clear() / unused iter_mut() calls do not
    grow the retired snapshot list, while pending writes stay visible and iter_mut() still
    publishes mutations.
  • Full suite green (24 lib tests + integration + doc tests), plus a Miri run with
    --no-default-features --features std-lock.