| // Copyright 2021 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.dynamic; | |
| import static com.google.common.base.Preconditions.checkNotNull; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.flogger.GoogleLogger; | |
| import com.google.common.util.concurrent.ListeningExecutorService; | |
| import com.google.common.util.concurrent.SettableFuture; | |
| import com.google.devtools.build.lib.actions.ActionExecutionContext; | |
| import com.google.devtools.build.lib.actions.DynamicStrategyRegistry.DynamicMode; | |
| import com.google.devtools.build.lib.actions.ExecException; | |
| import com.google.devtools.build.lib.actions.Spawn; | |
| import com.google.devtools.build.lib.actions.SpawnResult; | |
| import com.google.devtools.build.lib.util.io.FileOutErr; | |
| import com.google.devtools.build.lib.vfs.FileSystemUtils; | |
| import com.google.devtools.build.lib.vfs.Path; | |
| import java.io.IOException; | |
| import java.util.concurrent.Callable; | |
| import java.util.concurrent.ExecutionException; | |
| import java.util.concurrent.Future; | |
| import java.util.concurrent.Semaphore; | |
| import java.util.concurrent.atomic.AtomicBoolean; | |
| import java.util.concurrent.atomic.AtomicReference; | |
| /** | |
| * Wraps the execution of a function that is supposed to execute a spawn via a strategy and only | |
| * updates the stdout/stderr files if this spawn succeeds. | |
| */ | |
| abstract class Branch implements Callable<ImmutableList<SpawnResult>> { | |
| private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); | |
| /** | |
| * True if this branch is still starting up, i.e. didn't get to the inner part of {@link | |
| * #callImpl(ActionExecutionContext)} yet. | |
| */ | |
| protected final AtomicBoolean starting = new AtomicBoolean(true); | |
| /** The {@link Spawn} this branch is running. */ | |
| protected final Spawn spawn; | |
| /** | |
| * The {@link SettableFuture} with the results from running the spawn. Must not be null if | |
| * execution succeeded. | |
| */ | |
| protected final SettableFuture<ImmutableList<SpawnResult>> future = SettableFuture.create(); | |
| /** | |
| * The strategy (local or remote) that cancelled the other one. Null until one has been cancelled. | |
| * This object is shared between the local and remote branch of an action. | |
| */ | |
| protected final AtomicReference<DynamicMode> strategyThatCancelled; | |
| /** | |
| * Semaphore that indicates whether this branch is done, i.e. either completed or cancelled. This | |
| * is needed to wait for the branch to finish its own cleanup (e.g. terminating subprocesses) once | |
| * it has been cancelled. | |
| */ | |
| protected final Semaphore done = new Semaphore(0); | |
| protected final DynamicExecutionOptions options; | |
| protected final ActionExecutionContext context; | |
| /** | |
| * Creates a new branch of dynamic execution. | |
| * | |
| * @param context the action execution context given to the dynamic strategy, used to obtain the | |
| * final location of the stdout/stderr | |
| */ | |
| Branch( | |
| ActionExecutionContext context, | |
| Spawn spawn, | |
| AtomicReference<DynamicMode> strategyThatCancelled, | |
| DynamicExecutionOptions options) { | |
| this.context = context; | |
| this.spawn = spawn; | |
| this.strategyThatCancelled = strategyThatCancelled; | |
| this.options = options; | |
| } | |
| boolean isDone() { | |
| return future.isDone(); | |
| } | |
| /** Returns the {@code Semaphore} indicating whether this branch is done. */ | |
| Semaphore getDoneSemaphore() { | |
| return done; | |
| } | |
| /** Returns whether this branch has already been cancelled. */ | |
| boolean isCancelled() { | |
| return future.isCancelled(); | |
| } | |
| /** Cancels this branch. Equivalent to {@code Future.cancel(true)}. */ | |
| boolean cancel() { | |
| return future.cancel(true); | |
| } | |
| /** Gets the results from this branch, when available. Behaves like {@link Future#get()} */ | |
| ImmutableList<SpawnResult> getResults() throws ExecutionException, InterruptedException { | |
| return future.get(); | |
| } | |
| public Spawn getSpawn() { | |
| return spawn; | |
| } | |
| public abstract DynamicMode getMode(); | |
| /** Returns a human-readable description of what we can tell about the state of this Future. */ | |
| String branchState() { | |
| return (isCancelled() ? "cancelled" : "not cancelled") | |
| + " and " | |
| + (isDone() ? "done" : "not done"); | |
| } | |
| /** Executes this branch using the provided executor. */ | |
| public void execute(ListeningExecutorService executor) { | |
| future.setFuture(executor.submit(this)); | |
| } | |
| /** | |
| * Moves a set of stdout/stderr files over another one. Errors during the move are logged and | |
| * swallowed. | |
| * | |
| * @param from the source location | |
| * @param to the target location | |
| */ | |
| private static void moveFileOutErr(FileOutErr from, FileOutErr to) { | |
| try { | |
| if (from.getOutputPath().exists()) { | |
| FileSystemUtils.moveFile(from.getOutputPath(), to.getOutputPath()); | |
| } | |
| if (from.getErrorPath().exists()) { | |
| FileSystemUtils.moveFile(from.getErrorPath(), to.getErrorPath()); | |
| } | |
| } catch (IOException e) { | |
| logger.atWarning().withCause(e).log("Could not move action logs from execution"); | |
| } | |
| } | |
| private static FileOutErr getSuffixedFileOutErr(FileOutErr fileOutErr, String suffix) { | |
| Path outDir = checkNotNull(fileOutErr.getOutputPath().getParentDirectory()); | |
| String outBaseName = fileOutErr.getOutputPath().getBaseName(); | |
| Path errDir = checkNotNull(fileOutErr.getErrorPath().getParentDirectory()); | |
| String errBaseName = fileOutErr.getErrorPath().getBaseName(); | |
| return new FileOutErr( | |
| outDir.getChild(outBaseName + suffix), errDir.getChild(errBaseName + suffix)); | |
| } | |
| /** | |
| * Hook to execute a spawn using an arbitrary strategy. | |
| * | |
| * @param context the action execution context where the spawn can write its stdout/stderr. The | |
| * location of these files is specific to this branch. | |
| * @return the spawn results if execution was successful | |
| * @throws InterruptedException if the branch was cancelled or an interrupt was caught | |
| * @throws ExecException if the spawn execution fails | |
| */ | |
| abstract ImmutableList<SpawnResult> callImpl(ActionExecutionContext context) | |
| throws InterruptedException, ExecException; | |
| /** | |
| * Executes the {@link #callImpl} hook and handles stdout/stderr. | |
| * | |
| * @return the spawn results if execution was successful | |
| * @throws InterruptedException if the branch was cancelled or an interrupt was caught | |
| * @throws ExecException if the spawn execution fails | |
| */ | |
| @Override | |
| public final ImmutableList<SpawnResult> call() throws InterruptedException, ExecException { | |
| FileOutErr fileOutErr = getSuffixedFileOutErr(context.getFileOutErr(), "." + getMode().name()); | |
| ImmutableList<SpawnResult> results = null; | |
| ExecException exception = null; | |
| try { | |
| results = callImpl(context.withFileOutErr(fileOutErr)); | |
| } catch (ExecException e) { | |
| exception = e; | |
| } finally { | |
| try { | |
| fileOutErr.close(); | |
| } catch (IOException ignored) { | |
| // Nothing we can do here. | |
| } | |
| } | |
| moveFileOutErr(fileOutErr, context.getFileOutErr()); | |
| if (exception != null) { | |
| throw exception; | |
| } else { | |
| checkNotNull(results); | |
| return results; | |
| } | |
| } | |
| } |