Wait for pending asynchronous tree deletions during shutdown. Ensure AsynchronousTreeDeleter.shutdown() waits for all pending async tree deletions to finish using awaitTermination. This matches the documented behavior in SandboxOptions and prevents incomplete trash deletions when the server shuts down. Part of https://github.com/bazelbuild/bazel/issues/29564 PiperOrigin-RevId: 970524629 Change-Id: I9dc7cbd95d31e07a7aa7beb54ad5098a8551e46e
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/AsynchronousTreeDeleter.java b/src/main/java/com/google/devtools/build/lib/sandbox/AsynchronousTreeDeleter.java index 63d3d6c..c8245f5 100644 --- a/src/main/java/com/google/devtools/build/lib/sandbox/AsynchronousTreeDeleter.java +++ b/src/main/java/com/google/devtools/build/lib/sandbox/AsynchronousTreeDeleter.java
@@ -123,6 +123,13 @@ if (service != null) { logger.atInfo().log("Finishing %d pending async tree deletions", service.getTaskCount()); service.shutdown(); + try { + service.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); + } catch (InterruptedException e) { + logger.atWarning().withCause(e).log( + "Interrupted while waiting for async tree deletions to finish"); + Thread.currentThread().interrupt(); + } service = null; } }
diff --git a/src/test/java/com/google/devtools/build/lib/sandbox/SandboxHelpersTest.java b/src/test/java/com/google/devtools/build/lib/sandbox/SandboxHelpersTest.java index 8eeaa50..08769ae 100644 --- a/src/test/java/com/google/devtools/build/lib/sandbox/SandboxHelpersTest.java +++ b/src/test/java/com/google/devtools/build/lib/sandbox/SandboxHelpersTest.java
@@ -53,11 +53,14 @@ import java.util.LinkedHashSet; import java.util.Set; import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; import java.util.function.Function; import javax.annotation.Nullable; import org.junit.After; @@ -466,4 +469,44 @@ .containsExactly("hello", "pathmapper") .inOrder(); } + + @Test + public void asynchronousTreeDeleter_shutdown_waitsForPendingDeletions() throws Exception { + CountDownLatch deletionStarted = new CountDownLatch(1); + CountDownLatch allowDeletionToComplete = new CountDownLatch(1); + + FileSystem customFs = + new InMemoryFileSystem(DigestHashFunction.SHA256) { + @Override + public boolean delete(PathFragment path) throws IOException { + deletionStarted.countDown(); + try { + allowDeletionToComplete.await(); + } catch (InterruptedException e) { + throw new IOException(e); + } + return super.delete(path); + } + }; + + Scratch customScratch = new Scratch(customFs); + Path trashBase = customScratch.dir("/trash"); + Path dir = customScratch.dir("/dir"); + customScratch.file("/dir/file.txt"); + + AsynchronousTreeDeleter deleter = new AsynchronousTreeDeleter(trashBase); + deleter.deleteTree(dir); + + // Wait until background thread starts deleting + deletionStarted.await(); + + ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + executorToCleanup = executor; + var unused = executor.schedule(allowDeletionToComplete::countDown, 100, TimeUnit.MILLISECONDS); + + deleter.shutdown(); + + assertThat(trashBase.getDirectoryEntries()).isEmpty(); + } } +