Skip to content

fix(store): wake in-flight slow-write waiters when any upload completes the digest - #36

Open
b7r6 wants to merge 2 commits into
mainfrom
b7r6/fastslow-inflight-waiter-fresh-upload
Open

b7r6 wants to merge 2 commits into
mainfrom
b7r6/fastslow-inflight-waiter-fresh-upload

Conversation

@b7r6

@b7r6 b7r6 commented Oct 7, 2026 •

Copy link
Copy Markdown
Owner

What and why

Lane A stability fix from the findings catalog (see nativelink-findings-2026-10-05.csv
on the meta/findings-catalog-2026-10-05 branch), rebased onto OSS main
@bf4b3aa855. This fork PR exists to validate the change on CI that is
representative of upstream PR lanes (public read-only cache, same endpoints)
before any upstream submission.

How was this verified?

The branch carries its own regression tests (fails-without where the fix
class allows it — see the branch's test commits). This PR's purpose is the
full representative CI run; green here rehearses the exact checks an
upstream PR would face.

Risk

Fork-internal validation PR; nothing lands anywhere by merging this. The
underlying fix's risk assessment happens at upstream-submission time.

AI assistance

Agent-assisted pipeline (Claude) for rebase and PR mechanics under my
direction; the fix itself carries verification in its tests.

@github-actions

This comment has been minimized.

@b7r6
b7r6 force-pushed the b7r6/fastslow-inflight-waiter-fresh-upload branch 2 times, most recently from 9c06b74 to 11d0614 Compare October 7, 2026 20:28
b7r6 and others added 2 commits October 7, 2026 23:07
… entry

The inflight-dedup drop-guard in CacheLookupScheduler::add_action was bound
as `let _scope_guard = scope_guard;`, so it fired on *every* exit of the
spawn — including normal completion, after the spawn had already removed its
own entry and resolved the coalesced waiters.

A concurrent request for the same cacheable key, arriving in the window
between the leader's own `inflight_cache_checks.remove(...)` and the spawn's
exit (dropping the guard), inserts a fresh entry. The stale guard then
removes *that* entry, orphaning the second request: its oneshot sender is
dropped, surfacing a spurious "tx hung up" error to the client and forcing
a needless retry.

Fix: defuse the guard via `ScopeGuard::into_inner` the moment the cache
check returns. From that point every arm removes our own entry with no
intervening `.await` (no drop point), so defusing is leak-free and the guard
can no longer clobber a concurrent same-key re-insertion. The guard still
provides true drop-safety for the only window that matters: a spawn death
during the cache-store check, before we claim the entry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…es the digest

An existence check that found a slow-store write in flight parked on that
specific writer's completion mutex. A ByteStream upload that is interrupted
(cancel, half-close, client deadline) is parked for resumption, keeping its
FastSlowStore::update() future -- and thus the locked mutex -- alive. A later
registration for the same key overwrote the map entry, so completing the
digest under a fresh upload id never woke the original waiters: they starved
until their client deadline even though the blob was present and new lookups
succeeded.

Replace the per-writer mutex with a per-key slot shared by all concurrent
writers of that key, broadcasting Pending/Done over a watch channel. Any
writer completing the digest wakes every waiter; the entry is removed (and
waiters released with "not written") only when the last writer deregisters,
which also fixes the stale-remove where a second writer's guard evicted a
slot the first writer still occupied.

Regression: stalled_upload_waiter_released_by_fresh_upload_of_same_digest --
fails on v1.7.3 (waiter hangs past a 2s deadline), passes with the fix; also
asserts a genuinely-absent digest still resolves promptly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@b7r6
b7r6 force-pushed the b7r6/fastslow-inflight-waiter-fresh-upload branch from 11d0614 to 77dc359 Compare October 8, 2026 17:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant