| // Copyright 2019 The Bazel Authors. All rights reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| package com.google.devtools.build.lib.sandbox; | |
| import static com.google.common.base.Preconditions.checkNotNull; | |
| import static com.google.common.base.Preconditions.checkState; | |
| import com.google.common.flogger.GoogleLogger; | |
| import com.google.common.util.concurrent.ThreadFactoryBuilder; | |
| import com.google.devtools.build.lib.exec.TreeDeleter; | |
| import com.google.devtools.build.lib.vfs.Path; | |
| import java.io.IOException; | |
| import java.util.concurrent.LinkedBlockingQueue; | |
| import java.util.concurrent.ThreadFactory; | |
| import java.util.concurrent.ThreadPoolExecutor; | |
| import java.util.concurrent.TimeUnit; | |
| import javax.annotation.Nullable; | |
| /** | |
| * Executes file system tree deletions asynchronously. | |
| * | |
| * <p>The number of threads used to process the backlog of tree deletions can be configured at any | |
| * time via {@link #setThreads(int)}. While a build is running, this number should be low to not use | |
| * precious resources that could otherwise be used for the build itself. But when the build is | |
| * finished, this number should be raised to quickly go through any pending deletions. | |
| */ | |
| class AsynchronousTreeDeleter implements TreeDeleter { | |
| private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); | |
| /** Thread pool used to execute asynchronous tree deletions; null in synchronous mode. */ | |
| @Nullable private ThreadPoolExecutor service; | |
| /** Constructs a new asynchronous tree deleter backed by just one thread. */ | |
| AsynchronousTreeDeleter() { | |
| logger.atInfo().log("Starting async tree deletion pool with 1 thread"); | |
| ThreadFactory threadFactory = | |
| new ThreadFactoryBuilder() | |
| .setNameFormat("tree-deleter") | |
| .setDaemon(true) | |
| .setPriority(Thread.MIN_PRIORITY) | |
| .build(); | |
| service = | |
| new ThreadPoolExecutor( | |
| 1, 1, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), threadFactory); | |
| } | |
| /** | |
| * Resizes the thread pool to the given number of threads. | |
| * | |
| * <p>If the pool of active threads is larger than the requested number of threads, the resize | |
| * will progressively happen as those active threads become inactive. If the requested size is | |
| * zero, this will wait for all pending deletions to complete. | |
| * | |
| * @param threads desired number of threads, or 0 to go back to synchronous deletion | |
| */ | |
| void setThreads(int threads) { | |
| checkState(threads > 0, "Use SynchronousTreeDeleter if no async behavior is desired"); | |
| logger.atInfo().log("Resizing async tree deletion pool to %d threads", threads); | |
| checkNotNull(service, "Cannot call setThreads after shutdown").setMaximumPoolSize(threads); | |
| } | |
| @Override | |
| public void deleteTree(Path path) { | |
| checkNotNull(service, "Cannot call deleteTree after shutdown") | |
| .execute( | |
| () -> { | |
| try { | |
| path.deleteTree(); | |
| } catch (IOException e) { | |
| logger.atWarning().withCause(e).log( | |
| "Failed to delete tree %s asynchronously", path); | |
| } | |
| }); | |
| } | |
| @Override | |
| public void deleteTreesBelow(Path path) { | |
| checkNotNull(service, "Cannot call deleteTree after shutdown") | |
| .execute( | |
| () -> { | |
| try { | |
| path.deleteTreesBelow(); | |
| } catch (IOException e) { | |
| logger.atWarning().withCause(e).log( | |
| "Failed to delete contents of %s asynchronously", path); | |
| } | |
| }); | |
| } | |
| @Override | |
| public void shutdown() { | |
| if (service != null) { | |
| logger.atInfo().log("Finishing %d pending async tree deletions", service.getTaskCount()); | |
| service.shutdown(); | |
| service = null; | |
| } | |
| } | |
| } |