Skip to content

Rewrite the build infrastructure - #394

Merged
aaronmondal merged 1 commit into
TraceMachina:mainfrom
aaronmondal:rewrite-build
Nov 23, 2023
Merged

aaronmondal merged 1 commit into
TraceMachina:mainfrom
aaronmondal:rewrite-build

Conversation

@aaronmondal

@aaronmondal aaronmondal commented Nov 15, 2023 •

Copy link
Copy Markdown
Contributor

Our previous build layout was fairly custom and hard to maintain.
Migrate to a more standards-conform layout that is compatible with good
Cargo and Bazel practices and feels natural to develop in.

This commit swaps the previous "one-crate-per-file" layout to a layout
where we have a few medium sized crates that are just fine-grained
enough to allow almost the same amount of parallelization as the
previous fine-grained layout.

The new layout enables dependency auto-updaters, standard cargo tooling
and doc autogeneration. This should improve DX quite a bit and gives us
some much needed compatibility with nix/cargo interop and should make
most standard cargo extensions compatible with the repository.

Some notable changes to the file layout:

  • The previous error crate is no longer part of utils. It is now a
    standalone module. This was necessary to keep macro exports compatible
    with the Cargo build.
  • The old scheduler.rs file is now split into action_scheduler.rs
    and worker_scheduler.rs since we don't want imports like
    native_link_scheduler::scheduler.
  • Some tests now use a slightly different import mechanism for test-only
    dependencies.
  • The store traits are now in native_link_utils, as it was used
    virtually everywhere. Moving this up in the dependency graph allows
    for higher parallelization of the other crates.

Added documentation targets for every crate (except the cas binary
itself, as this will require a dedicated commit). These doc targets can
be viewed by pointing a browser to
bazel-bin/thetarget.rustdoc/thetarget/index.html.

Added rustfmt configuration to standardize imports. We now use crate::
syntax which plays well with rusfmts's import formatting. The new layout
makes it easier to spot crate/directory interdependencies so the we can
remove those in future commits.


This change is Reviewable

@allada allada left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you update this PR and lets get it landed?

Reviewable status: 0 of 202 files reviewed, all discussions resolved (waiting on @blakehatch and @MarcusSorealheis)

@aaronmondal
aaronmondal requested a review from allada November 21, 2023 04:53
@aaronmondal
aaronmondal force-pushed the rewrite-build branch 4 times, most recently from f389570 to 33b1d29 Compare November 21, 2023 06:12

@allada allada left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 98 of 202 files at r1, 13 of 108 files at r2, all commit messages.
Reviewable status: 111 of 213 files reviewed, 9 unresolved discussions (waiting on @aaronmondal, @blakehatch, and @MarcusSorealheis)


BUILD.bazel line 11 at r2 (raw file):

exports_files(
    glob(["src/**", "tests/**"]) + [

nit: Lets list every file.


BUILD.bazel line 22 at r2 (raw file):

    name = "native_link",
    srcs = glob(
        include = ["src/**/*.rs"],

nit: Lets list every file.


BUILD.bazel line 100 at r2 (raw file):

rust_test_suite(
    name = "native_link_tests",
    srcs = glob(["tests/*.rs"]),

nit: Lets list every file.


Cargo.toml line 7 at r2 (raw file):

[workspace]
resolver = "2"

Do we need this now?


src/config.rs line 1 at r2 (raw file):

pub mod cas_server;

nit: Needs copyright.


src/grpc_service.rs line 1 at r2 (raw file):

pub mod ac_server;

nit: Needs copyright.


src/lib.rs line 1 at r2 (raw file):

pub mod config;

nit: Needs copyright.


src/scheduler.rs line 1 at r2 (raw file):

pub mod action_messages;

nit: Needs copyright.


src/store.rs line 15 at r2 (raw file):

// limitations under the License.

pub mod ac_utils;

yuck, can we somehow use crate:: instead?

@allada allada left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 111 of 213 files reviewed, 10 unresolved discussions (waiting on @aaronmondal, @blakehatch, and @MarcusSorealheis)


BUILD.bazel line 74 at r2 (raw file):

rust_binary(
    name = "nlink",

Lets not rename this yet, we will do it in a follow up PR. Because there's things like terraform that will likely break.

@aaronmondal aaronmondal left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 104 of 214 files reviewed, 3 unresolved discussions (waiting on @allada, @blakehatch, and @MarcusSorealheis)


BUILD.bazel line 74 at r2 (raw file):

Previously, allada (Nathan (Blaise) Bruer) wrote…

Lets not rename this yet, we will do it in a follow up PR. Because there's things like terraform that will likely break.

Done.


Cargo.toml line 7 at r2 (raw file):

Previously, allada (Nathan (Blaise) Bruer) wrote…

Do we need this now?

Unfortunately yes. Both the Bazel resolver and nix cargo wrappers need this workspace for some reason. The resolver = "2" line is technically redundant and we could remove that. I think there was some issue I can't remember where it was recommended to make the resover version explicit.


src/store.rs line 15 at r2 (raw file):

Previously, allada (Nathan (Blaise) Bruer) wrote…

yuck, can we somehow use crate:: instead?

I removed the rest of the actual code from here and put it in a store_manager subcrate. The amount of files here is a bit much, but I'd say this is more of a "not hiding anything" type of thing. Arguably quite ugly, but this gives us very explicit import paths that (in conjunction with the now ordered imports in every file) make it much easier to see where we could reduce interdependencies and split this into sensible smaller crates.

There are a few alternatives to the thing.rs/thing directory approach, but this seems to be the current "recommended" way of doing things and it's at least better than the mod.rs approach where literally every file in the repo would just be called mod.rs 🤣

As a side note, a benefit of this explicit pub mod approach is that it makes it quite easy to turn features on and off by using markers like #[cfg(feature = "s3")].

@aaronmondal aaronmondal left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 104 of 214 files reviewed, 3 unresolved discussions (waiting on @allada, @blakehatch, and @MarcusSorealheis)


src/store.rs line 15 at r2 (raw file):

Previously, aaronmondal (Aaron Siddhartha Mondal) wrote…

I removed the rest of the actual code from here and put it in a store_manager subcrate. The amount of files here is a bit much, but I'd say this is more of a "not hiding anything" type of thing. Arguably quite ugly, but this gives us very explicit import paths that (in conjunction with the now ordered imports in every file) make it much easier to see where we could reduce interdependencies and split this into sensible smaller crates.

There are a few alternatives to the thing.rs/thing directory approach, but this seems to be the current "recommended" way of doing things and it's at least better than the mod.rs approach where literally every file in the repo would just be called mod.rs 🤣

As a side note, a benefit of this explicit pub mod approach is that it makes it quite easy to turn features on and off by using markers like #[cfg(feature = "s3")].

Actually, when I think about it, this pattern might be better than I initially thought. With the right code layout,thing.rs can act as the public API interface (think exposed header in C++ land) while all the files in the thing directory act as implementation details. In that case we'd not have the pub mod lines anymore and instead have a seemingly very clean interface where thing.rs contains the entire public API for a crate and everything else can be ignored by users. This would also make it easy to spot API changes since one only needs to look at history of the thing.rs file.

@aaronmondal
aaronmondal force-pushed the rewrite-build branch 2 times, most recently from 02f1a57 to 54bd14a Compare November 23, 2023 01:23
@aaronmondal
aaronmondal requested a review from allada November 23, 2023 01:24
@aaronmondal

Copy link
Copy Markdown
Contributor Author

FYI @allada In case you're wondering why the new targets are native-link-something and not native_link_somethin as is usually done in Bazel, this is because the dashes make it easier to distinguish build targets and crate names when grepping and it allows shell completion for targets like bazel build native-<TAB>

Also note that all the test targets are just called integration because the rust_test_suite automatically appends the _tests string and it looks nicer in the terminal this way.

@allada allada left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:lgtm:

All the details we can fight another day.

Reviewed 4 of 202 files at r1, 2 of 108 files at r2, 1 of 1 files at r4, 119 of 119 files at r5, 12 of 12 files at r6, all commit messages.
Reviewable status: all files reviewed, 22 unresolved discussions (waiting on @aaronmondal, @blakehatch, and @MarcusSorealheis)


native-link-service/tests/ac_server_test.rs line 1 at r6 (raw file):

// Copyright 2022 The Turbo Cache Authors. All rights reserved.

nit: Wrong name.


native-link-service/tests/bytestream_server_test.rs line 1 at r6 (raw file):

// Copyright 2022 The Turbo Cache Authors. All rights reserved.

nit: Wrong name.


native-link-service/tests/cas_server_test.rs line 1 at r6 (raw file):

// Copyright 2022 The Turbo Cache Authors. All rights reserved.

nit: Wrong name.


native-link-store/tests/ac_utils_test.rs line 1 at r6 (raw file):

// Copyright 2023 The Turbo Cache Authors. All rights reserved.

nit: Wrong name.


native-link-store/tests/compression_store_test.rs line 1 at r6 (raw file):

// Copyright 2022 The Turbo Cache Authors. All rights reserved.

nit: Wrong name.


native-link-store/tests/dedup_store_test.rs line 1 at r6 (raw file):

// Copyright 2022 The Turbo Cache Authors. All rights reserved.

nit: Wrong name.


native-link-store/tests/fast_slow_store_test.rs line 1 at r6 (raw file):

// Copyright 2022 The Turbo Cache Authors. All rights reserved.

nit: Wrong name.


native-link-store/tests/filesystem_store_test.rs line 1 at r6 (raw file):

// Copyright 2022 The Turbo Cache Authors. All rights reserved.

nit: Wrong name.


native-link-store/tests/memory_store_test.rs line 1 at r6 (raw file):

// Copyright 2022 The Turbo Cache Authors. All rights reserved.

nit: Wrong name.


native-link-store/tests/ref_store_test.rs line 1 at r6 (raw file):

// Copyright 2022 The Turbo Cache Authors. All rights reserved.

nit: Wrong name.


native-link-store/tests/shard_store_test.rs line 1 at r6 (raw file):

// Copyright 2023 The Turbo Cache Authors. All rights reserved.

nit: Wrong name.


native-link-store/tests/size_partitioning_store_test.rs line 1 at r6 (raw file):

// Copyright 2022 The Turbo Cache Authors. All rights reserved.

nit: Wrong name.


native-link-store/tests/verify_store_test.rs line 1 at r6 (raw file):

// Copyright 2022 The Turbo Cache Authors. All rights reserved.

nit: Wrong name.


native-link-util/tests/async_fixed_buffer_test.rs line 1 at r6 (raw file):

// Copyright 2022 The Turbo Cache Authors. All rights reserved.

nit: Wrong name.


native-link-util/tests/buf_channel_test.rs line 1 at r6 (raw file):

// Copyright 2022 The Turbo Cache Authors. All rights reserved.

nit: Wrong name.


native-link-util/tests/evicting_map_test.rs line 1 at r6 (raw file):

// Copyright 2022 The Turbo Cache Authors. All rights reserved.

nit: Wrong name.


native-link-util/tests/fastcdc_test.rs line 1 at r6 (raw file):

// Copyright 2022 The Turbo Cache Authors. All rights reserved.

nit: Wrong name.


native-link-util/tests/fs_test.rs line 1 at r6 (raw file):

// Copyright 2023 The Turbo Cache Authors. All rights reserved.

nit: Wrong name.


native-link-util/tests/resource_info_test.rs line 1 at r6 (raw file):

// Copyright 2022 The Turbo Cache Authors. All rights reserved.

nit: Wrong name.


native-link-util/tests/retry_test.rs line 1 at r6 (raw file):

// Copyright 2022 The Turbo Cache Authors. All rights reserved.

nit: Wrong name.


native-link-worker/tests/utils/local_worker_test_utils.rs line 1 at r6 (raw file):

// Copyright 2022 The Turbo Cache Authors. All rights reserved.

nit: Wrong name.


native-link-worker/tests/utils/mock_running_actions_manager.rs line 1 at r6 (raw file):

// Copyright 2022 The Turbo Cache Authors. All rights reserved.

nit: Wrong name.

@aaronmondal aaronmondal left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🥳

Reviewable status: 161 of 239 files reviewed, all discussions resolved (waiting on @allada, @blakehatch, and @MarcusSorealheis)

Our previous build layout was fairly custom and hard to maintain.
Migrate to a more standards-conform layout that is compatible with good
Cargo and Bazel practices and feels natural to develop in.

This commit swaps the previous "one-crate-per-file" layout to a layout
where we have a few medium sized crates that are just fine-grained
enough to allow almost the same amount of parallelization as the
previous fine-grained layout.

The new layout enables dependency auto-updaters, standard cargo tooling
and doc autogeneration. This should improve DX quite a bit and gives us
some much needed compatibility with nix/cargo interop and should make
most standard cargo extensions compatible with the repository.

Some notable changes to the file layout:

- The previous `error` crate is no longer part of `utils`. It is now a
  standalone module. This was necessary to keep macro exports compatible
  with the Cargo build.
- The old `scheduler.rs` file is now split into `action_scheduler.rs`
  and `worker_scheduler.rs` since we don't want imports like
  `native_link_scheduler::scheduler`.
- Some tests now use a slightly different import mechanism for test-only
  dependencies.
- The store traits are now in `native_link_utils`, as it was used
  virtually everywhere. Moving this up in the dependency graph allows
  for higher parallelization of the other crates.

Added documentation targets for every crate (except the `cas` binary
itself, as this will require a dedicated commit). These doc targets can
be viewed by pointing a browser to
`bazel-bin/thetarget.rustdoc/thetarget/index.html`.

Added rustfmt configuration to standardize imports. We now use `crate::`
syntax which plays well with rusfmts's import formatting. The new layout
makes it easier to spot crate/directory interdependencies so the we can
remove those in future commits.
@aaronmondal
aaronmondal merged commit 3147265 into TraceMachina:main Nov 23, 2023
TripleKai pushed a commit to TripleKai/turbo-cache that referenced this pull request Nov 29, 2023
Our previous build layout was fairly custom and hard to maintain.
Migrate to a more standards-conform layout that is compatible with good
Cargo and Bazel practices and feels natural to develop in.

This commit swaps the previous "one-crate-per-file" layout to a layout
where we have a few medium sized crates that are just fine-grained
enough to allow almost the same amount of parallelization as the
previous layout.

The new layout enables dependency auto-updaters, standard cargo tooling
and doc autogeneration. This should improve DX quite a bit and gives us
some much needed compatibility with nix/cargo interop and should make
most standard cargo extensions compatible with the repository.

Some notable changes to the file layout:

- The previous `error` crate is no longer part of `utils`. It is now a
  standalone module. This was necessary to keep macro exports compatible
  with the Cargo build.
- The old `scheduler.rs` file is now split into `action_scheduler.rs`
  and `worker_scheduler.rs` since we don't want imports like
  `native_link_scheduler::scheduler`.
- Some tests now use a slightly different import mechanism for test-only
  dependencies.
- The store traits are now in `native_link_utils`, as it was used
  virtually everywhere. Moving this up in the dependency graph allows
  for higher parallelization of the other crates.

Added documentation targets for every crate (except the `cas` binary
itself, as this will require a dedicated commit). These doc targets can
be viewed by pointing a browser to
`bazel-bin/thetarget.rustdoc/thetarget/index.html`.

Added rustfmt configuration to standardize imports. We now use `crate::`
syntax which plays well with rusfmts's import formatting. The new layout
makes it easier to spot crate/directory interdependencies so that we can
remove them in future commits.
TripleKai pushed a commit to TripleKai/turbo-cache that referenced this pull request Nov 30, 2023
Our previous build layout was fairly custom and hard to maintain.
Migrate to a more standards-conform layout that is compatible with good
Cargo and Bazel practices and feels natural to develop in.

This commit swaps the previous "one-crate-per-file" layout to a layout
where we have a few medium sized crates that are just fine-grained
enough to allow almost the same amount of parallelization as the
previous layout.

The new layout enables dependency auto-updaters, standard cargo tooling
and doc autogeneration. This should improve DX quite a bit and gives us
some much needed compatibility with nix/cargo interop and should make
most standard cargo extensions compatible with the repository.

Some notable changes to the file layout:

- The previous `error` crate is no longer part of `utils`. It is now a
  standalone module. This was necessary to keep macro exports compatible
  with the Cargo build.
- The old `scheduler.rs` file is now split into `action_scheduler.rs`
  and `worker_scheduler.rs` since we don't want imports like
  `native_link_scheduler::scheduler`.
- Some tests now use a slightly different import mechanism for test-only
  dependencies.
- The store traits are now in `native_link_utils`, as it was used
  virtually everywhere. Moving this up in the dependency graph allows
  for higher parallelization of the other crates.

Added documentation targets for every crate (except the `cas` binary
itself, as this will require a dedicated commit). These doc targets can
be viewed by pointing a browser to
`bazel-bin/thetarget.rustdoc/thetarget/index.html`.

Added rustfmt configuration to standardize imports. We now use `crate::`
syntax which plays well with rusfmts's import formatting. The new layout
makes it easier to spot crate/directory interdependencies so that we can
remove them in future commits.
MarcusSorealheis pushed a commit to MarcusSorealheis/nativelink that referenced this pull request Nov 3, 2025
Our previous build layout was fairly custom and hard to maintain.
Migrate to a more standards-conform layout that is compatible with good
Cargo and Bazel practices and feels natural to develop in.

This commit swaps the previous "one-crate-per-file" layout to a layout
where we have a few medium sized crates that are just fine-grained
enough to allow almost the same amount of parallelization as the
previous layout.

The new layout enables dependency auto-updaters, standard cargo tooling
and doc autogeneration. This should improve DX quite a bit and gives us
some much needed compatibility with nix/cargo interop and should make
most standard cargo extensions compatible with the repository.

Some notable changes to the file layout:

- The previous `error` crate is no longer part of `utils`. It is now a
  standalone module. This was necessary to keep macro exports compatible
  with the Cargo build.
- The old `scheduler.rs` file is now split into `action_scheduler.rs`
  and `worker_scheduler.rs` since we don't want imports like
  `native_link_scheduler::scheduler`.
- Some tests now use a slightly different import mechanism for test-only
  dependencies.
- The store traits are now in `native_link_utils`, as it was used
  virtually everywhere. Moving this up in the dependency graph allows
  for higher parallelization of the other crates.

Added documentation targets for every crate (except the `cas` binary
itself, as this will require a dedicated commit). These doc targets can
be viewed by pointing a browser to
`bazel-bin/thetarget.rustdoc/thetarget/index.html`.

Added rustfmt configuration to standardize imports. We now use `crate::`
syntax which plays well with rusfmts's import formatting. The new layout
makes it easier to spot crate/directory interdependencies so that we can
remove them in future commits.
rejuvenile added a commit to rejuvenile/nativelink that referenced this pull request Aug 19, 2026
…tlier probe (TraceMachina#413 Option A)

Per audit `.claude/audits/413-large-blob-cascade-design-20260511.md`:
17-min post-deploy window 21:55:38–22:13 PDT 2026-05-11 captured 11
cascade events / 9 distinct digests (1.2% of 900 chunked commits) at
the prior cap=64. Size distribution:

- 64-128 MiB: 2 events
- 128-256 MiB: 7 events
- 256-540 MiB: 2 events

Cap=256 (× 1 MiB CHUNK_SIZE = 256 MiB per-blob, exactly equal to
`MAX_CHUNKED_BLOB_SIZE`) structurally eliminates 9 of 11 events
(everything ≤256 MiB). The 2 residual 256-540 MB events (RustcLink
output blobs / macOS bundles) are tracked under TraceMachina#413 Option D
(server-internal driver fan-out, design doc
`.claude/audits/303-driver-fanout-design-20260512.md`) and will be
revisited once the new `back_edge_ms` probe data validates the
inferred ~25-50 MB/s per-blob drain ceiling.

**Bottleneck localized via TraceMachina#413 audit:** `chunk_send_ms = 0` for ALL
14 logged steps in cascade trace — the producer never blocks. The
cascade is **chunker → driver back-edge**, where per-blob driver
pwrite (serialized by per-entry async mutex at
`chunked_filesystem.rs:467`) drains slower than the chunker fills
the per-blob mpsc. cap=256 widens the buffer; the underlying drain
rate (TraceMachina#394) is unchanged.

**Aggregate RSS bound unchanged.** Per-blob × concurrent-blobs is
NOT the regulator. The §13.2 12-16 GiB worst-case server RSS bound
is derived from the GLOBAL `ChunkBudget` (4 GiB / 4096 permits at
`chunk_budget.rs:52`) and per-blob `PinBudget`. Bumping cap=256
shifts fan-out shape (max 16 saturated blobs at this cap; was 64
at cap=64; was 256 at cap=16) but not the aggregate ceiling.

**New `back_edge_ms` outlier probe** at `chunked_driver.rs:987-999`:
emits `warn!` when per-chunk back-edge drain exceeds 50 ms (40 ms =
1 MiB / 25 MB/s healthy ceiling + 25 % cushion). Survives in
release builds (`Cargo.toml:101 release_max_level_info` retains
`warn!`/`error!`; only `debug!`/`trace!` are compiled out — a
`debug!`-level probe would have been dead code in production).
Fires only on the success path; the timeout arm
(`record_pwrite_timeout_and_maybe_warn`) and write-error arm emit
their own `warn!`s without `back_edge_ms`. Acceptable for cap-
tuning: failures already log loudly; outlier-on-success was the gap.

Field name `back_edge_ms` (NOT `pwrite_ms`) reflects the actual
scope: the wall-clock includes per-blob async-mutex acquire BEFORE
spawn_blocking, so a same-digest mutex contention spike shows up as
"async-mutex hold-time" in the >50 ms arm — operator-actionable,
correctly attributed.

**Updated:**
- `PER_BLOB_MPSC_CAP` constant 64 → 256 + rewritten doc-comment
  (audit cite, back-edge bound rationale, MAX_CHUNKED_BLOB_SIZE
  natural ceiling, observability gap acknowledgment)
- ChunkPin doc-comment math (64 MiB → 256 MiB)
- Unit test renamed `per_blob_mpsc_cap_is_sixtyfour` →
  `per_blob_mpsc_cap_is_two_fifty_six`, updated body + pin doc
- NEW `global_chunk_budget_remains_4_gib_bound_at_cap_256` composite-
  invariant test (CLAUDE.md "Admission/Eviction/Pin Composability"
  mandatory): asserts `PER_BLOB_MPSC_CAP × 16 == TOTAL_CHUNK_PERMITS`
  + saturates 16 × cap = 4096 permits + 17th `try_acquire_chunk()`
  returns `None`. Mutation-verified RED-fail with bespoke
  "composite invariant violated" message.
- `chunked_client.rs` module-doc reference (256; bumped from 64
  on 2026-05-12; was 16 before 2026-05-11; see TraceMachina#413 audit)
- `chunked_write_handler.rs:591` stale `(64)` literal dropped from
  comment (pattern has gone stale 3 cap-bumps in a row; symbolic
  `PER_BLOB_MPSC_CAP` only)
- Integration test `handler_per_blob_mpsc_full_returns_resource_exhausted_with_backpressure_signal`
  — `const N: usize = 32` → `const N: usize = PER_BLOB_MPSC_CAP + 1`
  (symbolic, with `wrapping_add` for byte-fill overflow safety) +
  imports + history-of-regression doc (16 → 64 → 256). Was already
  toothless at cap=64; cap=256 doubly so. Restored to actually
  exercise the rejection branch.

Composite invariant (per CLAUDE.md): gate-active (per-blob mpsc
admission cap) ⇒ (eviction-recovers-fast via chunk-completion
permit-drop OR pin-prevents-double-admission via PinBudget). Cap
bump shifts which corner fires first; the global ChunkBudget
remains the dominant gate at 4 GiB.

Reviewer cadre (4 + 2 follow-up):
- perf-optimizer: APPROVE (after fix-up: 2 BLOCKERs closed cleanly,
  mutation-verified — debug→warn release-survival + integration test
  symbolic N + composite-invariant test with bespoke red-fail)
- distributed-systems-reviewer: APPROVE-WITH-FINDINGS (no
  composite invariant violated; cap edge with MAX_CHUNKED_BLOB_SIZE
  validated; bitmap completeness exact-equal at 256 chunks; TraceMachina#345
  mirror cap interaction confirmed unrelated for chunked path)
- code-reviewer: APPROVE-WITH-FINDINGS (file-local `?digest`
  convention preserved over CLAUDE.md style guide; sibling-pwrite-
  probe audit clean — no other per-chunk pwrite sites)
- assumption-auditor: SHIP (12/12 claims PASS post-fix-up; mutation
  re-verified the composite test red-fails with the bespoke message;
  9-of-11 cascade-coverage doc citation corrected from "8 of 11")

Build: `cargo check -p nativelink-store -p nativelink-service` clean.
Test: 59 chunked + 20 chunked_write_handler tests pass; new
`global_chunk_budget_remains_4_gib_bound_at_cap_256` passes; renamed
constant-pin test passes; integration test now actually exercises
the rejection branch at N=257.

Follow-ups filed:
- TraceMachina#413 Option D (driver fan-out, design ready, awaiting pwrite_ms
  data — see `.claude/audits/303-driver-fanout-design-20260512.md`)
- TraceMachina#420 (chunked test /tmp tempdir leak — separate from this diff)

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#394/TraceMachina#413 Phase 1 measurement gate

Per audit `.claude/audits/394-413-saturation-class-plan-20260512.md` §8.
Phase 1 of TraceMachina#394 (chunked admission p99) / TraceMachina#413 (large-blob cascade)
saturation-class plan: ADD INSTRUMENTATION ONLY — no architectural
change. Phase 2 (Option C+A — unbounded mpsc + send().await) lands
AFTER this probe confirms the pulse-burst hypothesis in production.

**Falsifiable prediction (audit §8):** for blobs that go on to
cascade, at least one 100 ms window shows
`chunks_in_window > BURST_THRESHOLD_CHUNKS_PER_WINDOW (= 50)` —
i.e., producer arrived ≥5x faster than the inferred ~25-50 MB/s
per-blob drain ceiling cited in `chunked_driver.rs`'s
`PER_BLOB_MPSC_CAP` doc. Clean blobs are predicted to stay <20.

**Composite invariant unchanged.** No gate / pin / TTL corner is
modified. The probe observes the producer-side admit attempts;
sub-gates (1) global ChunkBudget, (2) PinBudget, and (3) per-blob
mpsc still gate identically.

**Red-team 20260512 RECONSIDER-PREMISE amendments** (this revision):

Finding 1 (BLOCK): the original probe could not falsify
`H_drain_stall`. Under a wedged drain, the producer hammers a
saturated mpsc, `record_attempt` fires BEFORE `try_send`, and
rejected attempts still count toward `attempts_in_window`. A
drain-stall therefore looks IDENTICAL to a pulse-burst at the warn
site — instrument could not discriminate the two hypotheses it was
deployed to discriminate.

Fix: add a per-window admitted snapshot
(`chunks_admitted_at_window_start: u32`) captured when the window
opens, and emit `admitted_in_window = chunks_admitted_total -
chunks_admitted_at_window_start` in BOTH warn sites. No cross-task
coordination because `chunks_admitted_total` is incremented by
`record_success` on the same single-task admit loop immediately
after a successful `try_send`. Operator discriminator:

  - `admitted_in_window ≈ chunks_in_window` → pulse-burst (queue
    accepted the burst; producer outran a healthy drain)
  - `admitted_in_window << chunks_in_window` → drain-stall (queue
    stayed full; producer hammered rejections on a wedged drain)

Finding 3 (silent-probe vs broken-probe): add an `info!` heartbeat
at admit-loop entry in `dispatch_chunks_to_driver`. Fires exactly
once per Bazel-facing chunked stream. Tells the operator "the probe
was reached for this digest" even if no warn ever fires for the
stream, eliminating the prior log-quiet → "is the probe disarmed,
threshold never crossed, or this code path not reached?" ambiguity.
Format: `info!(target: "nativelink_service::chunked_write_handler",
?digest, "producer-arrival probe armed (TraceMachina#394/TraceMachina#413 Phase 1)")`.

Assumption-auditor CLAIM 3 PARTIAL: doc-comment now explicitly
relates the probe to the existing `back_edge_ms` probe — "shares the
`warn!` / `?digest` / `offset` shape" but latches one-per-window to
avoid log-flood on sustained bursts (the `back_edge_ms` probe fires
per-chunk on the drain side; this one fires once-per-100ms-window on
the admit side).

Assumption-auditor CLAIM 4 PARTIAL: under-action test now drives
N=200 admissions (up from 60). Even with one mid-loop window
rollover on loaded CI, the second window crosses the 50-attempt
threshold with plenty of margin, eliminating a flake hazard.

Red-team Finding 2 (threshold-calibration unfalsifiability):
deferred to follow-up tracker — run probe with
`BURST_THRESHOLD_CHUNKS_PER_WINDOW=0` (always-fire) for 5 min in
baseline mode to measure healthy distribution before locking the
threshold. Current 50/100ms is inferred from the same 25-50 MB/s
drain rate the experiment is meant to measure; that circular
justification is documented in the const doc.

**New per-stream probe state** (`ProducerArrivalProbe`, owned by
`dispatch_chunks_to_driver`'s admit loop — single-task per blob, no
atomicity needed):
- `first_attempt_at: Option<Instant>` — for
  `producer_arrival_us_since_admission` on the rejection log
- `window_start_at: Option<Instant>` + `attempts_in_window: u32` —
  rolling 100 ms count of admission ATTEMPTS (counts producer rate
  regardless of which sub-gate accepts/rejects)
- `warned_this_window: bool` — latches one warn per pulse, avoids
  log-flood on sustained burst within a single window
- `chunks_admitted_total: u32` — cumulative successful try_send;
  cross-references rejection log (chunk 20/200 = pulse-burst;
  chunk 200/210 = drain-stall)
- `chunks_admitted_at_window_start: u32` (NEW) — snapshot of
  `chunks_admitted_total` at window open; powers the
  `admitted_in_window` discriminator (red-team Finding 1 fix)

**Three log sites** in the Bazel-facing path:
1. Probe-armed `info!` at admit-loop entry in
   `dispatch_chunks_to_driver` — fires once per stream (Finding 3).
2. Burst-detect `warn!` inside `record_attempt` when window count
   exceeds threshold — fires at most once per 100 ms window per blob.
   Message: "producer-arrival burst exceeded threshold — pulse-burst
   signature (TraceMachina#394/TraceMachina#413 Phase 1 probe)" with fields `chunks_in_window`,
   `admitted_in_window` (NEW), `elapsed_window_ms`,
   `mpsc_capacity_remaining`, `chunks_admitted_total`, `offset`,
   `?digest`.
3. Rejection enrichment `warn!` inside the `Err(Full)` arm augments
   the existing per-blob-mpsc-full event with probe state. Message:
   "per-blob mpsc full at producer-arrival probe — pulse-burst vs
   drain-stall correlation (TraceMachina#394/TraceMachina#413 Phase 1 probe)" with fields
   `chunks_in_window`, `admitted_in_window` (NEW), `chunks_admitted_total`,
   `producer_arrival_us_since_admission`, `mpsc_capacity_remaining`,
   `offset`, `?stream_digest`.

**Why `warn!` (not `debug!`):** workspace pins
`tracing = features = ["release_max_level_info"]` (`Cargo.toml:101`)
which compiles out `debug!`/`trace!` in release builds — a `debug!`
probe would be dead code in production. Same rationale as the
existing `back_edge_ms` probe (commit 3d43759).

**Threshold = 50 attempts / 100 ms:** at the inferred 25-50 MB/s
drain ceiling and 1 MiB chunks, steady-state delivers ~3-5 chunks
per 100 ms window; a 50-attempt window means producer is running
≥10x faster than steady-state drain. Audit §8 predicts cascade-bound
blobs cross this threshold; clean blobs stay <20. Tunable as
`BURST_THRESHOLD_CHUNKS_PER_WINDOW`. NOTE: threshold-calibration
follow-up (red-team Finding 2) will run a baseline-mode measurement
before locking this constant.

**Probe scope (Bazel-facing path only):** `dispatch_chunks_to_driver`
passes `Some(&mut probe)` to `admit_prepared_chunk`; the worker-side
`ChunkedWriteHandler::admit_chunk` passes `None`. The cascade
hypothesis targets the chunker → driver back-edge on the Bazel-facing
internal-chunking path; the worker-driven WriteChunked RPC path has
a different producer (worker reads its own slow tier) and is not
the cascade-prone path.

**Tests (asymmetric-contract coverage per CLAUDE.md):**

1. `producer_arrival_probe_warns_on_burst_above_threshold` (UNDER-action):
   - Production composition: calls `admit_prepared_chunk` with
     `Some(&mut probe)` — the same shape `dispatch_chunks_to_driver`
     uses on the cascade-prone Bazel-facing path.
   - Drives 200 admission attempts (was 60; bump per auditor CLAIM 4
     PARTIAL fix — robust against mid-loop window rollover on loaded
     CI).
   - Asserts via `logs_contain` that the bespoke marker
     "pulse-burst signature (TraceMachina#394/TraceMachina#413 Phase 1 probe)" appears.
   - Wrapped in `tokio::time::timeout(5s)` deadlock detector.
   - Mutation verified: setting `if false &&` on the burst-detect gate
     red-fails with bespoke message "Phase 1 probe never warned —
     burst-detect gate stripped or threshold raised".

2. `producer_arrival_probe_quiet_below_threshold` (OVER-action, NEW):
   - Same production composition (`admit_prepared_chunk` with
     `Some(&mut probe)`).
   - Drives 40 admissions — below the 50-attempt threshold.
   - Asserts via `!logs_contain(...)` that the pulse-burst marker
     does NOT appear (healthy streams must not produce spurious
     false-alarm warns).
   - Mutation verified: changing `> BURST_THRESHOLD_CHUNKS_PER_WINDOW`
     to `> 0` (always-fire) red-fails with bespoke message "Phase 1
     probe fired below threshold — the burst-detect arm emitted a
     `warn!` after only 40 admissions".

**No changes to:** `admit_prepared_chunk` body order (validation →
ChunkBudget → PinBudget → try_send), error codes, retry hints,
backpressure signals, gate composition. All 22 tests in
`chunked_write_handler_test.rs` 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
Mechanical sweep demoting audited high-volume log sites. No behavior changes
to data flow, error handling, or metrics.

Behavior changes (mandatory per .claude/rules/architectural-changes.md):

WARN → debug:
  buf_channel.rs:228: warn→debug, "channel backpressure (>1s wait)", 1,373×/12h
  buf_channel.rs:694: warn→debug/warn (debug for <60s gap/wait; warn kept for
    ≥60s gap or ≥60s recv wait — previously fired at >5s), 39,424×/12h
  buf_channel.rs:963: warn→debug, "Receiver dropped mid-stream", 463×/12h
  stall_detector.rs:302: warn→debug (benign SuppressDumpProducerDriven branch
    only; stack-dump branch unchanged), 3,049×/12h
  proto_stream_utils.rs:87: warn→debug, "GrpcStore::write made no progress", 477×/12h
  proto_stream_utils.rs:182: warn→debug, "read path made no progress", 122×/12h
  bytestream_server.rs:616: warn→debug, "slow read completion (>=5s, <1MB/s)", 153×/12h
  bytestream_server.rs:2148: warn→debug, "mirror channel closed, dropping mirror", 433×/12h
  grpc_store.rs:1788: warn→debug, "GrpcStore::write succeeded but took > 60s", 410×/12h
  fast_slow_store.rs:5525: warn→debug, "background slow write task was delayed
    before starting" (streaming update path), 337×/12h (both paths combined)
  fast_slow_store.rs:5874: warn→debug, "update_oneshot: background slow write
    task was delayed before starting"
  fast_slow_store.rs:4913,4939,4963: warn→debug, "slow lock acquire" (3 sites,
    has_with_results local_only_reads: in_flight/chunked/mirror), 65×/12h
  fast_slow_store.rs:4998,5035,5065: warn→debug, "slow lock acquire" (3 sites,
    has_with_results server: in_flight/chunked/mirror)
  filesystem_store.rs:1229: warn→debug, "update_file slow phases (>100ms)", 67×/12h
  filesystem_store.rs:2162: warn→debug, "update slow write (>100ms)", 115×/12h
  cas_server.rs:1011: warn→debug, "GetTree: slow BFS level (>100ms)", 85×/12h
  fast_slow_store.rs:4918-ish: see slow lock acquire above
  streaming_blob.rs:1323: warn→debug, "streaming blob reader slow notify wakeup", 39×/12h
  api_worker_scheduler.rs:3756: warn→debug, "tree resolution slow directory fetch", 69×/12h

WARN → trace:
  fs.rs:571: warn→trace, "read_file_to_channel: slow read syscall (>100ms)", 311×/12h

INFO → debug:
  grpc_store.rs:1445: info→debug, "TraceMachina#59 GrpcStore::write: starting ByteStream write"
  grpc_store.rs:1673: info→debug, "TraceMachina#59 GrpcStore::write: RPC returned Ok (normal success)"
  grpc_store.rs:1777: info→debug, "TraceMachina#59 GrpcStore::write: completed successfully"; combined ~464K/12h
  bytestream_server.rs:660: info→debug, "ByteStream::read: CAS read completed", ~243K/12h
  api_worker_scheduler.rs:1931: info→debug, "scoring: pre-match state", ~207K/12h
  bytestream_server.rs:3092: info→debug, "TraceMachina#62 ByteStream::Write: already-exists short-circuit", ~155K/12h
  chunked_write_handler.rs:1013: info→debug, "WriteChunked RPC entry"
  chunked_write_handler.rs:1317: info→debug, "in_flight handler-local entry inserted (write_chunked_inner)"
  chunked_write_handler.rs:2901: info→debug, "chunked dispatch: blob committed (reaper)"
  chunked_write_handler.rs:3072: info→debug, "in_flight handler-local entry inserted (dispatch_chunks_to_driver)"
  chunked_write_handler.rs:3119: info→debug, "producer-arrival probe armed (TraceMachina#394/TraceMachina#413 Phase 1)"
  chunked_write_handler.rs:3827: info→debug, "chunked_in_flight registered"
  chunked_write_handler.rs:3877: info→debug, "chunked_in_flight guard disarmed"
  chunked_write_handler.rs:3934: info→debug, "chunked_in_flight removed"; combined ~120K/12h
  chunked_write_handler.rs:1517: "WriteChunked: blob committed" KEPT at info (terminal state transition)
  fast_slow_store.rs:1854: info→debug, "FastSlowStore::update (chunked): admission accepted", ~54K/12h
  fast_slow_store.rs:2093: info→debug, "FastSlowStore::update (chunked): commit complete"
  fast_slow_store.rs:1830: info→debug, "data_stream_fut step", ~16K/12h
  existence_cache_store.rs:848: info→debug, "returning NotFound to caller", ~17K/12h
  cas_server.rs:1067: info→debug, "GetTree: resolved directory tree", ~16K/12h
  api_worker_scheduler.rs:2384: info→debug, "inline tree resolution complete, caching"
  api_worker_scheduler.rs:2396: info→debug, "tree cache byte-bounded eviction"
  api_worker_scheduler.rs:2643: info→debug, "prefetch: spawning batched push"
  api_worker_scheduler.rs:2827: info→debug, "prefetch: completed batched push"; combined ~51K/12h
  api_worker_scheduler.rs:1263: info→debug, "phase6 scheduler dispatching StartAction"
  worker_api_server.rs:1493: info→debug, "phase6 scheduler received action-N completion"

NOT touched (kept at warn per spec):
  bytestream_server.rs:841 "TraceMachina#418: discarding corrupt StreamState" — KEEP warn
  proto_stream_utils.rs aggregate warn (rate-limited burst warn) — KEEP warn
  chunked per-chunk back-edge lines (TraceMachina#47 active) — KEEP warn
  redis_store slow-pipeline/slow-command warns — KEEP warn

No tests required for pure level changes.
cargo check -p nativelink-util -p nativelink-store -p nativelink-service -p nativelink-scheduler — clean (exit 0). Log: /tmp/logsweep-check1.log.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
rejuvenile added a commit to rejuvenile/nativelink that referenced this pull request Aug 19, 2026
… (observability-only)

The ~9 stuck large CAS blobs (ead6c5ce-62303904, 647ad175-44673680,
a79ccb25-59958352, …) loop forever on the Bazel-facing internal-chunking
backfill, failing in ~100–660 ms with `"buf_channel: writer dropped without
commit : bazel-facing internal-chunking: reader.recv()"`. The server logged
NOTHING per-digest for them — the reject returns the Status before the
per-digest log path runs — so the root branch was DARK while 38,544 other
blobs committed. Per CLAUDE.md "instrument first, theorize second" (three
prior root-cause hypotheses already FALSIFIED this session), this ships the
probe instead of a 4th guess.

WHAT (three probe sites, all observability-only):
- (a) `dispatch_bazel_facing_internal_chunking` early-dedup bounded-drain
  failure (`chunked_write_handler.rs`): a reject that fires BEFORE
  `dispatch_chunks_to_driver` arms the TraceMachina#394/TraceMachina#413 producer-arrival probe.
  Bumps `chunked_reject_pre_probe` + rate-limited `warn!` tagging digest +
  branch.
- (b) `build_bazel_chunk_stream` FIRST `reader.recv()` Err
  (`chunked_write_handler.rs`) — the EXACT stuck-loop signature. Bumps
  `chunked_reject_at_offset0` (producer sent zero chunks; `bytes_received ==
  0`) or `chunked_reject_midstream` (dropped after ≥1 chunk) + rate-limited
  `warn!` tagging digest + `bytes_received`/`chunks_received` + branch. The
  offset0:midstream ratio discriminates "worker sends nothing" from "worker
  drops partway".
- (c) worker `WorkerApiWriteChunkedDispatcher::dispatch`
  (`chunked_client.rs:201`): tags the failing attempt `reject_origin =
  channel_acquire` (worker-side; RPC never reached the server / stuck client
  state) vs `rpc_returned_error` (RPC reached the transport, error came back).
  This is the worker-side half of the open fork sites (a)/(b) answer.

RATE LIMIT: every `warn!` is gated by the EXACT idiom shipped for the QUIC
read-failure diagnostic in `ccab2243` (`quic_read_fail_should_emit`): a pure
monotonic-nanos predicate over an `AtomicU64` last-emit, single-flighted by a
CAS, carrying `suppressed_since_last`. ≲1/s per site, so the ~30 s loop cannot
reproduce the 46k-line spam class the `dafcf8b6`/`cbc2e595` demotions retired.
`warn!` (not `debug!`/`trace!`) survives `release_max_level_info` in the prod
binary. Each site owns its own gate so a burst at one does not silence another.

COUNTERS: `chunked_reject_pre_probe`, `chunked_reject_at_offset0`,
`chunked_reject_midstream` — additive `#[metric] AtomicU64` on the existing
`ChunkedWriteHandlerMetrics` derive (`// CAPPED AT 1` each). Their ratio IS the
discriminator.

Invariant being violated: every reproducible CAS-write failure must be
attributable to a specific code branch from logs alone.
Mechanism that violates it: the reject path
(`build_bazel_chunk_stream` first-recv Err; early-dedup bounded-drain Err)
returns/yields the Error without a digest-tagged log, so the stuck loop is
un-diagnosable per-digest.
Mechanism that re-establishes it after the fix: the three probes tag digest +
branch at warn! level (release-surviving), rate-limited so they cannot
themselves become a log-volume incident.
Composite invariants this fix interacts with: NONE behaviorally — no
pin/eviction/ack/admission/retry/routing touch; the diagnostic is purely
additive observation called on the existing Err paths. The rate-limit gate
prevents a log-volume regression (the only way an observability change can
harm prod).
Test that proves the fix re-establishes the invariant:
`chunked_reject_diagnostic_counts_offset0_drop_and_logs_digest` — drives
`dispatch_bazel_facing_internal_chunking` (production composition) with the
producer dropped at offset 0 (tx dropped without bytes/EOF → the exact
`writer dropped without commit : reader.recv()` signature), asserts
`chunked_reject_at_offset0 == 1`, `chunked_reject_midstream == 0`, and that the
captured `warn!` carries the digest. Mutation: commenting the
`chunked_reject_at_offset0.fetch_add` red-fails with "offset-0 reject counter
MUST increment …" (left 0, right 1); stripping `%digest` from the `warn!`
red-fails the warn-carries-digest assertion.

Behavior changes: none. Every runtime path returns/yields the SAME Result it
did before; the only added runtime effects are (1) three monotonically-
increasing counters and (2) the rate-limited warn!s, both inert until a
chunked write actually rejects. Verified against production composition:
- Bazel-facing internal chunking (`FastSlowStore::update` → BazelChunked-
  DispatcherImpl → dispatch_bazel_facing_internal_chunking): the failing
  path. site (a) tags early-dedup drain rejects; site (b) tags first-recv
  rejects. `build_bazel_chunk_stream` now threads the `metrics` Arc through
  its `unfold` `State` (no per-chunk Arc clone on the hot path) — the chunk
  stream it yields is byte-identical to before.
- worker v1 WriteChunked dispatch (GrpcStore → WorkerApiWriteChunkedDispatcher
  ::dispatch): the `factory().await?` and `map_err` legs propagate the SAME
  errors; site (c) only tags + logs before propagation. The v2 dispatcher
  (`WorkerApiWriteChunkedV2Dispatcher`) is untouched.
- early-dedup hit, stalled-producer, oversized-producer, digest-mismatch, and
  all 16 pre-existing bazel_facing tests pass unchanged.

Internal-RPC policy: no per-RPC timeout/deadline added; no buffering field
added beyond single `AtomicU64`s (each `// CAPPED AT 1`). No fsync/sync-write
primitive.

Bench: exempt — observability-only on a data-plane file; per
.claude/rules/data-plane-bench.md the bench is exempt for trace-only changes
with no production-code behavior delta.

Tests (timeout-wrapped, teed to /tmp/chunkdiag-*.log):
- bazel_facing_internal_chunking_test: 17 passed (16 prior + 1 new).
- chunked::chunked_client unit tests: 14 passed (13 prior + 1 new gate test).
- writer_site_logs_test: 5 passed (handler-touch regression).
- mutation-verified: offset-0 counter bump (bespoke red); warn! digest strip
  (bespoke red); chunked_client gate `last_emit == 0` guard flip (bespoke red).

DESIGN-DRIFT FLAGGED (does not block the diagnostic): the spec premise "the
server logs nothing per-digest, not even `producer-arrival probe armed`" is
inconsistent with the code path. The failing error string originates ONLY at
`build_bazel_chunk_stream` first-recv, which is reached via
`dispatch_chunks_to_driver`'s admit loop — AFTER that fn arms the
producer-arrival `info!` (`chunked_write_handler.rs:~3119`). So a digest that
reaches the `reader.recv()` reject MUST already have emitted the armed-probe
info!. Either that info! is being lost for another reason (target filtering,
or the reject races the emit) or the operator scan missed it. The diagnostic
is placed correctly regardless; site (b) is the authoritative per-digest tag
for this failure. Surfaced for the parent to reconcile against the live logs.

Co-Authored-By: Claude Opus 4.8 <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.

2 participants