Skip to content

Record materialized top-level outputs in completion values - #30877

Draft
fmeum wants to merge 6 commits into
bazelbuild:masterfrom
fmeum:toplevel-download-skyfunction
Draft

Record materialized top-level outputs in completion values#30877
fmeum wants to merge 6 commits into
bazelbuild:masterfrom
fmeum:toplevel-download-skyfunction

Conversation

@fmeum

@fmeum fmeum commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Description

This PR implements the consolidated design from @coeuvre's design document, replacing the dedicated download SkyFunction from earlier revisions of this PR: the materialization state of top-level outputs is recorded directly in the existing completion values instead of a child node, adding no Skyframe nodes or edges.

The commits are meant to be reviewed individually:

  1. The invocation's download policy (mode, --remote_download_regex patterns, command) is injected as a precomputed value that completion functions depend on, so a policy change reevaluates them. This is a correction to the design document, which doesn't specify how completions are invalidated on policy changes after maybeInvalidateSkyframeValues is deleted; without it, e.g. a minimal build followed by a toplevel build of the same target would never download the top-level outputs. The policy must not be gated on Skymeld (non-Skymeld invocations also need policy-change invalidation, and SkymeldModule.beforeCommand runs after RemoteModule's).
  2. TargetCompletionValue and AspectCompletionValue, previously empty singletons, record the outputs that are present in the local filesystem while their metadata tracked in Skyframe remains remote, together with their contents proxy. Invocations that materialize nothing keep sharing the empty singleton.
  3. FilesystemValueChecker compares each completion value's record against the local filesystem and invalidates the completion node if a file is missing or modified; its reevaluation rematerializes the file if the policy wants it locally, without reexecuting or even revalidating the generating action. The completion functions become non-hermetic to permit this invalidation, matching action execution nodes. Lost-output rewinding stays in CompletionFunction, unchanged.
  4. The previous-invocation state in RemoteOutputChecker (the lastRemoteOutputChecker chain and maybeInvalidateSkyframeValues) is deleted. The current invocation's policy check in shouldTrustMetadata stays for the non-Skymeld path.
  5. The incremental build tests from Check previous build's toplevel outputs in shouldTrustMetadata #30852 are carried over; they fail on master and pass here. Fixes [7.6.1] Incremental build without any changes with BwoB within rules_xcodeproj is causing output artifacts to be redownloaded each time #26924.

The base commit is #30871, whose contents-proxy recording keeps the completion values' record precise for files that were verified by digest rather than freshly downloaded.

Two caveats relative to the design document, to be addressed separately: completion values are only produced on success, so partially materialized outputs of failed targets are still not tracked (the failed event path keeps its inline important output handler call); and the FSVC-notification-to-prefetcher-submission mechanism for --remote_download_regex-matched intermediate outputs (the document's second pillar) is left to a follow-up, including a story for lost remote blobs on that path.

Motivation

With Build without the Bytes, the completion functions download top-level outputs that only exist as remote metadata as an unmodeled side effect of their evaluation, and correctness across invocations was maintained by point mechanisms whose gaps surfaced as bugs: a top-level output deleted locally was silently not restored if any intervening invocation didn't request it (the new downloadToplevel_outputDeletedAfterUnrelatedBuild_toplevelOutputIsRestored test fails on master), and an invocation with a broader download policy caused the next incremental build to reevaluate and reexecute actions wholesale (#26924).

Full test runs: BuildWithoutTheBytesIntegrationTest (Java and shell), RemoteTests, FilesystemValueCheckerTest, RewindingTest, SkymeldBuildIntegrationTest, TargetCompleteEventTest.

Build API Changes

No

Checklist

  • I have added tests for the new use cases (if any).
  • I have updated the documentation (if applicable).

Release Notes

RELNOTES: With Build without the Bytes, locally materialized top-level outputs are now tracked in Skyframe. A requested top-level output that was deleted locally is reliably redownloaded in the next build that requests it, and changes to the download policy no longer cause unnecessary reevaluation or reexecution of actions.

When the prefetcher finds a file already present in the local filesystem and its metadata carries no (matching) contents proxy, it verifies the file by digest before skipping the download, but previously left the metadata unchanged, so every subsequent modification check had to digest the file again. Record the verified file's contents proxy just like after a fresh download so that future checks only require a stat.
@fmeum
fmeum force-pushed the toplevel-download-skyfunction branch from 844a9e9 to 939a367 Compare August 26, 2026 17:44
@coeuvre

coeuvre commented Aug 27, 2026

Copy link
Copy Markdown
Member

I think solving the problem in skyframe is the right direction. I fed Gemini with the problem space and the PR, had some discussion and came up with another idea: artifact_level_materialization_design_doc.md.

@fmeum

fmeum commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks, I'll spend more time thinking through this analysis. My main concern is that one of the premises is wrong: Folks regularly do bazel test //... outside Google, which would add one SkyKey/SkyValue per output for all targets in the graph. I don't think that this is feasible without drastically increased memory usage.

We may need to revisit what top-level mode means for BwoB before we attempt this.

@coeuvre

coeuvre commented Aug 27, 2026

Copy link
Copy Markdown
Member

Yes, we don't want to increase the memory usage.

@coeuvre

coeuvre commented Aug 27, 2026

Copy link
Copy Markdown
Member

Asked Gemini to address the memory concern: consolidated_skyframe_materialization.md

"Instead of creating a fine-grained ArtifactMaterializationKey for every artifact—which would explode Skyframe memory on bazel test //...—the new design embeds top-level materialization directly into existing completion values with strictly zero new Skyframe nodes, handling intermediate regex downloads via FSVC-to-prefetcher notifications."

fmeum added 5 commits August 27, 2026 14:03
With Build without the Bytes, the completion functions download top-level outputs that only exist as remote metadata. Which outputs they materialize depends on the invocation's download policy (download mode, --remote_download_regex patterns, and command), so completion values are only valid for invocations with the same policy.

This change makes that dependency explicit: the policy is injected as a precomputed value on each sync and completion functions depend on it, so that a policy change (e.g. a different download mode or a switch between build and run) reevaluates them. This prepares for replacing RemoteOutputChecker#maybeInvalidateSkyframeValues, which deletes all completion nodes on download mode or command changes and misses --remote_download_regex changes entirely.

The policy is set whenever a remote or disk cache is in use. It is deliberately not gated on Skymeld even though completion functions only download outputs when the Skymeld-only important output handler is registered: policy-change invalidation must also cover non-Skymeld invocations, and the policy must not depend on state that other modules' beforeCommand implementations have not yet initialized (SkymeldModule runs after RemoteModule).
With Build without the Bytes, the completion functions download top-level outputs of actions with an action cache hit as an unmodeled side effect of their evaluation: nothing in Skyframe records which files were materialized this way, so nothing can be invalidated when their local filesystem state diverges.

This change records the materialization in the completion values themselves: TargetCompletionValue and AspectCompletionValue, previously empty singletons, now carry the output files that are present in the local filesystem while the metadata tracked for them in Skyframe remains remote, together with the contents proxy they were last observed with (recorded by the prefetcher when it materializes or verifies a file). Since the values already exist for every top-level target and aspect, this adds no Skyframe nodes or edges, and invocations that materialize nothing keep sharing the empty singleton.
FilesystemValueChecker now compares the outputs recorded as locally materialized by each completion value against the local filesystem and invalidates the completion node if a file is missing or modified. The next invocation that requests the corresponding top-level target reevaluates the completion function, which rematerializes the file if the invocation's download policy wants it locally - without reexecuting or even revalidating the generating action, whose outputs didn't change. The completion functions become non-hermetic to permit this invalidation, matching action execution nodes.

This fixes a hole in incremental builds: a top-level output that was materialized by the completion function (rather than by its generating action, which had an action cache hit) is tracked in Skyframe with remote metadata, so its local deletion was only detected by RemoteOutputChecker consulting the immediately preceding invocation's state. Any intervening invocation that didn't request the output discarded that record, after which an incremental build reported success without restoring the deleted top-level output. The new integration tests cover the deletion of such an output after an unrelated build (restored), the deletion of an output that later invocations no longer want locally (not restored, and nothing is reevaluated), and the deletion of outputs downloaded during action execution by a --remote_download_outputs=all build (restored via the ordinary modified output check, which is unaffected by this change).
With materialized top-level outputs recorded in completion values and invalidated through the modified output check, the mechanisms that approximated cross-invocation materialization state are no longer needed:

- shouldTrustMetadata no longer consults the previous invocation's checker: a top-level output materialized without its generating action being reexecuted is now restored by invalidating the completion value that recorded it instead of the generating action, no matter how many invocations happened in between.
- maybeInvalidateSkyframeValues is deleted: a change to the download mode, --remote_download_regex or command changes the download policy, which is injected as a precomputed value and thus invalidates all completion functions.

The current invocation's policy check in shouldTrustMetadata stays: without Skymeld, where the important output handler is not registered, top-level outputs are still restored by invalidating their generating action based on the download policy, which is fully known by the time the modified output check runs after the analysis phase.
The four tests document that an invocation using a broader download policy (including one that doesn't build anything) doesn't cause the next invocation to reevaluate or reexecute actions whose outputs it doesn't want locally, while still materializing the outputs it does want. They fail on master and pass with materialized outputs recorded in completion values, which no longer consult the previous invocation's download policy at all.

Fixes bazelbuild#26924.
@fmeum
fmeum force-pushed the toplevel-download-skyfunction branch from 939a367 to a3b746e Compare August 27, 2026 12:14
@fmeum fmeum changed the title Download top-level outputs through a dedicated Skyframe function Record materialized top-level outputs in completion values Aug 27, 2026
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.

[7.6.1] Incremental build without any changes with BwoB within rules_xcodeproj is causing output artifacts to be redownloaded each time

2 participants