[9.4.0] Don't release the repo contents cache lock that wasn't acquired (#31518) (#31674) ### Description `BazelRepositoryModule.afterCommand` only releases the shared lock of the local repo contents cache if the process holds it (`LocalRepoContentsCache#holdsSharedLock`), rather than whenever the cache is enabled. ### Motivation If `beforeCommand` rejected the cache path, e.g. because the output base lies inside it, it failed before acquiring the lock, and the release in `afterCommand` then crashed the server with an internal error (exit code 37) instead of reporting the rejection. Reported in https://github.com/bazelbuild/bazel/pull/31452#issuecomment-5971506616. Work towards #31456 ### Build API Changes No ### Release Notes RELNOTES: None Closes #31518 COPYBARA_INTEGRATE_REVIEW=https://github.com/bazelbuild/bazel/pull/31518 from fmeum:repo-contents-cache-lock-release 930b47a41d1cd32e7dee6c99ebd3db48c2bf335a PiperOrigin-RevId: 995832431 Change-Id: I9807199432d7d243b6e6bb0affda025ccec47f1c Commit https://github.com/bazelbuild/bazel/commit/b3dbbc117475f91cc4dbb255a72aa8f648cc02d8 Co-authored-by: Fabian Meumertzheim <fabian@meumertzhe.im> Co-authored-by: Ian (Hee) Cha <heec@google.com>
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java b/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java index 6cc5240..8835aa2 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java
@@ -791,7 +791,8 @@ @Override public void afterCommand() throws AbruptExitException { - if (repositoryCache.getRepoContentsCache().isEnabled()) { + // The lock isn't held if beforeCommand rejected the repo contents cache path. + if (repositoryCache.getRepoContentsCache().holdsSharedLock()) { try { repositoryCache.getRepoContentsCache().releaseSharedLock(); } catch (IOException e) {
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/cache/LocalRepoContentsCache.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/cache/LocalRepoContentsCache.java index 1df4c5d..a446285 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/repository/cache/LocalRepoContentsCache.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/cache/LocalRepoContentsCache.java
@@ -190,6 +190,10 @@ sharedLock = FileSystemLock.get(path.getRelative(LOCK_PATH), LockMode.SHARED); } + public boolean holdsSharedLock() { + return sharedLock != null; + } + public void releaseSharedLock() throws IOException { Preconditions.checkState(sharedLock != null); sharedLock.close();
diff --git a/src/test/py/bazel/bzlmod/repo_contents_cache_test.py b/src/test/py/bazel/bzlmod/repo_contents_cache_test.py index 204f676..f282117 100644 --- a/src/test/py/bazel/bzlmod/repo_contents_cache_test.py +++ b/src/test/py/bazel/bzlmod/repo_contents_cache_test.py
@@ -101,6 +101,20 @@ except OSError: pass # Not a symlink means not cached, which is expected + def testOutputBaseInsideRepoContentsCache_rejected(self): + _, stdout, _ = self.RunBazel(['info', 'output_base']) + output_base = stdout[0].strip() + + # The command fails with an error rather than a crash, and the server stays + # usable. + exit_code, _, stderr = self.RunBazel( + ['info', '--repo_contents_cache=' + output_base], allow_failure=True + ) + self.assertNotEqual(exit_code, 0) + self.assertNotEqual(exit_code, 37) + self.assertIn('is inside the repo contents cache', '\n'.join(stderr)) + self.RunBazel(['info']) + def testCachedAfterCleanExpunge(self): self.ScratchFile( 'MODULE.bazel',