Skip to content

Fix concurrent TreeArtifact prefetches#29803

Open
tamird wants to merge 2 commits into
bazelbuild:masterfrom
tamird:tamird/fix-concurrent-tree-prefetches
Open

Fix concurrent TreeArtifact prefetches#29803
tamird wants to merge 2 commits into
bazelbuild:masterfrom
tamird:tamird/fix-concurrent-tree-prefetches

Conversation

@tamird

@tamird tamird commented Jun 10, 2026

Copy link
Copy Markdown

Remote prefetches download into temporary files, but finalization and
output-permission restoration can race across calls targeting the same
TreeArtifact. One call can make the tree read-only while another is
moving a child into it.

Coordinate these operations by resolved TreeArtifact root. Allow child
finalizations to proceed concurrently, but restore permissions only
after all in-progress finalizations have completed. Weakly retain the
per-root locks so a long-lived prefetcher does not retain every tree it
touches.

Reopen every ancestor before moving a child because
ActionOutputDirectoryHelper caches directory creation after output
permissions are restored.

Fixes #29801.

Remote prefetches download into temporary files, but finalization and
output-permission restoration can race across calls targeting the same
TreeArtifact. One call can make the tree read-only while another is
moving a child into it.

Serialize finalization and permission restoration by resolved
TreeArtifact root. Reopen every ancestor explicitly because
ActionOutputDirectoryHelper may cache directory creation after output
permissions are restored.

Fixes bazelbuild#29801.

This change was implemented with OpenAI Codex (GPT-5.5) and reviewed by
me.
@tamird tamird requested a review from a team as a code owner June 10, 2026 12:05
@tamird

tamird commented Jun 10, 2026

Copy link
Copy Markdown
Author

@dzbarsky @fmeum, this is the proposed fix for #29801.

@github-actions github-actions Bot added team-Remote-Exec Issues and PRs for the Execution (Remote) team awaiting-review PR is awaiting review from an assigned reviewer labels Jun 10, 2026
}

@Test
public void prefetchFiles_treeFiles_concurrentFinalizations_doNotRacePermissions()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could you also add a stress test that launches many concurrent operations, some of which overlap? That would give us additional confidence in the new synchronization scheme.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Added in 8c5acce. The stress test starts 128 requests with 32 concurrent callers across four TreeArtifacts. Adjacent requests overlap one child, and the request pattern repeats to cover partially overlapping and fully deduplicated downloads.


// TreeArtifact downloads use temporary paths. Serialize only permission changes and final file
// moves, keyed by resolved tree root, so unrelated trees can still finalize concurrently.
private final Striped<Lock> treeArtifactLocks = Striped.lock(64);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is 64 a good choice here? We could also use weakly referenced locks that scale as high as needed.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done in 8c5acce. This now uses Striped.lazyWeakReadWriteLock(Integer.MAX_VALUE), so unused locks can be collected and distinct roots only contend on a hash collision rather than a fixed 64-stripe pool.

// read-only while another call moves a child into the tree.
for (var entry : directoriesByTreeRoot.asMap().entrySet()) {
Lock lock = treeArtifactLocks.get(entry.getKey().asFragment());
lock.lock();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Would it make sense to make this a rw lock? Moves could happen concurrently (reads), only permission changes must be exclusive (write).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done in 8c5acce. Finalization holds a read lock from reopening ancestors through the move, while call-scoped permission restoration takes the write lock. Widening permissions is safe under read locks because it is monotonic during finalization and DirectoryTracker.compute serializes each directory transition. The deterministic test also verifies that one move completes while another is paused, but restoration cannot finish.

@fmeum fmeum requested a review from tjgq June 10, 2026 12:50
Allow child downloads beneath the same TreeArtifact to finalize
concurrently while retaining exclusive permission restoration. Use
weakly referenced locks across the full hash space to avoid fixed-stripe
contention and unbounded lock retention.

Add stress coverage for overlapping prefetch calls across multiple
TreeArtifacts.

This change was implemented with OpenAI Codex (GPT-5.5) and reviewed by
me.

@tamird tamird left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed all three comments in 8c5acce.

}

@Test
public void prefetchFiles_treeFiles_manyConcurrentOverlappingCalls() throws Exception {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Does this test always fail without the fix or is it just flaky?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

With only the production file restored to d9f0fb4, this stress test failed 20/20 locally, each time because a tree remained writable. It is still schedule-dependent in principle: the latch starts callers together but does not force the failing interleaving, so unlike the two targeted tests I would not call it guaranteed.

}

@Test
public void prefetchFiles_treeFiles_sequentialCalls_reopenAncestors() throws Exception {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Against d9f0fb4 with only the production file restored, the two targeted tests fail deterministically: prefetchFiles_treeFiles_sequentialCalls_reopenAncestors leaves the tree writable after the second prefetch, while prefetchFiles_treeFiles_concurrentFinalizations_doNotRacePermissions lets the first prefetch complete while the second move is paused. With the production fix restored, the full RemoteActionInputFetcherTest class passes.

}

@Test
public void prefetchFiles_treeFiles_manyConcurrentOverlappingCalls() throws Exception {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

With only the production file restored to d9f0fb4, this stress test failed 20/20 locally, each time because a tree remained writable. It is still schedule-dependent in principle: the latch starts callers together but does not force the failing interleaving, so unlike the two targeted tests I would not call it guaranteed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting-review PR is awaiting review from an assigned reviewer team-Remote-Exec Issues and PRs for the Execution (Remote) team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Lazy TreeArtifact materialization can fail with permission denied

2 participants