Skip to content

LockMap can retain an empty lock entry after a cancelled acquirer #165

Description

@pathscale

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:

  1. Cleanup ran earlier, saw count 3, correctly kept the entry.
  2. The cancelled task's clone drops; count returns to 1 (map only).
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions