Skip to content

Various changes to improve debugging - #5

Merged
allada merged 1 commit into
masterfrom
minor-changes
Nov 3, 2021
Merged

allada merged 1 commit into
masterfrom
minor-changes

Conversation

@allada

@allada allada commented Nov 3, 2021 •

Copy link
Copy Markdown
Contributor

Also fixes some spelling errors.


This change is Reviewable

Also fixes some spelling errors.
@allada
allada merged commit 3a4d743 into master Nov 3, 2021
@allada
allada deleted the minor-changes branch November 11, 2021 06:22
erneestoc added a commit to erneestoc/nativelink that referenced this pull request May 22, 2026
Reverts the bounded-concurrency construct on this POC branch. After #1 makes construct metadata-only, intra-tree parallelism is bounded by APFS metadata serialization; on a busy worker (inter-action concurrency already saturates the box) 64-wide spawn_blocking fan-out risks oversubscription and stealing cycles from the compiles. Keeping #1/#2/TraceMachina#5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rejuvenile added a commit to rejuvenile/nativelink that referenced this pull request Aug 19, 2026
Six review nits surfaced on 7d37257 (spawn-detach populator).

1. Lock-hold-time micro-regression (nit TraceMachina#1, FIXED)
   `Drop::drop` and `get_loader` were calling `into_owned()` inside the
   `populating_digests` lock. Restored 006f897's pre-spawn-detach
   pattern: clone/owned-allocation hoisted outside the lock so the
   critical section is just the hashmap entry.

2. `Loader = Arc<OnceCell<()>>` is dead weight (nit TraceMachina#2, FIXED)
   The OnceCell was used solely for `Arc::ptr_eq`-based loader
   identity in `LoaderGuard::Drop`; the actual `OnceCell::get_or_try_init`
   call was removed by 7d37257's spawn-detach refactor. Replaced with
   `Arc<()>` and dropped the unused `tokio::sync::OnceCell` import.

3. Misleading send_error / waiter comment (nit TraceMachina#3, FIXED)
   The "best-effort send to the streaming buffer; ignore errors so a
   slow waiter cannot stall the producer" comment claimed
   `streaming_writer.send()` waited on waiters. It does not — `send()`
   only blocks for the brief write-lock on the chunk deque, and waiter
   backpressure is handled by sliding-window eviction inside the
   writer. Re-wrote to match actual semantics.

4. Stale `populate_and_maybe_stream` reference (nit TraceMachina#4, FIXED)
   `LoaderGuard`'s doc comment still pointed at
   `populate_and_maybe_stream` — renamed to `run_producer` by 7d37257.

5. `is_populator_caller` asymmetry rationale (nit TraceMachina#5, DOC)
   Added a TODO(fast_slow_asymmetry) note explaining why the
   populator-vs-waiter error split exists (failpoint suite contract),
   what would let it be unified (post-spawn-detach the waiter
   fallback yields a typed error too), and what blocks doing so today.

6. drain_streaming_buffer terminal-with-data race (nit TraceMachina#6, FIXED)
   The recovery path under `Err(Unavailable) + is_terminal()` used to
   create a fresh reader and treat any `Ok(_)` chunk as proof the
   producer succeeded. This silently swallowed producer errors when
   chunks remained behind in the sliding window. Reachable in
   production: any populate where (a) the drain reader fell behind
   and (b) the producer ended with `send_error` (slow-store mid-
   stream failure, fast-store update error) could return Ok(()) from
   `copy_slow_to_fast` despite the producer failing — leading to
   downstream callers (populate_fast_store{,_unchecked}) treating
   half-populated blobs as ready.

   Fix: refactored `drain_streaming_buffer` to consult
   `streaming_inner.terminal_result()` BEFORE creating a reader. If
   the producer is terminal, return its actual outcome regardless of
   buffered chunks. Eliminates the buggy "data means success" branch
   structurally — the recovery path is now a single-loop break-back
   to terminal check.

   Added `StreamingBlobInner::terminal_result()` accessor (returns
   `Option<Result<(), Error>>`) so the new code path can read the
   structured result without consuming a reader.

   Visibility: `drain_streaming_buffer` is now `#[doc(hidden)] pub`
   so the regression tests can call it with a hand-built
   `StreamingBlobInner`. Same pattern as the existing
   `populating_streaming_inner` test accessor.

Regression tests:

- `drain_streaming_buffer_propagates_terminal_error_over_buffered_data`:
  contract test — terminal=Err with chunks in the sliding window
  must surface the structured error.
- `drain_streaming_buffer_eviction_race_propagates_error`: stress
  test (50 iterations) of the genuine race — producer hammers chunks
  into a 10-byte buffer then send_errors while drain runs concurrently.
  Verified to fail on iter 0 pre-fix (returned Ok despite producer
  error); passes deterministically post-fix.
- `streaming_blob::tests::terminal_result_reflects_writer_outcome`:
  unit test for the new accessor (None / Some(Ok) / Some(Err)).

Verification:
  cargo check --features quic,pprof --all-targets — clean
  cargo test --features quic,failpoints -p nativelink-store \
    --test fast_slow_store_test — 29/29 pass (incl. 2 new)
  cargo test --features quic,failpoints -p nativelink-store \
    --test failpoint_tests — 13/13 pass
  cargo test -p nativelink-util --lib streaming_blob — 16/16 pass
    (incl. 1 new for terminal_result)

Pre-commit gate: code-reviewer / perf-optimizer agents are NOT
available in this sub-agent's tool environment (no Task tool, no
general-purpose subagent). Self-review pass completed against the
CLAUDE.md style and performance criteria. Parent agent should
dispatch retroactive code+perf review before pushing or deploying.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rejuvenile added a commit to rejuvenile/nativelink that referenced this pull request Aug 19, 2026
… handler

Extract the body of the `Update::BlobsInStableStorage` match arm into a
free `handle_blobs_in_stable_storage(state, cas_store, proto_digests)`
function in `local_worker`, and add a test-only
`BlobsAvailableState::new_for_test(fs_store, cas_server_fss)`
constructor. Both are gated on the new `test-utils` feature in
nativelink-worker (which transitively enables nativelink-store/test-utils).

Tests in `nativelink-worker/tests/blobs_in_stable_storage_handler_test.rs`:

  1. `mirror_blob_removed_when_proto_received` — pin two mirror blobs,
     send a `BlobsInStableStorage` proto for one, assert the matching
     blob is gone and the other is untouched. Mutate-tested by commenting
     out `remove_mirror_blobs` inside the handler — test fails with
     `acked digest must be removed from mirror_blobs` on the count
     mismatch (left=2, right=1).
  2. `invalid_proto_digest_does_not_crash_handler` — malformed hex digest
     in the proto must be skipped, but the rest of the batch must still
     be processed.
  3. `handler_no_op_when_cas_server_fss_absent` — None for cas_server_fss
     must not panic; covers the workers that do not run a CAS server.

Also adds a stub for the `download_to_directory_mirror_test` (review TraceMachina#6,
filled in next commit) so cargo's `[[test]] required-features` block can
reference it without a manifest error.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rejuvenile added a commit to rejuvenile/nativelink that referenced this pull request Aug 19, 2026
… comments, lock-ordering stress, register_blobs alloc

Address punch-list items TraceMachina#1-TraceMachina#5 from the 7-reviewer pass on commit
70b2f59:

TraceMachina#1 (CRITICAL) — Tests gated on `test-utils` were silently skipped under
`cargo test --all`. Added a dedicated CI step
`cargo test -p nativelink-store -p nativelink-worker --features test-utils
--tests` so the mirror_durability / blobs_in_stable_storage_handler /
download_to_directory_mirror tests actually run. Added matching
rust_test entries with crate_features=["test-utils"] in nativelink-store/
BUILD.bazel and nativelink-worker/BUILD.bazel for Bazel parity (cargo CI
step is the authoritative coverage; comments document the limitation).

TraceMachina#2 (MAJOR) — pb.rs hand-edits diverged from the .proto: pinned_mirror_digests
(field 13) had abbreviated 6-line doc; mirror_used_bytes (14) had a
truncated 4-line doc. Restored verbatim from worker_api.proto.

TraceMachina#3 (MAJOR) — `register_blobs` collapse introduced a fresh Vec<DigestInfo>
+ 2 extend_from_slice copies. Added `register_blobs_iter` taking
`impl IntoIterator<Item = DigestInfo>`; worker_api_server now chains the
two slices with `.iter().copied().chain(...)` — zero allocation, single
endpoint Arc<str> alloc preserved. Backward-compat: `register_blobs` is
now a thin wrapper.

TraceMachina#4/TraceMachina#5 (MAJOR) — Lock-ordering stress was 200 iters with `yield_now()`
inside tokio tasks, which under inverted lock order would wedge the
ENTIRE tokio worker pool (sync parking_lot waits, not awaits — the
inner `tokio::time::timeout(15s)` could never fire). Rewrote to use
`std::thread::spawn` + `std::sync::mpsc::recv_timeout(15s)` outer guard
so a deadlock surfaces as a clean panic ("AB/BA deadlock detected")
within 15s. Bumped to 10_000 iters, dropped `yield_now()`. Mutate-verified:
inverting `mirror_blobs`/`mirror_changes` lock order in
`snapshot_and_reset_mirror_changes` causes the test to FAIL (not wedge)
in exactly 15s.

cargo check --all-targets clean. cargo test --features test-utils
-p nativelink-store -p nativelink-worker --tests: 14/14 mirror_durability,
3/3 blobs_in_stable_storage_handler, 1/1 download_to_directory_mirror —
all green. Mutate-tests: lock-ordering inversion produces correct
failure mode within timeout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rejuvenile added a commit to rejuvenile/nativelink that referenced this pull request Aug 19, 2026
…diagnosis

Resolves the local_worker.rs conflict with the cleanup chain:
- Cleanup chain extracted Update::BlobsInStableStorage handler into
  free function handle_blobs_in_stable_storage (item TraceMachina#5).
- Instrumentation branch added info!/warn! inside the inline body that
  no longer exists.
- Resolution: keep the call to the extracted handler; place the
  stable_storage_gate info! immediately before the call so it fires
  when the gate is taken.

The arm-entry stable_storage_received info! and the else-branch
stable_storage_gate warn! ("blobs_available_state is None, dropping
unpin") auto-merged correctly outside the conflict region.

The non-local_worker.rs portions (server stable_notify_fire,
stable_storage_broadcast, scheduler stable_storage_dispatch_per_worker)
auto-merged cleanly.

Goal: distinguish whether the BlobsInStableStorage ack pipeline is
broken at (a) server never broadcasts, (b) server broadcasts but
message lost, or (c) worker arm never entered.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rejuvenile added a commit to rejuvenile/nativelink that referenced this pull request Aug 19, 2026
Per testing-czar MISSING-COVERAGE TraceMachina#5 from
.claude/reviews/valkey-pool-521c13d1/testing-czar.md.
`stores.rs:1571` documents "In Cluster mode the value is ignored" but
no test pinned the contract. A future regression that tried to allocate
64-way pools per cluster node would not be caught.

Test `cluster_mode_ignores_connection_pool_size` constructs a cluster-
mode store via the existing MockRedisConnection scaffold and hammers
`RedisManager::get_connection` 64 times (mirroring prod-server.json5's
`connection_pool_size: 64`). Asserts every call returns the SAME uuid.
Production `new_cluster` always wraps a single ClusterConnection in
ClusterRedisManager (single uuid baked in at construction); a future
pool-ization regression would surface as multiple distinct uuids and
fail this test on the second iteration.

Added `connection_manager()` accessor on `RedisStore<C, M>` (#[doc(hidden)])
so the test can issue trait calls directly without mediated Store
operations. Production paths already route through the manager via
the `M: RedisManager<C>` bound; this just exposes it for tests.

Mutation evidence (verified locally):
- ClusterRedisManager::get_connection returning Uuid::new_v4() each call
  (instead of self.uuid) → test fails on the second iteration with
  "cluster manager must yield ONE stable uuid across all
  get_connection calls (no per-call pool round-robin)".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rejuvenile added a commit to rejuvenile/nativelink that referenced this pull request Aug 19, 2026
…eMachina#97 red-team TraceMachina#5)

Per red-team finding TraceMachina#5 on the original TraceMachina#97 PR: `next_bis_broadcast_id`
resets to 1 on every server start, so a worker holding a stale
`BisAck { broadcast_id = 42 }` from a previous server-process can
silently drop an unrelated chunk from the new server's resend buffer
(the new server allocates broadcast_id=42 for an unrelated broadcast,
sees the matching (broadcast_id, sequence) in the buffer, and removes
it). Same end-result as the TraceMachina#89 unpin-leak — wrong chunk gets
released, its pin state stays leaked.

Wire-level fix:
  * proto: add `server_instance_token: uint64` (field 5) to
    `BlobsInStableStorageChunk` and `server_instance_token: uint64`
    (field 3) to `BisAck`. Both default-zero so unset values are
    detectable as legacy / pre-fixup; treated as always-mismatch
    today (per task spec: fleet rev's all-at-once).
  * generated .pb.rs hand-edited to match (consistent with prior
    BIS commits — Bazel proto-gen is the canonical regenerator).

Server-side (`api_worker_scheduler.rs`):
  * `ApiWorkerScheduler` gains a `server_instance_token: u64`
    field, generated at `new_with_locality_map` via `rand::rng()`.
    Loop-until-non-zero so the all-zero token (proto default for
    legacy workers) cannot accidentally match this server's nonce.
    Emits an `info!` at startup so operators see the chosen value.
  * Inject token into every dispatched
    `BlobsInStableStorageChunk.server_instance_token`.
  * `bis_ack_received` now takes a `server_instance_token: u64`
    arg; mismatching acks emit a `debug!` and return early
    (silently dropped — the chunk waits for a same-token ack on the
    next reconnect).
  * `scheduler/Cargo.toml` gains the `rand` dependency
    (workspace-pinned 0.9.0; same default-features-off + thread_rng
    config as the existing nativelink-util usage).

Worker-side (`local_worker.rs`):
  * `handle_bis_chunk` echoes `chunk.server_instance_token`
    into the `BisAck` it emits.

Trait + dispatch wiring:
  * `WorkerScheduler::bis_ack_received` trait signature gains the
    `server_instance_token` arg (default trait impl is a no-op);
    `SimpleScheduler` + `ApiWorkerScheduler`'s trait impl
    forward.
  * `worker_api_server.rs` BisAck dispatch arm passes
    `ack.server_instance_token` through.

Tests:
  * `bis_ack_with_stale_server_token_silently_dropped`
    (api_worker_scheduler.rs): pre-populates buffer, sends acks with
    `valid_token.wrapping_add(1)`, asserts buffer unchanged. Then
    sends matching-token acks, asserts buffer drains. Mutation step
    verified — comment out the `if server_instance_token !=`
    guard, test panics with the SPECIFIC "stale-token ack must NOT
    remove the chunk from the resend buffer" message.
  * `bis_concurrent_ack_and_replay_no_deadlock`: exercises the
    `bis_ack_received` × `replay_bis_chunks_to_worker` race on
    the same inner.write() lock. Wrapped in
    `tokio::time::timeout(5s)` deadlock detector. Asserts no chunk
    count above dispatched total.
  * Existing scheduler tests updated to thread `chunk.server_instance_token`
    through their `bis_ack_received` calls.
  * Existing worker tests updated to construct
    `BlobsInStableStorageChunk { ..., server_instance_token: ... }`
    AND assert `acks[0].server_instance_token` round-trips
    correctly.

TLA+ spec (specs/BISChunkingAck.tla):
  * Added `StaleTokenAccepted` constant + `StaleAck(c)` action
    that models a stale-token ack attempting to drop a chunk from
    the buffer. When `StaleTokenAccepted = TRUE` the action removes
    `c` from `serverBuffer` (modeling pre-fixup behavior); when
    FALSE, the action is a no-op (modeling the token check).
  * `BISChunkingAckBugged.cfg`: `StaleTokenAccepted = TRUE` —
    `ServerBufferReleaseImpliesUnpin` now violates INDEPENDENTLY
    of `BugMode` (a stale ack releases a chunk before the worker
    has unpinned it).
  * `BISChunkingAckFixed.cfg`: `StaleTokenAccepted = FALSE` —
    no violations.
  * `bash scripts/verify_tla.sh`: 13/13 specs PASS, including
    BISChunkingAck.

Verification:
  * `cargo check -p nativelink-scheduler -p nativelink-worker
    -p nativelink-proto -p nativelink-service --tests` clean.
  * `cargo test -p nativelink-scheduler --lib bis_` 6 passed.
  * `cargo test -p nativelink-worker --test bis_chunk_handler_test
    --features test-utils` 4 passed.
  * `cargo test -p nativelink-scheduler --test simple_scheduler_test`
    38 passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rejuvenile added a commit to rejuvenile/nativelink that referenced this pull request Aug 19, 2026
…mplemented)

Add three end-to-end tests that codify the existing get_part_and_cache
tee contract, exercised through the production CAS chain
(ExistenceCacheStore -> VerifyStore -> FastSlowStore):

* cdn_cache_populates_local_on_first_peer_fetch (UNDER-action guard):
  proves the tee populates inner CAS so a subsequent read hits local
  even when the peer is unreachable. This is the structural-amplification
  defense that motivates CDN-tee post-TraceMachina#171.

* cdn_cache_does_not_populate_on_partial_range_read (OVER-action guard):
  uses a RecordingInnerStore to detect tee invocation directly, so the
  test fires on the over-action itself (caching prefix/middle bytes
  under the full-blob digest) rather than on downstream defenses.

* cdn_cache_failure_does_not_fail_read: a FailingUpdateStore as inner
  proves tee best-effort semantics — peer-fetch must succeed even when
  inner.update returns Err.

Each test is wrapped in tokio::time::timeout(5s) as a deadlock detector
with a contract-specific .expect() message. Mutation steps verified for
all three:
  * Test 1: gating cache_write_fut to drain-only -> assertion fails with
    "CDN tee MUST populate local CAS after peer-fetch".
  * Test 2: removing the (offset==0 && length.is_none()) guard ->
    update_calls=1, assertion fails with the over-action message.
  * Test 3: replacing the warn! log with `?` propagation -> read errors
    surface to the caller, assertion fails on the best-effort message.

Sibling-bug audit (peer-fetch sites in the codebase):
  * worker_proxy_store::get_part parallel race path (line 2200) does NOT
    tee when peer wins. Owned by a parallel agent extending TraceMachina#171's
    INNER_MISS_NO_TERMINATE; not modified here per spec ownership rule.
  * worker_proxy_store::get_part_and_cache::!should_cache branch
    (line 907) intentionally bypasses the tee for partial-range and
    oversized blobs; matches design constraint TraceMachina#5.
  * bytestream_server::read delegates to store.get_part on the wrapped
    chain that includes WorkerProxyStore -- no separate peer-fetch path
    exists outside the proxy.

No production code change in this commit; the existing tee at
worker_proxy_store.rs:883-1056 (get_part_and_cache) is the
implementation under test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rejuvenile added a commit to rejuvenile/nativelink that referenced this pull request Aug 19, 2026
…aceMachina#334 bundle fixup TraceMachina#5)

Code-reviewer MA-6: the Fix C eviction extension's success-path
`info!` ("backpressure-driven eviction freed unpinned entries to admit
incoming write") at `memory_store.rs:284-291` fires on every eviction-
driven admission. In production at line rate that's hundreds of events
per second per FastSlowStore — same shape as TraceMachina#262 burst-info demote
class.

Demoted to `debug!`. The distinct "eviction freed nothing → genuinely
full" case is already signaled via the `Err(...)` arm below, where the
debug-level message ("every byte is pinned") carries the
operationally important info. No information is lost; only
production log noise.

Removed unused `use tracing::info;` import.

All 3 `memory_store_backpressure_eviction_test` regression tests pass
after the demote (the tests assert on behavior, not log content).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rejuvenile added a commit to rejuvenile/nativelink that referenced this pull request Aug 19, 2026
…ror} import (TraceMachina#334 bundle fixup CR-1)

Fixup TraceMachina#5 (ad7656de "demote backpressure-driven eviction info! to
debug!") deleted the `use tracing::info;` line but left the
`#[cfg(feature = "chunked_fast_slow")]` attribute above it, which
silently re-attached itself to the next `use` statement —
`use tracing::{debug, error};`. Default-feature builds were broken
(9 errors: `error` and `debug` macros undefined when
chunked_fast_slow is disabled).

Fix: drop the orphaned `#[cfg]` so `use tracing::{debug, error};`
is unconditional. Both `debug!` and `error!` are used in
non-chunked-gated paths.

Verified:
- `cargo build --all` (default features) succeeds.
- `cargo build --all --features chunked_fast_slow` still works.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rejuvenile added a commit to rejuvenile/nativelink that referenced this pull request Aug 19, 2026
…BLOCKs closed)

The convergent BLOCK from code-reviewer + testing-czar + red-team + dsr
on TraceMachina#335 V3 (failed_slow_writes drainer self-retry) was: the V3 walker
in `failed_writes_drain.rs:163-186` was a third copy that called
`store.inner_store(None)`, which terminates at SizePartitioningStore
(the partitioning trap). For the production CAS chain
(WorkerProxyStore → ExistenceCacheStore → VerifyStore →
SizePartitioningStore(16384) → FastSlowStore) the walker returned
`None`, V3 was structurally inactive, and there was no test crossing
the production composition.

Invariant being violated: V3 self-retry must fire when the server's
fast tier still holds the bytes; the walker must locate the FSS
inside the production wrapper chain.

Mechanism that violated it: `failed_writes_drain.rs:300`'s
`store.inner_store(None)` + the local walker recursing via `None`
both terminated at SizePartitioningStore (its
`inner_store(None)` returns `self`, ptr-eq guard short-circuits).

Mechanism that re-establishes it after the fix: canonical helper
`wrapper_walker::find_fast_slow_via_chain` + `synthetic_large_key()`
sentinel routes the walker into the upper arm. Both consumers (V3
drainer + chunked-dispatcher wiring) call the same helper.

Composite invariants this fix interacts with: the walker contract
plus the M1 stats invariant
`drained == dispatched + no_worker + throttled + send_failed +
self_retried + self_retry_failed`. The new test asserts both.

Test that proves the fix re-establishes the invariant:
`failed_slow_writes_v3_walker_descends_production_composition`.
Mutation step physically executed: temporarily replaced all 3
`synthetic_large_key()` calls in `wrapper_walker.rs` with `None` →
test red-failed with the bespoke message
"V3 walker failed to descend production composition —
synthetic_large_key pattern broken: walker returned None for the
cas_INNER chain so self-retry was structurally inactive". The M1
observability `warn!` also fired with the named composition.
Restored helper, all 8 tests green.

Changes:
* `nativelink-store/src/wrapper_walker.rs` (NEW): canonical
  walker + `synthetic_large_key()` sentinel. One source for the V3
  drainer + chunked-dispatcher wiring.
* `nativelink-service/src/failed_writes_drain.rs`:
  - Replace local 3rd-copy walker with `find_fast_slow_via_chain`.
  - dsr M1: emit `warn!` per-tick-per-store when walker returns
    None (V3-inactive observability — names the composition + the
    `walker_path = "no_fss_in_chain"` field).
* `src/bin/nativelink.rs`: replace local
  `find_fast_slow_chunked` + `synthetic_large_key` with the shared
  helper. No behaviour change in production (chunked-dispatcher
  was already correct).
* `nativelink-store/src/fast_slow_store.rs`: doc-comment block on
  `try_self_retry_slow_write` naming Fix C dependency
  (PIN_TIMEOUT_SECS=120 must outlive slow-tier outage; cap-rejected
  digests are intentionally excluded — they never had a fast-tier
  pin so V3 cannot recover them; admission-side fix is Fix A+B).
  Closes dsr MINOR-2 + red-team TraceMachina#2 + red-team TraceMachina#5.
* `nativelink-service/tests/failed_writes_drain_test.rs`:
  - dsr M3: BIS push assertion in V3 headline test
    (`drain_stable_digests().contains(&digest)` after self-retry
    success — closes the FSS → stable_digests seam contract).
  - dsr M2 + code-reviewer + testing-czar: NEW test
    `failed_slow_writes_v3_walker_descends_production_composition`
    wraps the production composition (ExistenceCacheStore →
    VerifyStore → SizePartitioningStore(16384) → FastSlowStore +
    FastSlowStore lower arm) with a 32 KiB digest that routes
    upper. Asserts walker descent + self-retry + BIS push end-to-
    end. Mutation step physically run + green-confirm.

Out of scope (explicitly deferred):
* drain_tick FuturesUnordered+Semaphore parallelism (red-team TraceMachina#4 /
  dsr M4) — documentation-only until production data warrants;
  follow-up TraceMachina#338.
* Cap-rejected digest recovery — V3's domain excludes them by
  construction (no pin → no fast-tier replica). Fix lives in Fix
  A+B (TraceMachina#334) admission gating.
* `find_fast_slow_for_pin` (small-key walker) consolidation —
  TODO(TraceMachina#303) noted in `wrapper_walker.rs` module docs; different
  semantics (lower arm vs upper arm).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rejuvenile added a commit to rejuvenile/nativelink that referenced this pull request Aug 19, 2026
…Machina#2+TraceMachina#3+TraceMachina#5+TraceMachina#9+TraceMachina#11 (BLOCK-class)

Addresses 6 of 11 reviewer findings on the BlobsAvailable chunking
landing (PR3 of TraceMachina#80). Each fix is annotated with the originating
reviewer + finding ID.

ACCUMULATOR (nativelink-service/src/blobs_available_accumulator.rs):

* Fix TraceMachina#1 (invariant-prover BLOCK + dsr + code-reviewer): Add
  sequence-completeness gate at terminal commit. Before this fix, a
  terminal chunk arriving with gaps in `seen_sequences` committed a
  strict-subset partial — the half-applied-snapshot bug class the
  Path A semantics are meant to prevent. The new
  `BroadcastAccumulator::sequences_contiguous()` checks
  `seen_sequences == (1 << (max_seen_sequence + 1)) - 1`. On
  rejection: drop the partial accumulator, increment
  `dropped_incomplete_sequence` counter, warn-log, return None.
  Worker re-broadcasts on next tick.

* Fix TraceMachina#3 (red-team BLOCK + testing-czar IMP-4 + assumption-auditor
  REFUTED-1): Header scalars (worker_cas_endpoint, cpu_load_pct,
  mirror_used_bytes, etc.) now stored as `Option<HeaderScalars>`,
  populated only when `sequence == 0` arrives. If the terminal
  commits without seq=0 ever arriving, refuse (subsumed by Fix TraceMachina#1
  in practice but documented separately).
  `BroadcastAccumulator::new()` no longer reads scalars from "first
  chunk to arrive" — that path silently lost headers under
  out-of-order delivery.

* Fix TraceMachina#2 (dsr BLOCK-1 + red-team alt 1): Raise MAX_SEQUENCES from
  64 → 256 (bitset switched from u64 to u128-pair) AND
  MAX_ACCUMULATED_ENTRIES_PER_CONN from 200_000 → 1_000_000 to
  match the AC-pin per-endpoint cap of 1M. Worker FSS sizing 50K-
  200K + AC pins + cached_directory_digests easily reaches 1M
  total; the prior 200K cap silently dropped legitimate worker
  traffic.

* Fix TraceMachina#11 (security M1 + assumption-auditor REFUTED-2):
  - Pre-merge per-conn entries-cap projection: cap fires BEFORE
    `acc.merge(chunk)` so peak accumulator memory is bounded by
    `MAX_ACCUMULATED_ENTRIES_PER_CONN + MAX_ENTRIES_PER_CHUNK`,
    not `+ (one hostile chunk's worth)`.
  - Per-chunk entries cap (`MAX_ENTRIES_PER_CHUNK = 8192`):
    rejects oversized chunks before any per-broadcast state is
    touched.
  - Falsification test `entries_cap_drops_partial_at_2x_threshold`:
    drives 2× the cap of synthetic data, asserts cap eviction
    fires AT threshold (not far before, not after OOM).

* Fix TraceMachina#9 (dsr MAJOR-1): Per-reason `chunks_dropped_*` counters
  exposed via `pub drop_counts: ChunkDropCounts`. 9 reasons:
  token_zero, token_mismatch, per_conn_broadcasts_cap,
  per_conn_entries_cap, per_chunk_entries_cap, sequence_cap,
  validation_other, incomplete_sequence, missing_chunk_zero. Each
  warn-log carries the reason as a structured field. Test
  `drop_counters_cover_all_documented_reasons` exercises 7 of 9
  paths (the two missing-chunk-zero paths are structurally
  unreachable when the completeness gate fires first; counter
  exists for defense-in-depth).

* Fix TraceMachina#5 (invariant-prover + assumption-auditor): Replaced
  `out_of_order_sequence_within_window_tolerated` with
  `out_of_order_sequence_with_gap_rejected`. The old test
  ENSHRINED THE BUG by asserting `is_some()` on a commit that
  skipped sequence 1 — the new test asserts `is_none()` with the
  bespoke message "sequence-completeness gate must reject
  incomplete commit; saw is_some() instead — Fix TraceMachina#1
  (invariant-prover BLOCK) regression".

* Removed gratuitous `total_accumulated: AtomicUsize` (code-reviewer
  TraceMachina#4): always accessed under the inner mutex; reduced to a plain
  `usize` field on `AccumulatorInner`. Adds a `total_accumulated()`
  test helper.

CHUNKER (nativelink-util/src/blobs_available_chunking.rs):

* Fix TraceMachina#2 (dsr BLOCK-1): Added
  `BLOBS_AVAILABLE_MAX_CHUNKS_PER_BROADCAST = 256` MUST-equal-the-
  server-MAX_SEQUENCES constant. Changed
  `chunk_blobs_available()` signature from `Vec<...>` to
  `Result<Vec<...>, &'static str>`. Returns Err if a broadcast
  would emit > 256 chunks. New tests:
  `chunker_errors_when_exceeding_max_chunks_per_broadcast` and
  `chunker_allows_exact_max_chunks_per_broadcast` (boundary).

WORKER (nativelink-worker/src/local_worker.rs):

* `send_periodic_blobs_available` updated to handle the chunker's
  new Result return: on Err, warn-log and return Ok(()) (skip the
  tick; next tick retries). Better than emitting chunks the server
  silently discards.

INVARIANTS WALKED:

* terminal-chunk-commits ⇒ seen_sequences forms contiguous prefix
  [0, max_seen_sequence] (Fix TraceMachina#1 — re-established at
  blobs_available_accumulator.rs `sequences_contiguous()`).
* per-conn memory bound: `total_accumulated <=
  MAX_ACCUMULATED_ENTRIES_PER_CONN + MAX_ENTRIES_PER_CHUNK` even
  with a hostile chunk (Fix TraceMachina#11 — re-established at pre-merge
  cap-check).
* chunker output count <=
  BLOBS_AVAILABLE_MAX_CHUNKS_PER_BROADCAST = server's MAX_SEQUENCES
  (Fix TraceMachina#2 — enforced at chunker producer-side guard).

TESTS:

* nativelink-util chunker: 11/11 pass (added 2 tests).
* nativelink-service accumulator: 19/19 pass (added 7 tests,
  rewrote 1, removed 0).

Reviewers cited: invariant-prover BLOCK, distributed-systems-reviewer
BLOCK-1 + MAJOR-1, red-team blind-spot-1 + alt-1 + alt-2,
testing-czar IMP-2 + IMP-3 + IMP-4, code-reviewer Critical TraceMachina#1 +
suggestion TraceMachina#4, assumption-auditor REFUTED-1 + REFUTED-2,
security-reviewer M1 + M2.

Composite invariants this fix interacts with: Path A
(deferred-wipe) commit semantics, mirror_blobs ≥2-replica
durability (untouched — BlobsAvailable is locality hint only),
worker_instance_token contract from TraceMachina#97/TraceMachina#98.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rejuvenile added a commit to rejuvenile/nativelink that referenced this pull request Aug 19, 2026
…r-branch sibling guard

Code-reviewer @ 2d46d33 found a NEW-BUG-INTRODUCED in the prior fixup:
on token-mismatch rebuild, `inner.total_accumulated` is decremented
at line 549-550 (wipe stale broadcast's entries), then unconditionally
overwritten at line 596 by `prev_total.saturating_add(delta)` where
`prev_total` was captured BEFORE the wipe. The wipe is undone, leaving
stale entries double-counted in `total_accumulated` indefinitely.

Invariant being violated: `inner.total_accumulated` MUST equal
`sum(broadcasts.values().accumulated_entries)` at all times — this is the
bookkeeping invariant the per-conn entries cap admission gate at lines
506-507 reads. Drift causes premature per-conn-entries-cap eviction
(legitimate broadcasts dropped once enough token-mismatches accumulate).

Mechanism that violates it: line 596 used the pre-wipe `prev_total`
snapshot.

Mechanism that re-establishes it: read CURRENT `inner.total_accumulated`
after the wipe, so the increment is on top of the wiped value.

Composite invariants this fix interacts with: per-conn entries cap
admission gate (line 506-507) is correct iff total_accumulated matches
sum-over-broadcasts. Eviction sites (548-550 token-mismatch, 590-592
validation-fail, 605-607 terminal-commit) all decrement based on
snapshotted entries. Pre-merge cap projection (line 506-507) reads
total_accumulated. NO pin corner.

Tests:
- `token_mismatch_drift_total_accumulated_consistency`: 3-step drift
  scenario; the BETWEEN-step-3-and-4 assertion red-fails pre-fix with
  the bespoke "expected total_accumulated == M after token-mismatch
  rebuild, got total_accumulated == N + M — bookkeeping drift not
  fixed" message (verified mutation-step output stored at
  /tmp/99-fixup-2-redfail-2018247.log: `left: 100, right: 20`).
- `err_branch_does_not_double_subtract`: locks down the Err-branch
  subtraction safety claim with a multi-broadcast composition where
  broadcast 2's duplicate-sequence Err must NOT bleed into broadcast
  1's contributions to `total_accumulated`.

Sibling-bug audit (Err branch at lines 590-592): traced each of the
three pre-merge_chunk cases (keep-existing, token-mismatch-rebuild,
new-broadcast). `BroadcastAccumulator::merge` returns Err only at
validation gates that ALL precede the `accumulated_entries` mutation
at line 250. So:
- keep-existing case: acc_accumulated_entries == prev_entries, already
  counted in inner.total_accumulated from prior successful merges.
  Subtracting it removes those prior contributions correctly.
- token-mismatch-rebuild + new cases: acc was just constructed with
  accumulated_entries=0, never mutated by the failing merge; subtracting
  0 is a safe no-op. The wipe at line 549-550 stands.
SAFE-DOCUMENTED with inline comment + audit citation at the
subtraction site.

Verdict on Err-branch sibling concern: SAFE-DOCUMENTED.

Mutation step (CLAUDE.md TDD TraceMachina#5): pre-fix red-fail verified at
/tmp/99-fixup-2-redfail-2018247.log; post-fix all 21 accumulator
tests pass (cargo test result: 21 passed; 0 failed).
rejuvenile added a commit to rejuvenile/nativelink that referenced this pull request Aug 19, 2026
…test (testing-czar MAJOR-1)

Per testing-czar MAJOR-1 from the TraceMachina#99 fix-up-2 cadre review:
`BlobsAvailableAccumulator::new_with_drop_counts` had exactly one
production caller (`worker_api_server.rs` near
`Arc::clone(&metrics.chunked_blobs_available_drop_counts)`) and zero
test callers, so a future refactor that accidentally swapped
`Arc::clone` for `Arc::new(ChunkDropCounts::default())` would silently
fork the per-connection counters and under-report server-wide drops on
dashboards — without any existing test red-failing.

The new `new_with_drop_counts_aggregates_across_accumulators` test
constructs TWO accumulators sharing one `Arc<ChunkDropCounts>`, drives
3 token-mismatch increments through accumulator A and 5 through
accumulator B, then asserts: (1) the shared Arc observes 8 increments,
(2) both accumulators' `drop_counts` views observe the same 8 (Arc
aliasing), (3) `Arc::ptr_eq` confirms all three handles point at one
allocation. Bespoke `assert_eq!` message names the regression and the
exact production callsite a refactor would touch.

Mutation step (CLAUDE.md TDD TraceMachina#5): swapping `drop_counts` for
`Arc::new(ChunkDropCounts::default())` inside `new_with_drop_counts`
red-fails the test with `shared_view=0, expected 8` and the bespoke
message — confirmed locally then reverted.

Test count: 21 -> 22 passing in
`nativelink-service::blobs_available_accumulator::tests`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rejuvenile added a commit to rejuvenile/nativelink that referenced this pull request Aug 19, 2026
`BazelChunkedDispatcherImpl::dispatch` used to manually
`set.lock().insert(digest)` then `dispatch_bazel_facing_internal_chunking
.await` then `set.lock().remove(&digest)` in match arms. The await is a
cancellation point: when an upstream `try_join!` (e.g.
`bytestream_server.rs:1761-1775`) short-circuits on a producer Err and
drops the dispatch future, the matched-arm removal NEVER runs. Result:
the digest leaks into `chunked_in_flight_digests` until process restart;
`FastSlowStore::has_with_results` reports `Some(size)` for leaked digests
forever; `ExistenceCacheStore` caches the `Some`; FMB returns "present"
forever; subsequent populates NotFound on the missing bytes. The
production smoking gun was the shutdown WARN at
`fast_slow_store.rs:2012-2017` ("unflushed chunked-path write at
shutdown") firing 3x for `4cb49612...` 10.5h after the cancel.

Fix: introduce `InFlightChunkedGuard`, an RAII guard whose `Drop`
removes the digest and fires the empty-notify. Drop runs on every exit
path including cancellation, so the leak is impossible by construction.
The success path explicitly `disarm`s the guard (turning Drop into a
no-op) and hands the (set, digest, notify) tuple to the post-dispatch
reaper task, which performs removal only after the chunked-driver's
in-flight tracker drains -- preserving the TraceMachina#210 graceful-drain
semantics. The Err path lets the guard's natural Drop perform removal,
matching the prior behavior.

Tests in `nativelink-service/tests/chunked_inflight_cancel_safety_test.rs`:
  - `guard_drop_removes_digest`: RAII unit contract.
  - `guard_drop_notifies_when_set_becomes_empty`: preserves the
    `flush_slow_writes` lost-wakeup contract on the cancel path.
  - `cancel_mid_await_removes_digest`: production-shape regression
    test. Wraps the guard in a future awaiting a long sleep, races
    against a short timer in `select!`, asserts the set is empty after
    the dispatch future is cancelled. With the bug present
    (manual insert/remove), this red-fails with the bespoke message
    "InFlightChunkedGuard MUST remove digest on cancellation -- leak
    detected". With the fix, this passes.
  - `disarm_prevents_drop_removal`: success-path contract -- after
    `disarm()`, Drop is a no-op so the spawned reaper owns removal.

Mutation step (CLAUDE.md TDD TraceMachina#5) verified: commenting out the body of
`impl Drop for InFlightChunkedGuard` red-fails
`cancel_mid_await_removes_digest` with the bespoke message above (and
also red-fails `guard_drop_removes_digest` and
`guard_drop_notifies_when_set_becomes_empty`). Drop body restored.

Invariant being violated: every digest inserted into
`chunked_in_flight_digests` MUST be removed before the dispatcher
returns control to its caller (or, in the success path, before the
chunked-driver's in-flight tracker drains).
Mechanism that violated it: `chunked_write_handler.rs:2462`
unconditional insert paired with match-arm removal at `:2492-2530`,
both bypassed when the inner `.await` is cancelled.
Mechanism that re-establishes it: `InFlightChunkedGuard::drop`
unconditionally removes (when armed), runs on every future exit path
including cancellation.
Composite invariants this fix interacts with:
  - TraceMachina#210 graceful-drain (preserved via `disarm()` + spawned reaper)
  - lost-wakeup on `in_flight_empty_notify` (preserved -- Drop fires
    notify_waiters when the set transitions to empty)
  - has_with_results chunked-path Some/None invariant (no longer
    poisoned by leaked digests)
Test that proves the fix re-establishes the invariant:
`cancel_mid_await_removes_digest`.

Builds verified: `cargo check --tests -p nativelink-service --features
chunked_fast_slow` (lib + new test), `cargo check -p nativelink-service
-p nativelink-store` (no chunked_fast_slow). 4/4 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rejuvenile added a commit to rejuvenile/nativelink that referenced this pull request Aug 19, 2026
Sibling-of-TraceMachina#401 (same file region, same shape, different field). DSR
review of TraceMachina#401 surfaced this: `bytestream_server.rs:337`'s
`in_flight_writes: Arc<Mutex<HashMap<DigestInfo,
watch::Receiver<Option<bool>>>>>` had the identical manual-insert /
manual-remove pattern around `tokio::time::timeout(WRITE_TIMEOUT=300s,
write_fut).await` (insert at :2395, remove at :2505). Cancellation of
the wrapping gRPC future (client disconnect, RST_STREAM, server
shutdown) drops `write_fut` mid-await; the manual remove never runs;
HashMap entry + watch channel leak permanently per cancelled upload.

Invariant being violated: every entry inserted into `in_flight_writes`
MUST be removed on every exit path of the primary-writer code, including
cancellation.

Mechanism that violates it:
`bytestream_server.rs:2393-2397` insert; `:2466-2496`
`tokio::time::timeout(WRITE_TIMEOUT, write_fut).await` cancellation
point; `:2505` `remove(&digest)` only on completion path. 300s cancel
window per upload.

Mechanism that re-establishes it after the fix: new
`InFlightWritesGuard` (RAII). `from_inserted` constructs the guard from
state already inserted under the dedup lock (preserves race-freedom
versus a concurrent second arrival becoming a duplicate primary).
`set_result(bool)` publishes the outcome to coalesced waiters via the
watch channel before Drop. `Drop` removes the map entry + drops the
`Sender`. On cancellation, `set_result` is never called; Drop's Sender
drop signals failure to coalesced waiters via `rx.changed() = Err`,
which the production loop at `bytestream_server.rs:2349-2358` already
translates to false (failure).

Composite invariants this fix interacts with:
- watch-channel-disappearance contract for coalesced waiters: dropping
  the Sender MUST signal failure (preserved — `tx` is dropped in
  guard's Drop on every exit path).
- dedup-lock atomicity: insert + check-then-insert MUST happen under
  one `in_flight_writes` lock to prevent two RPCs both becoming primary
  writers (preserved — `from_inserted` skips the insert because the
  caller already did it under the dedup lock at :2395).

Test that proves the fix re-establishes the invariant:
`nativelink-service/tests/bytestream_inflight_writes_cancel_safety_test.rs`
- `guard_drop_removes_entry`: unit test of the RAII contract.
- `guard_set_result_publishes_outcome`: success-path — set_result
  publishes the outcome to coalesced waiters BEFORE Drop removes.
- `cancel_mid_await_removes_entry`: production-shape cancellation test
  via `select!`; asserts map empty post-cancellation with bespoke
  message naming bug TraceMachina#402.
- `coalesced_waiter_observes_failure_on_writer_cancel`: cross-seam test
  composing primary writer + coalesced waiter + cancellation; asserts
  the waiter unblocks with `false` (rx.changed() = Err) within
  `tokio::time::timeout(2s)` — deadlock detector.

Mutation step (per CLAUDE.md TDD TraceMachina#5): commenting out the body of
`impl Drop for InFlightWritesGuard` red-fails all 4 tests, with
`cancel_mid_await_removes_entry` reporting:
"InFlightWritesGuard MUST remove entry on cancellation — leak detected
(map still contains 1 entries after cancelled await; this is bug TraceMachina#402 —
manual insert/remove around an await is not cancel-safe)".

No behavior change on success/error paths: `set_result(result.is_ok())`
publishes the same outcome the prior code's `tx.send(Some(result.is_ok()))`
did; map removal still happens before function returns (just inside
Drop, not as an explicit lock + remove).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rejuvenile added a commit to rejuvenile/nativelink that referenced this pull request Aug 19, 2026
…impls

Production wedge 2026-05-13 (PID 3818654, 13:19 - 17:21 PDT, 4h02m): every
`/metrics` scrape that raced an `ApiWorkerSchedulerImpl` writer permanently
parked one of ~48 tokio worker threads in the `event_listener` FIFO queue.
The leak accumulated over ~3h until the runtime starved its accept loops:
`/metrics` (port 50061) wedged ~14:01 PDT, then the Bazel port (50051)
wedged at 16:36 PDT (`SslHandshakeTimeoutException 10000ms`). Restart
required at 17:21:43.

RCA: `.claude/audits/wedge-2026-05-13-postmortem.md`. Direct evidence in
`/tmp/wedge-rca-stall-1778704391485.log` (TID 3818676 stack frames TraceMachina#5-TraceMachina#10:
`event_listener::Blocking::poll_with_strategy` -> `RwLock<ApiWorkerScheduler
Impl>::publish` -> `metrics_handler` on `tokio::runtime::scheduler::multi_th
read::worker::run`).

Root cause: blanket `impl MetricsComponent for async_lock::RwLock<T>`
(`nativelink-metric/src/lib.rs:461`, introduced 3ddded5 on 2026-02-18)
called `read_blocking()` from a sync trait method. `metrics_handler`
(`nativelink-util/src/metrics_publisher.rs:499`) is `async fn` invoked on
a tokio runtime worker thread; calling `read_blocking()` parks that worker
on the event_listener FIFO queue indefinitely until the writer releases.
The doc-comment "It is safe to block in the publishing thread" was
load-bearing-false. CLAUDE.md "NEVER block a tokio worker thread" hard
rule violated.

Composite invariant being re-established:
  metrics-publisher-call-from-async-context
    => NEVER blocks the calling thread
       (skip-on-contention OR async-await OR spawn_blocking)

Fix: skip-on-contention via `try_*()`. Stale metrics for one scrape are
strictly preferable to a permanently-parked tokio worker thread. A scrape
that races a writer publishes a default `Component` for the contended
subtree on this tick; the next scrape that catches the lock idle publishes
fresh values. Same fix applied to all four blanket impls in this file:

  - `async_lock::RwLock<T>`:  read_blocking()  -> try_read()
  - `async_lock::Mutex<T>`:   lock_blocking()  -> try_lock()
  - `parking_lot::RwLock<T>`: read()           -> try_read()
  - `parking_lot::Mutex<T>`:  lock()           -> try_lock()

The `parking_lot` variants are subject to the same wedge mechanism (writer-
held lock parks the calling worker thread) even though they don't use the
event_listener FIFO queue; same fix is justified by the same hard rule.

`tokio::sync::watch::Sender::borrow` (`lib.rs:138`) left as-is: the
metrics-side caller holding `&self` IS the writer end of the watch
channel, so structural contention against it is not possible.

Sibling-bug audit (workspace-wide via `grep -rn 'impl.*MetricsComponent
for.*\(RwLock\|Mutex\)' --include='*.rs'`):

  | crate                | site                          | type                     | status |
  |----------------------|-------------------------------|--------------------------|--------|
  | nativelink-metric    | lib.rs:449                    | async_lock::Mutex        | FIXED  |
  | nativelink-metric    | lib.rs:461                    | async_lock::RwLock       | FIXED  |
  | nativelink-metric    | lib.rs:473                    | parking_lot::Mutex       | FIXED  |
  | nativelink-metric    | lib.rs:485                    | parking_lot::RwLock      | FIXED  |
  | (any other crate)    | none                          | -                        | n/a    |

`grep -rn 'read_blocking\|write_blocking\|lock_blocking' --include='*.rs' .`
returns ZERO matches workspace-wide post-fix.

Tests (`nativelink-metric/src/lib.rs` test module, all `#[tokio::test(
flavor = "multi_thread", worker_threads = 2)]`):

  1. `publish_does_not_block_on_contended_async_lock_rwlock`
  2. `publish_does_not_block_on_contended_async_lock_mutex`
  3. `publish_does_not_block_on_contended_parking_lot_rwlock`
  4. `publish_does_not_block_on_contended_parking_lot_mutex`
  5. `publish_returns_real_value_when_uncontended` (sanity check that
     the skip-on-contention default isn't masking real values)

Each contention test holds a writer guard, then drives `publish` from a
fresh `tokio::spawn` (NOT `spawn_blocking` - we want to test the same
seam that production uses today, where `metrics_handler` runs on a
runtime worker). A `tokio::time::timeout(Duration::from_secs(1))`
deadline asserts the publish task completes; pre-fix it parks
indefinitely. The bespoke assertion message is "publish() blocked tokio
worker thread on contended <lock> (exceeded 1s deadline; pre-fix would
park indefinitely)" so the failure mode is unambiguous; without it a
generic timeout-elapsed would be misleading.

Mutation verification (per CLAUDE.md TDD rule): each fix was reverted in
turn and the corresponding test red-failed with the bespoke message.
Verified 2026-05-13.

Re-enabling `services.metrics: {}` in production after this commit ships
will no longer wedge the server.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rejuvenile added a commit to rejuvenile/nativelink that referenced this pull request Aug 19, 2026
Closes the BLOCK + MAJOR set surfaced by the TraceMachina#494-v3 cadre audit at
HEAD 5d7d08ac. The bundle ships with v2 default-on; this commit hardens
the v2 path against the half-done H1 reader cascade, the missing
QueryWriteStatus phantom-success guard, the v1 deferred-publish
phantom-Ok on commit failure, and the chunked_in_flight set lacking
refcount semantics for concurrent same-digest sessions.

BLOCK-A (QueryWriteStatus phantom-success guard):
- nativelink-service/src/bytestream_server.rs inner_query_write_status
  now mirrors the H2 pattern from bytestream_write: downcast to
  FastSlowStore, consult is_chunked_in_flight, return complete=false
  when the digest is in chunked_in_flight_digests. Pre-fix, the
  has()=Some cascade returned complete=true while the v2 commit could
  still fail, breaking Bazel's durability contract.
- Test: bytestream_server_test.rs::block_a_query_write_status_does_
  not_phantom_ack_for_chunked_in_flight. Mutation-verified red-fail
  with the bespoke "QueryWriteStatus phantom-acked in-flight chunked
  digest" message.

BLOCK-B (H1 reader-cascade Option F1 — read-blocks-on-commit):
- nativelink-store/src/fast_slow_store.rs get_part now waits (bounded
  5s polling at 50ms) when the digest is in chunked_in_flight_digests
  AND the chunked_read_registry pin has fallen through (v2 case: no
  driver pin). Closes the gap where has_with_results said Some (via
  the in-flight set) but get_part returned NotFound (canonical not yet
  on disk + no pin to serve).
- Test: chunked_write_handler_v2_test.rs::block_b_h1_reader_cascade_
  blocks_on_v2_inflight_then_serves_from_slow. Test simulates v2
  commit landing during the wait; reader observes correct bytes.
  Mutation-verified red-fail with bespoke "H1 reader-cascade returned
  NotFound during v2 in-flight window" message.

BLOCK-D (H2 production-composition test):
- New test bytestream_server_test.rs::block_d_bytestream_write_h2_
  does_not_phantom_ack_when_chunked_in_flight. Wraps FSS +
  chunked_in_flight_digests + ByteStreamServer in production
  composition; asserts second concurrent ByteStream::write does NOT
  phantom-ack. Mutation-verified red-fail with bespoke "ByteStream::
  write phantom-acked second concurrent writer while chunked commit
  was still in-flight" message.

BLOCK-E (subscribe → enable → peek → notified):
- nativelink-service/src/chunked_write_handler_v2.rs::v2_await_commit_
  result and chunked_write_handler.rs v1 AwaitCommit branch now call
  tokio::pin!(notified) + notified.as_mut().enable() BEFORE peek. The
  dispatch-prompt's missed-wakeup hypothesis is mitigated in tokio
  1.49 by notify.rs:572's counter-capture-at-creation, but enable()
  is defense-in-depth against future tokio API drift AND
  self-documenting (eager registration is the intent).
- Test: chunked_write_handler_v2_test.rs::block_e_missed_wakeup_
  publish_before_first_poll_is_observed. Verifies publish-between-
  subscribe-and-first-poll is observable through notified.await.
- Doc-comment correction: source code now documents that the
  classical missed-wakeup is NOT active in tokio 1.49 (counter-capture
  handles it), and explains why enable() is still worth applying.

MAJOR-F + MAJOR-H (deferred-publish reactive instead of busy-spin):
- chunked_write_handler.rs::dispatch_chunks_to_driver gains an
  async_result_relay parameter; the AsyncCommit branch threads it to
  run_async_commit_reaper. dispatch_bazel_facing_internal_chunking
  also gains the parameter; the BazelChunkedDispatcherImpl::dispatch
  Owner branch creates a oneshot pair, passes the sender into
  dispatch, and the deferred-publish task AWAITS the real
  commit_result instead of polling in_flight.contains_digest.
- Eliminates: busy-spin on yield_now (N×100us ticks during
  multi-second commits), synthetic-Cancelled trap on spawn drop,
  phantom-Ok on commit failure (the prime DS hazard).
- Test: chunked_write_handler_v2_test.rs::major_f_deferred_publish_
  propagates_commit_err_to_sibling_v2. Uses a hash-mismatch payload to
  force commit failure; sibling v2 in AwaitCommit MUST observe Err
  (not phantom-Ok). Mutation-verified red-fail with bespoke
  "deferred-publish published Ok despite v1 commit failure" message.

MAJOR-G (chunked_in_flight_digests refcount):
- Type change: Arc<Mutex<HashSet<DigestInfo>>> →
  Arc<Mutex<HashMap<DigestInfo, NonZeroU32>>> in fast_slow_store.rs +
  chunked_write_handler.rs. InFlightChunkedGuard::new bumps refcount;
  Drop decrements and removes only when refcount reaches zero.
- Pre-fix: two concurrent v2 sessions for the same digest both
  "inserted" (idempotent on HashSet), but the first to drop removed
  the entry while the second was still in-flight. BLOCK-B's reader
  wait then misses the second session and gets NotFound from the
  not-yet-committed slow tier.
- All call-sites updated: .contains(&d) → .contains_key(&d), iter()
  preserves (digest, refcount) tuple for shutdown-time warnings, test
  manipulations use lock().insert(digest, NonZeroU32::new(1).unwrap()).
- Test: chunked_inflight_cancel_safety_test.rs::major_g_refcount_
  keeps_digest_visible_until_both_guards_drop. Two guards on the same
  digest; assert refcount sequencing through 0→1→2→1→0 with entry
  visibility check at each step. Mutation-verified red-fail with
  bespoke "refcount removed digest while second guard still
  in-flight" message.

MAJOR-I (H2 doc-comment honesty):
- bytestream_server.rs:2742 doc-comment now reflects actual behavior:
  the H2 fall-through to in_flight_writes only dedups OTHER
  ByteStream::write callers (v2-chunked sessions don't populate
  in_flight_writes). For v2-in-flight cases, second writer becomes the
  primary and races; CAS convergence (digest = content) makes on-disk
  byte sequence well-defined. The H2 guard is "don't lie about
  durability" — NOT "serialize against the chunked writer."

MAJOR-J (TLA+ test assertion message divergence):
- chunked_write_handler_v2_test.rs::tla_all_attached_writers_terminate_
  within_70s_v1plus2v2: the v1_size + v2 commit assertion messages now
  match what mutation of single_stream_owner actually catches ("failed
  to stat chunked partial ... NotFound" — i.e. v1+v2 racing on the
  .partial file, NOT a 70s timeout).

MAJOR-K (chunked_v2_enabled=false rollback path):
- New test chunked_write_handler_v2_test.rs::major_k_chunked_v2_
  disabled_returns_unimplemented_for_v2_rpcs. Constructs adapter with
  v2_enabled=false; asserts v2 RPCs return Unimplemented with
  "chunked_v2_enabled" in the message; v1 RPC still works.

Out-of-scope (file follow-up trackers):
- DS MAJOR-1 (latent v1 AsyncCommit code path; gated by
  bazel_facing_internal_chunking=false default; eliminated by
  MAJOR-F refactor when the v1 path is enabled).
- DS MINOR-1 (chunked_in_flight TTL/sweeper backstop — MAJOR-G's
  refcount + RAII covers the common case).
- code MAJOR-1 (probabilistic metric), code MAJOR-3 (err_tip
  chaining), code MINOR-1/2/3, auditor MINOR (1-off test count, TLA+
  line cite).
- red-team TraceMachina#5 (pilot-vs-default-flip — user already decided).
- red-team TraceMachina#8 (hand-edited pb.rs — pre-existing TraceMachina#87/TraceMachina#346).
- Pre-existing test failure: bazel_facing_internal_chunking_test.rs::
  fast_tier_ok_dispatch_err_records_failed_slow_write — the
  typed-BackpressureSignal early-return at fast_slow_store.rs:1654
  (aeaf078) bypasses failed_slow_writes insertion. NOT a regression
  from this commit; flagged for follow-up tracker.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rejuvenile added a commit to rejuvenile/nativelink that referenced this pull request Aug 19, 2026
Adds 4 tests in `nativelink-service/tests/writer_site_logs_test.rs`:
- `fss_registration_emits_info_with_writer_path_label` — production
  `InFlightChunkedGuard::new_with_caller` emits one info! line tagged
  `registry=fss_chunked_in_flight_digests` with the supplied caller
  label.
- `fss_removal_emits_paired_info_with_outcome_drop` — Drop emits a
  paired removal line so a journal scan can compute in-flight
  duration per writer path.
- `fss_disarm_emits_info_with_outcome_disarm` — the success-path
  hand-off emits one line with `outcome=disarm` so a journal scan
  distinguishes "reaper-owned removal" from "silent drop bug".
- `v1_server_handler_local_insertion_label_contract` — pins the
  operator-facing label contract for the v1 server-side handler-local
  in_flight insertion.

Adds 3 tests in `nativelink-store/tests/chunked_client_test.rs`:
- `worker_write_chunked_stream_emits_entry_log` — worker-side
  write_chunked_stream emits `writer_path=worker_chunked_client`
  with the blob digest + expected_size.
- `v1_worker_dispatcher_dispatch_emits_wire_shape_log` —
  `WorkerApiWriteChunkedDispatcher::dispatch` emits
  `writer_path=worker_dispatch_v1 + wire_shape=v1`.
- `v2_worker_dispatcher_dispatch_emits_wire_shape_log` — same for
  v2 dispatcher with `wire_shape=v2`.

Each test has a bespoke assertion message naming the source file:line
of the deletable info! emission, so a mutation that removes the log
red-fails with actionable attribution.

Mutation verification (per CLAUDE.md TDD TraceMachina#5): manually commented out
the v1 worker dispatcher's info! and re-ran the test —
`v1_worker_dispatcher_dispatch_emits_wire_shape_log FAILED` with the
expected bespoke message. Restored the info!, test passes again.

Clippy fixups (no behavior change, addresses
`clippy::map_unwrap_or` lint introduced by the new digest_str /
peer_addr blocks):
- chunked_client.rs: 2× sites use `map_or_else` instead of
  `.map().unwrap_or_else()`.
- chunked_write_handler.rs / _v2.rs: peer_addr resolution uses
  `map_or_else`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rejuvenile added a commit to rejuvenile/nativelink that referenced this pull request Aug 19, 2026
…t honesty

Addresses 8 MAJOR findings from the 3-reviewer cadre (distributed-systems,
invariant-prover, red-team) on commit 7e27c61e. Specs-only diff — no
production source touched.

REVISIONS TO StreamingBlobReadDuringWrite.tla

R1 (DS-MAJOR-1, red-team-MAJOR-2): two-seam split. Replaced single
boolean `producerErrPropagated` with `writerTerminalErr` (Seam A,
`StreamingBlobInner::terminal`) + `seamBPropagated` (Seam B,
`state.maybe_get_part_result` consumed by `inner_read::consume_ok_eof`).
New action `SeamBObservesProducerErr` fires non-deterministically under
`WF_vars` fairness, modeling the per-RPC tokio::select! arm-ordering
race at `bytestream_server.rs:1726-1867` that the TraceMachina#500 commit message
acknowledged as "addresses one of multiple candidate mechanisms".
Consequence: strong "no Silent* under post-fix" is no longer a state
invariant — leads-to liveness `PostFixErrPropagationReachesReaderEventually`
is the post-revision safety-equivalent claim.

R2 (red-team-MAJOR-3): replaced tautological `BytesWrittenMatchesChunkCount`
(true by construction of the spec's single atomic writer action) with
`BytesWrittenBoundedByChunkCount` (structural bound). The production
two-AtomicU64 race is documented as an outstanding gap (Phase 5 TraceMachina#6).

R3 (red-team-MAJOR-5): rewrote the CASE→IF/ELSE comment to name the
actual spec issue (the original CASE was not total across `terminal`
domain because TLC's evaluator does not propagate caller preconditions).
No longer frames a spec bug as a TLC quirk.

R7 (invariant-prover-NIT-1): dropped unused `Sequences` from EXTENDS.

REVISIONS TO CFG FILES

R4 (invariant-prover-MAJOR-1): new file
`StreamingBlobReadDuringWriteBothOffBugged.cfg` covers the
`ShortShieldOn=FALSE × SilentZeroPropOn=FALSE` cross-product cell that
was missing pre-revision. Gate-classified via the `*Bugged.cfg` suffix.
TLC reaches violation at depth 4, 38 states.

R5 (red-team-MAJOR-4): all three Bugged cfgs now list
`WriterEventuallyTerminates` as a PROPERTY. Without this, a future
regression that masks the safety bug while introducing a lost-wakeup
would CI-pass.

R6: Fixed.cfg and informational 3r3c/4r2c cfgs updated to new invariant
list (the strong "no silent" forms are NOT listed; the conditional
`*IfSeamBFired` forms are kept in the spec as documentation only).

REVISIONS TO ChunkRaceReadersBuggedH3.tla / Fixed.cfg

R8 (invariant-prover-MAJOR-2): added 5 structural invariants
(`CommitDoneFlagSticksTrue`, `RunnerWriterAssignmentDiscipline`,
`WriterDoneImpliesCommitDone`, `ChunksInFlightDisjointFromPresent`,
`ReaderTerminalHasObservation`) to the H3 spec. Updated Fixed.cfg to
exercise all 6 invariants (was TypeOK-only — degenerate per the
reviewer's MAJOR-2). Documentation explicitly names the stopgap nature;
the proper fix is the `AllowEvictingMapInsert` refactor (Phase 5 TraceMachina#5).

R9 (DS-MINOR-1): corrected stale citation
`chunked_write_handler.rs:3997+` → `:4299` in ChunkRaceReadersV2.tla
and the audit doc.

REVISIONS TO AUDIT DOC

R10 (red-team audit-overclaim): rewrote the "19/19 PASS / Layer 1 OK"
framing as self-consistency of an author-encoded spec, NOT third-party
validation. Added "WHAT 'Layer 1 OK' DOES AND DOES NOT MEAN" paragraph
enumerating out-of-scope hazards (per-RPC race, sliding-window eviction,
multi-blob, gRPC cancellation, chunked-WC compose seam, atomics-ordering).

R11 (invariant-prover-MINOR-2): corrected Phase 4 trace-shape for
`_BuggedSilentZero.cfg` row.

R12 (red-team-MAJOR-7): Phase 5 gaps strengthened — TraceMachina#3 notes the
per-RPC race is now PARTIALLY modeled by SeamBObservesProducerErr;
new TraceMachina#6 names the atomics-ordering hazard.

R13: added Phase 6 — Revision section recording all revisions, what
remains DEFERRED with rationale, and a per-revision falsifiability check.

VERIFICATION

| Spec.cfg                                        | State count | Outcome  |
| StreamingBlobReadDuringWriteFixed.cfg           |       1,317 | clean    |
| StreamingBlobReadDuringWriteBugged.cfg          |          24 | violated |
| StreamingBlobReadDuringWrite_BuggedSilentZero   |          32 | violated |
| StreamingBlobReadDuringWriteBothOffBugged (NEW) |          38 | violated |
| StreamingBlobReadDuringWrite_1w_3r_3c.cfg       |      29,836 | clean    |
| StreamingBlobReadDuringWrite_1w_4r_2c.cfg       |     127,929 | clean    |
| ChunkRaceReadersBuggedH3Fixed.cfg (revised)     |         199 | clean    |
| ChunkRaceReadersBuggedH3Bugged.cfg              |          67 | violated |

Each Bugged cfg violates `NoSilentZeroToReader` for the SPECIFIC named
trace shape (per CLAUDE.md "Tests"). Each Fixed cfg runs clean against
its (now reviewer-honest) invariant list.

INVARIANT WALK (per CLAUDE.md "Invariant-walk in every dispatch prompt")

- Invariant being violated: the original spec's strong "no Silent*
  under post-fix" safety claim was OPTIMISTIC vs production (the per-RPC
  race window the TraceMachina#500 commit explicitly acknowledged as not fully
  closed).
- Mechanism that violates it: `bytestream_server.rs:1726-1867` —
  `consume_fut` returning `Ok(empty)` can win the select arm before
  `get_part_fut` resolves with Err, leading to the silent-zero shape
  for THIS poll.
- Mechanism that re-establishes it after the revision: the spec now
  models the race explicitly via `SeamBObservesProducerErr` + WF
  fairness; the safety form is replaced by a liveness leads-to that
  IS provable under fairness.
- Composite invariants this revision interacts with: the read-side
  observation contract (NoSilent*, NoCorrupt) × the writer's terminal-
  state classifier × the per-RPC seam-B propagation timing.
- Test that proves the revision re-establishes the invariant:
  `StreamingBlobReadDuringWriteFixed.cfg` runs clean against its
  post-revision invariant list AND
  `PostFixErrPropagationReachesReaderEventually` leads-to holds; the
  new `BothOffBugged.cfg` red-fails on `NoSilentZeroToReader`,
  confirming the composite-failure cell is exercised.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rejuvenile added a commit to rejuvenile/nativelink that referenced this pull request Aug 19, 2026
MAJOR-4 (red-team R8) — H3 Fixed cfg doc-comment reframed: explicitly
names that the 5 structural invariants hold in BOTH the bugged and any
hypothetical fixed module; none discriminate the H3 hazard. H3 fix proof
remains in ChunkRaceReadersV2Fixed.cfg. The Fixed cfg is a gate-
classifier-satisfying stopgap until the AllowEvictingMapInsert refactor
lands (Phase 5 TraceMachina#5).

MAJOR-5 (distributed-systems MAJOR-1) — spec TWO DISTINCT PRODUCTION
SEAMS block now contains an ABSTRACTION CAVEAT: 'seamBPropagated' is a
single global monotonic boolean; production 'ReaderState::maybe_get_part_result'
is per-RPC (constructed fresh =None inside each inner_read unfold at
bytestream_server.rs:1688). Spec is strictly weaker than production in
admitting race-loser interleavings; under-models, never over-models.
'At most one in-flight RPC sees the race-loser' cannot be expressed in
this spec.

MAJOR-6 (distributed-systems MAJOR-2) — spec PRODUCTION SHAPE block now
enumerates TWO READER PATHS: Path A is StreamingBlobReader::next_chunk
(streaming_blob.rs:631) with NO tokio::select! race; Path B is the
inner_read unfold w/ buf_channel (bytestream_server.rs:1712-1873) with
the seam-B race. Spec collapses both into one FSM (UNION-models);
race-loser DoneSilent* is unreachable in Path A in production.

MAJOR-7 (distributed-systems MAJOR-3) — WriterTerminationDisjunct
doc-comment now states the CFG CONTRACT: every cfg MUST satisfy
'at least one Allow*=TRUE' OR rely on WriterSendChunk reaching
chunkCount=NumChunks; reviewers verify per-cfg.
rejuvenile added a commit to rejuvenile/nativelink that referenced this pull request Aug 19, 2026
… AlreadyExists tonic_ok

Two convergent reviewers (assumption-auditor N2 + perf-optimizer N4)
flagged metric TraceMachina#5 (server_stable_digests_pusher_invoke_count) as
incomplete: three direct-push sites bypass stable_digests_pusher:
- fast_slow_store.rs:2616 try_self_retry_slow_write success arm
- fast_slow_store.rs:5244 failed-write drain re-broadcast
- fast_slow_store.rs:5498 failed-write drain re-broadcast

These directly push to stable_digests.lock() + notify, so they DO
get broadcast (queue_depth gauge reflects them) but they do NOT
bump pusher_invoke_count and they do NOT populate pusher_timestamps.
The pre-rename metric overstated its scope; per-digest
bis_broadcast_queue_latency silently drops samples for those digests.

Fix (option B from reviewer reports — smaller change, honest naming):
- Rename metric `server_stable_digests_pusher_invoke_count` →
  `server_stable_digests_pusher_invoke_via_commit_path_count`.
- Expand the field doc-comment to enumerate the three bypass sites
  and warn operators that this is NOT total BIS broadcast traffic
  (queue_depth gauge IS total).
- Update last_at gauge help text to surface the NTP-step caveat
  (distributed-systems MINOR-1) and cross-reference _count for
  monotonic verification.

Also addresses perf-optimizer N4 (AlreadyExists → tonic-Ok cache
leak): drop the record_tonic_ok call from the Code::AlreadyExists
arm at running_actions_manager.rs:5001-5010. When the slow tier
short-circuits update for an already-present blob,
stable_digests_pusher does NOT fire → no BIS chunk for this digest
→ the cache entry would sit for 10 min until TTL evicted it AND
the pin-release histogram would be polluted by samples that never
had a matching BIS arrival. Pure observability semantic: the
metric reflects fresh-write semantics; AlreadyExists is correctly
out of scope.

Tests: updated publish_emits_metrics_via_render_prometheus to use
the renamed metric path; all 13 phase0_metrics tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rejuvenile added a commit to rejuvenile/nativelink that referenced this pull request Aug 19, 2026
…na#502 (cross-crate miss)

streaming_blob_reader_failpoint_returns_error wrote 512 of 1024 declared
bytes then send_eof — encoding, incidentally, exactly the silent-short
case that TraceMachina#502 (48be7c6, 2026-05-16) made a guarded error at the reader.
TraceMachina#502 scoped its caller analysis to nativelink-util and updated that
crate's tests; this nativelink-store integration test was outside that
scope and has been red on main for 3+ weeks (invisible because the
failpoints CI step only runs on the GitHub fork remote — gitforge origin
main has no CI; follow-up tracked separately).

Triage verdict (read-only agent + reviewer verification): STALE-TEST,
not a production bug — the TraceMachina#502 defense is correct, production-active,
and guarded by 8 dedicated tests in nativelink-util (incl.
silent_short_eof_surfaces_as_internal + the production-shape
populate_path_silent_short_propagates_to_reader_as_err). The 2026-05-15
download-truncation incident is unrelated (OOM cross-PID stitching).

Fix: deliver the remaining 512 bytes (chunks 3+4, asserted) before
send_eof so the test exercises its actual contract — failpoint
RECOVERY → clean EOF — plus a 5s timeout deadlock detector and bespoke
expect messages on the EOF read.

Behavior changes: none (test-only).
Reviewers: code-reviewer + testing-czar, both APPROVE; findings
(comment wording, std::time::Duration consistency, inner unwrap →
bespoke expect) addressed. testing-czar confirmed no coverage loss.
Suite: 14/14 green (/tmp/fp5-final log).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
rejuvenile added a commit to rejuvenile/nativelink that referenced this pull request Aug 19, 2026
…rmanent-anchor doc claims

Addresses the bench-harness cadre findings on 7c5cdbde (red-team P1 +
assumption-auditor TraceMachina#1-5 + review-pair-b nit). Bench-harness-only; no production
code touched (only benchmarks/src/scenarios/worker_upload.rs).

CONVERGENT FINDING (red-team P1 + assumption-auditor TraceMachina#5): inter-iteration
async-spawn bleed in the *_rpc3ms cells. inner_upload_results uploads
stdout/stderr + Tree/Directory protos via update_oneshot;
FastSlowStore::update_oneshot spawns the slow write DETACHED
(fast_slow_store.rs:5785), not awaited by upload_results(). The trait-default
update_oneshot (store_trait.rs:1078) routes to LatencyInjectingStore::update →
3 ms sleep. measure() (mod.rs:200-202) starts the next sample's timer the
instant body().await returns, so without a drain each non-F2 iteration left
>=2 detached 3 ms-sleep tasks running into the NEXT sample's window — an
uncontrolled, scheduler-/core-count-dependent bleed (the same async-spawn
nondeterminism that disqualified N1, smaller amplitude). F2 + no_latency cells
were already clean (no slow-tier spawn / 0 ms sleep).

Invariant being violated: a permanent latency-anchor cell must measure one
self-contained upload, reproducibly — not one upload plus the scheduler-
dependent tail of the previous iteration's detached slow writes.
Mechanism that violates it: fast_slow_store.rs:5785 detached tokio::spawn of
the slow write, never awaited by upload_results(); the bench had no
inter-iteration drain.
Mechanism that re-establishes it: drain_slow_writes (worker_upload.rs) awaits
the slow-write map to quiesce to 0 at the head of each timed body, using the
production quiescence signals in_flight_empty_notify_handle() +
in_flight_slow_write_count(), with the lost-wakeup-safe pin!+enable()-before-
predicate pattern copied from FastSlowStore::flush_slow_writes
(fast_slow_store.rs:2360-2362). It is NOT flush_slow_writes (that sets
shutting_down=true permanently, fencing later iterations' spawns); it only
waits.
Composite invariants this fix interacts with:
- Timed window: drain sits at the body head because measure() has no out-of-
  window teardown slot; in the common case the map is already empty (the 3 ms
  detached writes finish during the longer upload) so the settle is near-zero
  and bounded, not part of the measured upload cost. Bounded by
  SLOW_WRITE_DRAIN_DEADLINE = 5 s; a residual there panics rather than
  silently inflating a sample.
- F2 / no_latency cells: drain returns immediately (no slow-tier spawn / 0 ms
  sleep), so the fix is inert for the already-clean cells.
- Same cas_store Arc is shared by the manager and the drain (worker_upload.rs
  :828 vs :887), so the drained map is exactly the one the uploads populate.
Test that proves the fix: u1_drain_slow_writes_quiesces_in_flight_map — drives
one detached slow write through the bench FastSlowStore (3 ms latency, so the
spawned task is provably mid-flight), asserts the in-flight count is non-zero,
then asserts drain_slow_writes returns 0 and the map is empty, under a
tokio::time::timeout deadlock detector. Mutation-verified: replacing the
notified await with an immediate return red-fails with "drain_slow_writes MUST
quiesce the in-flight map (return 0) — a non-zero residual means the detached
slow-write tail would bleed into the next sample's timed window" (left: 1,
right: 0).

Drain-vs-document choice: chose the DRAIN (red-team option a), not the
document-as-pipelined fallback (option b). A real quiescence signal exists
(in_flight_empty_notify + in_flight_slow_write_count), the wait is cheap and
deterministic (a definite signal, not sleep-as-sync), and a regression anchor
is far better served by isolated single-upload samples than by documenting a
scheduler-dependent pipeline tail. The one honest caveat — measure() has no
out-of-window teardown slot, so the drain is at the body head rather than
strictly between samples — is documented in both the helper and the cell doc;
because the tail is near-zero by the time the next body begins, the body-head
drain is a near-zero settle, not measured upload cost.

Permanent-anchor doc claims corrected (assumption-auditor FIX-FIRST):
1. "valid + shippable" / baseline discipline: clarified "valid" = the timed-
   window STRUCTURE (code-proven), NOT the numbers; the cell's p50/p99 are
   NOT yet a collected baseline (no U1 run exists); 100-file p99 stability at
   iters=20 is asserted by reasoning, not a two-run variance check.
2. "Tier-3 cadre would block any N1 seam": corrected. The COARSE output-upload
   window is ALREADY persisted in ExecutionMetadata
   (output_upload_start_timestamp running_actions_manager.rs:3804 ->
   output_upload_completed_timestamp :4329 -> output_upload_ms :4436), free to
   read. Only the FINE intra-phase split (batch_start - upload_start) lacks a
   field, and even that needs only a #[cfg(test)] sink through the already-
   injected Callbacks, not a hot-path prod hook. Honest reason N1 is deferred:
   OUT OF SCOPE for a bench fix-up, not impossible.
3. 3 ms "observed on buildcache/banjo": dropped "observed". Reframed as an
   explicit order-of-magnitude MODELLING choice for the has() RTT; added the
   repo's real figures for calibration (wire ~0.2 ms; remote write-leg p50
   ~152 ms / p99 ~720 ms, n=140) and noted 3 ms models the lightweight has()
   RTT, not the heavy update() write leg (so no conflict with 152 ms).
4. iters 5->20 "stable": softened to "more samples; first-sample outlier
   avoided; baseline not yet collected; stability unverified pending a two-run
   pass".
5. Differentiation story corrected: 3 ms differentiates no_latency<->rpc3ms
   reliably via the INLINE batch has() (+ foreground file update legs);
   stdout/proto update_oneshot latency is on the detached spawn and is now
   deliberately drained OUT of the sample (not racy-in-window); no_latency vs
   f2_mode are BOTH 0 ms — differentiated by the fast-store WRITE PATH, not by
   injected latency.

review-pair-b nit (Issue 3): the register_item_callback comment cited
fast_slow_store.rs:771 for the warn; the (false, Ok) arm's warn! macro is at
:779. Fixed.

Behavior changes: NONE in production. Bench-harness behavior change: each U1
rpc3ms sample now excludes the prior iteration's detached slow-write tail
(drained to quiescence at the body head) — the cells now measure isolated
single-upload latency instead of upload-plus-bleed. F2 + no_latency cells
unchanged (drain inert).

Tests: full benchmarks lib suite 56 passed (was 55, +1 drain test). The 15
clippy errors under --tests are all in nativelink-error/src/lib.rs (a
dependency, untouched) — pre-existing toolchain drift, not this change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
rejuvenile added a commit to rejuvenile/nativelink that referenced this pull request Aug 19, 2026
…re-first (default-off)

Re-derives the existing v2 slot-full Stage B code (e5cebc2) to v3 per design
§2.3-v3 (RE-DERIVATION banner). The live fleet runs `max_inflight_tasks=0`
(unlimited slots), so the v2 "busy-because-FULL" gate could NEVER fire; v3 makes
"busy" = P-SATURATED (the pcore-first signal), gates the hold on `p_gate_active`,
and computes T_wait_W as the k-th-soonest completion (time to regain ONE P-slot),
not the sum.

Invariant-walk (§2.3-v3):
- Invariant violated: the matcher must NOT reconstruct an op's tree on a cold X
  (routed-away-from a pre-built P-saturated holder W by pcore-first) when W will
  regain a P-slot before the reconstruct completes.
- Mechanism that violates it: pcore-first's `worker_is_viable_gated`
  (api_worker_scheduler.rs:2259) EXCLUDES a P-saturated holder W from the cache
  tiers → the op routes to a cold X that reconstructs, losing W's pre-built tree.
- Mechanism that re-establishes it: the temporal hold gate
  (api_worker_scheduler.rs:2592) — when `enable_speculative_hold && p_gate_active
  && all three cache winners None`, and ∃ a P-saturated root-holder W (from the
  already-materialized `p_gated_excluded` set) with `t_wait_w_locked(W) < T_SETUP`
  ∧ not overdue → return `None` (re-queue the op for W).
- Composite invariants: lock discipline (the k-th core `t_wait_w_locked` is
  lock-free — no `.read()/.write()/.await` — preserving the deadlock fix);
  pcore-first spread-under-saturation (gated on `p_gate_active`, so NO hold on a
  Phase-2-lifted fully-saturated fleet); C1 non-locality-X (all three winner
  guards); max-hold cap (wall-time OR cycle-count starvation bound); A5 legacy
  worker (p_core_count=0 → ungated → never a hold candidate).
- Proving tests: hold_fires_for_p_saturated_holder (k=1, cold X, p_gate_active →
  hold); no_hold_when_p_gate_inactive (fully-P-saturated fleet → NO hold, the
  distsys BLOCK-2 guard); no_hold_when_gossip_subtree_holder_x /
  c1_locality_winner_blocks_hold (C1); deep_backlog_declines +
  k_th_completion_frees_one_p_slot + stage_c_worker_time_to_free_kth_soonest (k-th
  order statistic); hold_no_deadlock_under_write_lock (lock-free core).

v3 adaptations (each cites the design fix):
1. Busy → P-saturation (v3.1/v3.7-#2/TraceMachina#3): `find_busy_full_holder` →
   `find_p_saturated_holder`, which FILTERS the already-materialized
   `p_gated_excluded` set (:2194 — the viable-but-no-P-headroom holders) for
   root-holders. No second `self.workers.iter()` scan. Capability filter (v3.7-TraceMachina#6)
   holds by construction (`p_gated_excluded` is built from `worker_is_viable`
   candidates = capability-index ∩ platform ∩ health).
2. t_wait_w_locked sum → k-th soonest (v3.3/v3.7-#4): `k = running − p_core_count
   + 1`, clamped to [1, timed_count]; k-th smallest remaining via
   `select_nth_unstable_by` (O(running), no full sort); overdue computed over the
   k-soonest set only. KEPT lock-free. `worker_time_to_free` wrapper delegates.
3. Gate condition (v3.7-TraceMachina#1/TraceMachina#5): `enable_speculative_hold && p_gate_active &&
   dir_cache_winner.is_none() && subtree_coverage_winner.is_none() &&
   locality_winner.is_none()`.
4. KEPT verbatim (v3.4): capped HoldRecord LruCache + 3 reaps, None-return, 4
   counters, default-OFF flag, max-hold cap.

Carried Tier-3 fixes (from e5cebc2 review):
- DELETED the fabricated T_SETUP histogram citation (":3408 #p1p2 p50 ~1.4s" — the
  file does not exist); replaced with an honest coarse-guess/tune-via-soak comment.
  T_SETUP=3s value unchanged.
- FIXED the deadlock-guard test: the reserve now runs on a SEPARATE spawned task
  whose JoinHandle is `timeout`ed (the v2 inline-await form WEDGED the current-
  thread runtime — empirically confirmed: a mutated inline reserve did not return
  in 2 min despite a 5 s internal timeout; the JoinHandle form fires the bespoke
  DEADLOCK message at 5.01 s).
- COVERAGE gaps: HOLD_MAX_CYCLES arm (hold_max_cycles_arm), both defensive reaps
  (defensive_reap_on_reroute/_on_evict — coverage+no-panic, the reap is defensive),
  the do_try_match None re-queue seam (do_try_match_none_requeue_seam — production
  composition), overdue boundary (overdue_boundary_elapsed_equals_estimate),
  subtree-holder disjunct (subtree_holder_disjunct).
- MIGRATED the Stage-C `worker_time_to_free` unit test from SUM to k-th semantics
  (stage_c_worker_time_to_free_kth_soonest) since the core changed sum→k-th.

Behavior changes (enumerated against production composition):
- enable_speculative_hold=false (DEFAULT, prod): NONE. The gate is gated on the
  flag; with it off the matcher assigns byte-identically to the pre-Stage-B path
  (flag_off_no_hold pins this against a pcore-first-ON fleet).
- worker_time_to_free (async wrapper) semantics changed SUM → k-th-soonest. It has
  NO production consumer (only Stage-C's own unit tests + the lock-free core, which
  the gate calls directly); prod behavior unaffected.
- enable_speculative_hold=true (soak-only, NOT prod; requires p_headroom_gate ON):
  on a P-headroom-available fleet, an op whose only holder W is P-saturated and
  about to regain a P-slot (t_wait_W < 3 s, not overdue) and whose alternatives are
  all cold (no dir/subtree/locality winner) is HELD (None, re-queued) instead of
  rebound to a cold worker. Bounded by the max-hold cap; NO hold on a fully-
  P-saturated Phase-2-lifted fleet (p_gate_active false). All other reserve
  outcomes unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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