Context
From the 2026-07-27 concurrency-fix series (patch 0001). LockMap::remove_with_lock_check (src/lock/map.rs) only removes an unlocked entry when Arc::strong_count(&lock) == 2 (the map entry + cleanup's local clone). Any higher count means some task has taken the Arc out of get_or_insert_with and is about to register an operation on it; removing then would let the next caller build a second lock for the same row and the two would not serialize — that was part of the original lost-update race.
The retention gap (documented in code, this issue tracks the real fix)
If a task is cancelled between cloning the Arc and registering its operation:
- Cleanup ran earlier, saw count 3, correctly kept the entry.
- The cancelled task's clone drops; count returns to 1 (map only).
- Nothing re-triggers cleanup — the unlocked, unused entry stays in the map until the next operation on the same key drops its guard.
Bounded: at most one empty Arc<RwLock<RowLock>> per abandoned key; self-healing on the next touch of that key; mutual exclusion unaffected. But workloads that touch many unique keys exactly once with cancellations (e.g. timeouts around row ops) can accumulate entries.
Suggested direction
Arc::strong_count is a proxy for "a caller is between lookup and registration". The robust replacement is an explicit acquisition handle: get_or_insert_with returns a small RAII guard that decrements a per-entry acquirer count on drop; cleanup removes when !is_locked() && acquirers == 0. Cancellation then triggers the handle's Drop, which can also re-attempt removal itself. Alternative cheap options: periodic sweep of unlocked entries, or accepting retention permanently and deleting remove_with_lock_check (simplest, trades memory for simplicity).
Note the read-path fast lock in get_or_insert_with (clone under read guard) interacts with whatever is chosen: cleanup holds the map write lock, so the count/handle must be established before the read guard drops.
Verification hints
The lost-update tests (tests/worktable/in_place.rs::test_update_val_by_id_two_thread etc.) are the mutual-exclusion canaries — 0/150 runs at the time of the series. A retention test would spawn+abort tasks mid-acquisition (tokio JoinHandle::abort between get_or_insert_with and lock registration) and assert map size.
Context
From the 2026-07-27 concurrency-fix series (patch 0001).
LockMap::remove_with_lock_check(src/lock/map.rs) only removes an unlocked entry whenArc::strong_count(&lock) == 2(the map entry + cleanup's local clone). Any higher count means some task has taken the Arc out ofget_or_insert_withand is about to register an operation on it; removing then would let the next caller build a second lock for the same row and the two would not serialize — that was part of the original lost-update race.The retention gap (documented in code, this issue tracks the real fix)
If a task is cancelled between cloning the Arc and registering its operation:
Bounded: at most one empty
Arc<RwLock<RowLock>>per abandoned key; self-healing on the next touch of that key; mutual exclusion unaffected. But workloads that touch many unique keys exactly once with cancellations (e.g. timeouts around row ops) can accumulate entries.Suggested direction
Arc::strong_countis a proxy for "a caller is between lookup and registration". The robust replacement is an explicit acquisition handle:get_or_insert_withreturns a small RAII guard that decrements a per-entry acquirer count on drop; cleanup removes when!is_locked() && acquirers == 0. Cancellation then triggers the handle's Drop, which can also re-attempt removal itself. Alternative cheap options: periodic sweep of unlocked entries, or accepting retention permanently and deletingremove_with_lock_check(simplest, trades memory for simplicity).Note the read-path fast lock in
get_or_insert_with(clone under read guard) interacts with whatever is chosen: cleanup holds the map write lock, so the count/handle must be established before the read guard drops.Verification hints
The lost-update tests (
tests/worktable/in_place.rs::test_update_val_by_id_two_threadetc.) are the mutual-exclusion canaries — 0/150 runs at the time of the series. A retention test would spawn+abort tasks mid-acquisition (tokioJoinHandle::abortbetweenget_or_insert_withand lock registration) and assert map size.