Skip to content

execution_server: pre-validate CAS blobs and return PreconditionFailure - #2322

Merged
amankrx merged 4 commits into
TraceMachina:mainfrom
amankrx:pre-validate-cas-blobs
May 12, 2026
Merged

amankrx merged 4 commits into
TraceMachina:mainfrom
amankrx:pre-validate-cas-blobs

Conversation

@amankrx

@amankrx amankrx commented May 11, 2026 •

Copy link
Copy Markdown
Member

Description

Inline definition of google.rpc.PreconditionFailure. Bazel's RemoteSpawnRunner inspects this proto in the gRPC Status.details of a FAILED_PRECONDITION response and, when violations of type MISSING are present, automatically re-uploads the named blobs and retries the Execute call. Without this detail Bazel surfaces the failure as a hard build error — exactly the symptom triggered by an interrupted previous build leaving partial CAS uploads.

Fixes # (issue)

Type of change

Please delete options that aren't relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to
    not work as expected)
  • This change requires a documentation update

How Has This Been Tested?

Please also list any relevant details for your test configuration

Checklist

  • Updated documentation if needed
  • Tests added/amended
  • bazel test //... passes locally
  • PR is contained in a single commit, using git amend see some docs

This change is Reviewable

@amankrx
amankrx force-pushed the pre-validate-cas-blobs branch from 9eb2114 to e74133a Compare May 11, 2026 17:34

@MarcusSorealheis MarcusSorealheis left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please do not do use string-parsing errors in action_messages.rs.**

extract_missing_blob_digest is fragile in a way that will silently rot. You're grepping for "not found in either fast or slow store" and then for "Object " … " not found", all of which are formatting choices owned by a different crate. Nothing prevents someone from rewording that message. There's no compile-time link, no test in the producing crate that pins the format. The day someone tweaks the fast-slow store's error string to be more helpful, the FAILED_PRECONDITION path silently degrades to a plain NotFound, Bazel stops auto-retrying, and you'll find out from a customer.

The right fix is structured, not textual. Attach the missing digest as typed metadata on the Error (an enum variant, an explicit field, or even a sentinel Code) at the point it's generated, and have to_execute_response match on that. The pre-check in execution_server.rs already does this properly. It knows the digest because it asked. The asymmetry between the two paths is the tell that the worker path took the shortcut. I'd block on this; the rest of the patch is solid enough that this one piece sticks out.

If you're not going to fix it now, at least export the marker as a pub const from wherever it's produced, and have that crate's tests assert the format. Don't let two unrelated files agree on a string by accident.

PreconditionFailure is defined four times. Once in execution_server.rs, once in action_messages.rs, and once in each of the two test files. The "avoid pulling tonic-types" justification is thin.tonic-types is small, and even if you don't want it, one pub(crate) definition in nativelink-util beats four. The current state means future schema changes (unlikely, but) require four edits and four chances to drift. Pick one home.

The nested Result<Result<Stream, Status>, Error> in inner_execute is hard to read. Two error channels in one return type isn't intuitive. At the call site you immediately unwrap the outer with ? and then match the inner. Either a small enum ExecuteOutcome { Stream(_), Reject(Status) } or a helper that returns Result<Stream, Status> and lifts internal errors to Status::internal would read better. Minor, but the file already has enough going on.

The Redis fix is the right call, but the match is awkward. This is cleaner:

let create_result = result.or_else(|e| {
    if e.kind() == redis::ErrorKind::Extension
        && e.code() == Some("Index")
        && e.detail() == Some("already exists")
    {
        Ok(())
    } else {
        Err(e).err_tip(|| format!("Error with ft_create in …"))
    }
});

Also, how confident are you that code()/detail() parsing is stable across RediSearch versions? "Index already exists" is the current text but I'd grep the RediSearch source to confirm capitalization hasn't drifted between 2.x branches. A case-insensitive contains("already exists") is uglier but probably safer.

The pre-check is racy with eviction between has_many and worker fetch — fine, the worker path covers it, but say so in the comment so the next reader doesn't re-litigate it. prost_types::Any is used in execution_server.rs and bare Any in action_messages.rs; pick one. The pre-check is intentionally shallow (top-level only, no nested Directory walk). The comment in action_messages.rs mentions this but execution_server.rs should too, since that's where someone looking to extend the check will land first.

The comments explaining why (the RemoteSpawnRunner behavior, the REv2 subject format) are the right level of detail and will save the next person a lot of time. So nice work there.

All in all, solid PR but just need to fix a few joints before we can merge er.

@amankrx
amankrx merged commit 80ac19c into TraceMachina:main May 12, 2026
41 of 42 checks passed
rejuvenile pushed a commit to rejuvenile/nativelink that referenced this pull request Aug 19, 2026
…re (TraceMachina#2322)

* execution_server: pre-validate CAS blobs and return PreconditionFailure

* Add Error Type to handle Not Found Context
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