blob: bff051b5b1d8529f722e8b72097bd4769f044e90 [file] [log] [blame]
ulfjackd19d4ba2019-02-21 02:08:40 -08001// Copyright 2019 The Bazel Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14package com.google.devtools.build.lib.skyframe;
15
mschaller37cc33a2021-03-24 19:37:06 -070016import static com.google.common.base.Preconditions.checkArgument;
17
ulfjackd5767c82019-02-21 04:21:57 -080018import com.google.common.base.Preconditions;
19import com.google.common.util.concurrent.SettableFuture;
ulfjackd19d4ba2019-02-21 02:08:40 -080020import com.google.devtools.build.lib.actions.Action;
21import com.google.devtools.build.lib.actions.ActionExecutionException;
22import com.google.devtools.build.lib.actions.ActionLookupData;
mschaller37cc33a2021-03-24 19:37:06 -070023import com.google.devtools.build.lib.actions.Artifact.OwnerlessArtifactWrapper;
24import com.google.devtools.build.lib.actions.LostInputsActionExecutionException;
Googler2036a0f2023-01-31 15:53:45 -080025import com.google.devtools.build.lib.actions.SharedActionEvent;
janakrb4540d82020-07-24 06:50:11 -070026import com.google.devtools.build.lib.bugreport.BugReport;
Googler9c930832022-08-05 07:46:51 -070027import com.google.devtools.build.lib.skyframe.ActionExecutionValue.ActionTransformException;
ulfjackd19d4ba2019-02-21 02:08:40 -080028import com.google.devtools.build.skyframe.SkyFunction;
mschaller37cc33a2021-03-24 19:37:06 -070029import com.google.devtools.build.skyframe.SkyFunction.Environment;
Googler0e04fc22024-02-08 14:29:51 -080030import com.google.devtools.build.skyframe.SkyKey;
mschaller37cc33a2021-03-24 19:37:06 -070031import com.google.errorprone.annotations.DoNotCall;
32import java.util.concurrent.ConcurrentMap;
ulfjackd5767c82019-02-21 04:21:57 -080033import javax.annotation.Nullable;
ulfjackd19d4ba2019-02-21 02:08:40 -080034import javax.annotation.concurrent.GuardedBy;
35
36/**
37 * A state machine representing the synchronous or asynchronous execution of an action. This is
38 * shared between all instances of the same shared action and must therefore be thread-safe. Note
39 * that only one caller will receive events and output for this action.
40 */
41final class ActionExecutionState {
ulfjackd5767c82019-02-21 04:21:57 -080042 /** The owner of this object. Only the owner is allowed to continue work on the state machine. */
ulfjackd19d4ba2019-02-21 02:08:40 -080043 private final ActionLookupData actionLookupData;
44
ulfjackd5767c82019-02-21 04:21:57 -080045 // Both state and completionFuture may only be read or set when holding the lock for this. The
46 // state machine for these looks like this:
47 //
48 // !state.isDone,completionFuture=null -----> !state.isDone,completionFuture=<value>
49 // | |
50 // | | completionFuture.set()
51 // v v
52 // state.isDone,completionFuture=null
53 //
mschaller37cc33a2021-03-24 19:37:06 -070054 // (Also, via obsolete(), all states can transition to state==Obsolete.INSTANCE with a null
55 // completionFuture, which is terminal.)
56 //
ulfjackd5767c82019-02-21 04:21:57 -080057 // No other states are legal. In particular, state.isDone,completionFuture=<value> is not a legal
58 // state.
59
ulfjackd19d4ba2019-02-21 02:08:40 -080060 @GuardedBy("this")
61 private ActionStepOrResult state;
62
ulfjackd5767c82019-02-21 04:21:57 -080063 /**
64 * A future to represent action completion of the primary action (randomly picked from the set of
65 * shared actions). This is initially {@code null}, and is only set to a non-null value if this
66 * turns out to be a shared action, and the primary action is not finished yet (i.e., {@code
67 * !state.isDone}. It is non-null while the primary action is being executed, at which point the
68 * thread completing the primary action completes the future, and also sets this field to null.
69 *
70 * <p>The reason for this roundabout approach is to avoid memory allocation if this is not a
71 * shared action, and to release the memory once the action is done.
janakr98f06152020-02-13 16:48:23 -080072 *
73 * <p>Skyframe will attempt to cancel this future if the evaluation is interrupted, which violates
74 * the concurrency assumptions this class makes. Beware!
ulfjackd5767c82019-02-21 04:21:57 -080075 */
76 @GuardedBy("this")
77 @Nullable
78 private SettableFuture<Void> completionFuture;
79
ulfjackd19d4ba2019-02-21 02:08:40 -080080 ActionExecutionState(ActionLookupData actionLookupData, ActionStepOrResult state) {
ulfjackd5767c82019-02-21 04:21:57 -080081 this.actionLookupData = Preconditions.checkNotNull(actionLookupData);
82 this.state = Preconditions.checkNotNull(state);
ulfjackd19d4ba2019-02-21 02:08:40 -080083 }
84
Googler5cc598c2022-07-06 03:29:45 -070085 @Nullable
janakr98f06152020-02-13 16:48:23 -080086 ActionExecutionValue getResultOrDependOnFuture(
ulfjackd19d4ba2019-02-21 02:08:40 -080087 SkyFunction.Environment env,
88 ActionLookupData actionLookupData,
89 Action action,
ulfjack646a34c2019-05-13 01:29:56 -070090 SharedActionCallback sharedActionCallback)
ulfjackd19d4ba2019-02-21 02:08:40 -080091 throws ActionExecutionException, InterruptedException {
ulfjack2ea2eab2019-02-21 06:11:22 -080092 if (this.actionLookupData.equals(actionLookupData)) {
mschaller37cc33a2021-03-24 19:37:06 -070093 // This object is owned by the Skyframe node executed by the current thread, so we use it to
94 // run the state machine.
ulfjackd19d4ba2019-02-21 02:08:40 -080095 return runStateMachine(env);
96 }
ulfjackd5767c82019-02-21 04:21:57 -080097
mschaller37cc33a2021-03-24 19:37:06 -070098 // This is a shared action, and the primary action is owned by another Skyframe node. If the
99 // primary action is done, we simply return the done value. If this state is obsolete (e.g.
100 // because the other node is rewinding), we restart. Otherwise we register a dependency on the
101 // completionFuture and return null.
ulfjackd5767c82019-02-21 04:21:57 -0800102 ActionExecutionValue result;
ulfjackd19d4ba2019-02-21 02:08:40 -0800103 synchronized (this) {
mschaller37cc33a2021-03-24 19:37:06 -0700104 if (state == Obsolete.INSTANCE) {
105 scheduleRestart(env);
106 return null;
107 }
ulfjackd5767c82019-02-21 04:21:57 -0800108 if (!state.isDone()) {
109 if (completionFuture == null) {
110 completionFuture = SettableFuture.create();
111 }
ulfjack646a34c2019-05-13 01:29:56 -0700112 // We expect to only call this once per shared action; this method should only be called
113 // again after the future is completed.
114 sharedActionCallback.actionStarted();
ulfjackd5767c82019-02-21 04:21:57 -0800115 env.dependOnFuture(completionFuture);
janakr98f06152020-02-13 16:48:23 -0800116 if (!env.valuesMissing()) {
117 Preconditions.checkState(
118 completionFuture.isCancelled(), "%s %s", this.actionLookupData, actionLookupData);
119 // The future is unexpectedly done. This must be because it was registered by another
120 // thread earlier and was canceled by Skyframe. We are about to be interrupted ourselves,
121 // but have to do something in the meantime. We can just register a dep with a new future,
122 // then complete it and return. If for some reason this argument is incorrect, we will be
123 // restarted immediately and hopefully have a more consistent result.
mschaller37cc33a2021-03-24 19:37:06 -0700124 scheduleRestart(env);
janakr98f06152020-02-13 16:48:23 -0800125 }
ulfjackd5767c82019-02-21 04:21:57 -0800126 return null;
ulfjackd19d4ba2019-02-21 02:08:40 -0800127 }
ulfjackd5767c82019-02-21 04:21:57 -0800128 result = state.get();
129 }
ulfjack646a34c2019-05-13 01:29:56 -0700130 sharedActionCallback.actionCompleted();
Googler2036a0f2023-01-31 15:53:45 -0800131
132 ActionExecutionValue transformed;
Googler9c930832022-08-05 07:46:51 -0700133 try {
Googler2036a0f2023-01-31 15:53:45 -0800134 transformed = result.transformForSharedAction(action);
Googler9c930832022-08-05 07:46:51 -0700135 } catch (ActionTransformException e) {
136 throw new IllegalStateException(
137 String.format("Cannot share %s and %s", this.actionLookupData, actionLookupData), e);
138 }
Googler2036a0f2023-01-31 15:53:45 -0800139 env.getListener().post(new SharedActionEvent(result, transformed));
140 return transformed;
ulfjackd5767c82019-02-21 04:21:57 -0800141 }
142
mschaller37cc33a2021-03-24 19:37:06 -0700143 private static void scheduleRestart(Environment env) {
144 SettableFuture<Void> dummyFuture = SettableFuture.create();
145 env.dependOnFuture(dummyFuture);
146 dummyFuture.set(null);
147 }
148
Googler5cc598c2022-07-06 03:29:45 -0700149 @Nullable
ulfjackd5767c82019-02-21 04:21:57 -0800150 private ActionExecutionValue runStateMachine(SkyFunction.Environment env)
151 throws ActionExecutionException, InterruptedException {
152 ActionStepOrResult original;
153 synchronized (this) {
mschaller37cc33a2021-03-24 19:37:06 -0700154 if (state == Obsolete.INSTANCE) {
155 scheduleRestart(env);
156 return null;
157 }
ulfjackd5767c82019-02-21 04:21:57 -0800158 original = state;
159 }
160 ActionStepOrResult current = original;
161 // We do the work _outside_ a synchronized block to avoid blocking threads working on shared
162 // actions that only want to register with the completionFuture.
163 try {
164 while (!current.isDone()) {
165 // Run the state machine for one step; isDone returned false, so this is safe.
166 current = current.run(env);
167
168 // This method guarantees that it either blocks until the action is completed and returns
169 // a non-null value, or it registers a dependency with Skyframe and returns null; it must
170 // not return null without registering a dependency, i.e., if {@code !env.valuesMissing()}.
171 if (env.valuesMissing()) {
janakrb4540d82020-07-24 06:50:11 -0700172 if (current.isDone()) {
173 // This can happen if there was an error in a dep, but another dep was missing. The
174 // Skyframe contract is that this SkyFunction should eagerly process that exception, so
175 // that errors can be transformed in --nokeep_going mode.
176 ActionExecutionValue value = current.get();
177 BugReport.sendBugReport(
178 new IllegalStateException(
179 actionLookupData + " returned " + value + " with values missing"));
180 }
ulfjackd5767c82019-02-21 04:21:57 -0800181 return null;
182 }
183 }
184 } finally {
185 synchronized (this) {
mschaller37cc33a2021-03-24 19:37:06 -0700186 if (state != Obsolete.INSTANCE) {
187 Preconditions.checkState(state == original, "Another thread illegally modified state");
188 state = current;
189 if (current.isDone() && completionFuture != null) {
190 completionFuture.set(null);
191 completionFuture = null;
192 }
ulfjackd19d4ba2019-02-21 02:08:40 -0800193 }
194 }
195 }
ulfjackd19d4ba2019-02-21 02:08:40 -0800196 // We're done, return the value to the caller (or throw an exception).
ulfjackd5767c82019-02-21 04:21:57 -0800197 return current.get();
ulfjackd19d4ba2019-02-21 02:08:40 -0800198 }
199
mschaller37cc33a2021-03-24 19:37:06 -0700200 /**
201 * Removes this state from {@code buildActionMap}, marks it obsolete so that racing shared actions
202 * with a reference to this state will restart, and signals to coalesced shared actions that they
203 * should re-evaluate.
204 */
Googler0e04fc22024-02-08 14:29:51 -0800205 synchronized void obsolete(
206 SkyKey requester,
mschaller37cc33a2021-03-24 19:37:06 -0700207 ConcurrentMap<OwnerlessArtifactWrapper, ActionExecutionState> buildActionMap,
208 OwnerlessArtifactWrapper ownerlessArtifactWrapper) {
Googler0e04fc22024-02-08 14:29:51 -0800209 if (actionLookupData.equals(requester)) {
210 // An action state's owner only obsoletes it when rewinding. The lost inputs exception thrown
211 // from ActionStepOrResult#run left its state undone.
212 Preconditions.checkState(
213 !state.isDone(), "owner unexpectedly obsoleted done state: %s", actionLookupData);
mschaller37cc33a2021-03-24 19:37:06 -0700214 ActionExecutionState removedState = buildActionMap.remove(ownerlessArtifactWrapper);
215 Preconditions.checkState(
216 removedState == this,
Googler0e04fc22024-02-08 14:29:51 -0800217 "owner removed unexpected state from buildActionMap; owner: %s, removed: %s",
mschaller37cc33a2021-03-24 19:37:06 -0700218 actionLookupData,
219 removedState.actionLookupData);
220 state = Obsolete.INSTANCE;
Googler0e04fc22024-02-08 14:29:51 -0800221 if (completionFuture != null) {
222 completionFuture.set(null);
223 completionFuture = null;
224 }
225 return;
mschaller37cc33a2021-03-24 19:37:06 -0700226 }
Googler0e04fc22024-02-08 14:29:51 -0800227 if (!state.isDone()) {
228 // An action obsoletes other actions' states when rewinding its dependencies. It may race with
229 // other actions to do so. Removing the buildActionMap entry must only be done by the race's
230 // winner, to ensure the removal only happens once and removes this state.
231 //
232 // An action may also attempt to obsolete a dependency's not-done state, if it lost the race
233 // with another rewinding action, and the dep started evaluating. If so, then do nothing,
234 // because that dep is already doing what it needs to.
235 return;
236 }
237 ActionExecutionState removedState = buildActionMap.remove(ownerlessArtifactWrapper);
238 Preconditions.checkState(
239 removedState == this,
240 "removed unexpected state from buildActionMap; requester: %s, this: %s, removed: %s",
241 requester,
242 actionLookupData,
243 removedState.actionLookupData);
244 state = Obsolete.INSTANCE;
mschaller37cc33a2021-03-24 19:37:06 -0700245 }
246
ulfjack646a34c2019-05-13 01:29:56 -0700247 /** A callback to receive events for shared actions that are not executed. */
248 public interface SharedActionCallback {
249 /** Called if the action is shared and the primary action is already executing. */
250 void actionStarted();
251
252 /**
253 * Called when the primary action is done (on the next call to {@link
254 * #getResultOrDependOnFuture}.
255 */
256 void actionCompleted();
257 }
258
ulfjackd19d4ba2019-02-21 02:08:40 -0800259 /**
260 * A state machine where instances of this interface either represent an intermediate state that
261 * requires more work to be done (possibly waiting for a ListenableFuture to complete) or the
262 * final result of the executed action (either an ActionExecutionValue or an Exception).
263 *
264 * <p>This design allows us to store the current state of the in-progress action execution using a
265 * single object reference.
ulfjackd5767c82019-02-21 04:21:57 -0800266 *
267 * <p>Do not implement this interface directly! In order to implement an action step, subclass
268 * {@link ActionStep}, and implement {@link #run}. In order to represent a result, use {@link
269 * #of}.
ulfjackd19d4ba2019-02-21 02:08:40 -0800270 */
Googler9d7a78d2024-03-22 08:52:52 -0700271 sealed interface ActionStepOrResult permits ActionStep, Finished, Exceptional, Obsolete {
ulfjackd19d4ba2019-02-21 02:08:40 -0800272 static ActionStepOrResult of(ActionExecutionValue value) {
ulfjackd5767c82019-02-21 04:21:57 -0800273 return new Finished(value);
ulfjackd19d4ba2019-02-21 02:08:40 -0800274 }
275
mschaller37cc33a2021-03-24 19:37:06 -0700276 /**
277 * Must not be called with a {@link LostInputsActionExecutionException}. Throw it from {@link
278 * #run} instead.
279 */
ulfjackd19d4ba2019-02-21 02:08:40 -0800280 static ActionStepOrResult of(ActionExecutionException exception) {
mschaller37cc33a2021-03-24 19:37:06 -0700281 checkArgument(
282 !(exception instanceof LostInputsActionExecutionException),
283 "unexpected LostInputs exception: %s",
284 exception);
ulfjackd5767c82019-02-21 04:21:57 -0800285 return new Exceptional(exception);
ulfjackd19d4ba2019-02-21 02:08:40 -0800286 }
287
mschaller37cc33a2021-03-24 19:37:06 -0700288 @DoNotCall("Throw from #run instead.")
289 static ActionStepOrResult of(LostInputsActionExecutionException ignored) {
290 throw new IllegalArgumentException();
291 }
292
ulfjackd19d4ba2019-02-21 02:08:40 -0800293 static ActionStepOrResult of(InterruptedException exception) {
ulfjackd5767c82019-02-21 04:21:57 -0800294 return new Exceptional(exception);
ulfjackd19d4ba2019-02-21 02:08:40 -0800295 }
296
297 /**
298 * Returns true if and only if the underlying action is complete, i.e., it is legal to call
ulfjackd5767c82019-02-21 04:21:57 -0800299 * {@link #get}. The return value of a single object must not change over time. Instead, call
300 * {@link ActionStepOrResult#of} to return a final result (or exception).
ulfjackd19d4ba2019-02-21 02:08:40 -0800301 */
ulfjackd5767c82019-02-21 04:21:57 -0800302 boolean isDone();
ulfjackd19d4ba2019-02-21 02:08:40 -0800303
304 /**
305 * Returns the next state of the state machine after performing some work towards the end goal
306 * of executing the action. This must only be called if {@link #isDone} returns false, and must
307 * only be called by one thread at a time for the same instance.
308 */
mschaller37cc33a2021-03-24 19:37:06 -0700309 ActionStepOrResult run(SkyFunction.Environment env)
310 throws LostInputsActionExecutionException, InterruptedException;
ulfjackd19d4ba2019-02-21 02:08:40 -0800311
312 /**
313 * Returns the final value of the action or an exception to indicate that the action failed (or
314 * the process was interrupted). This must only be called if {@link #isDone} returns true.
315 */
ulfjackd5767c82019-02-21 04:21:57 -0800316 ActionExecutionValue get() throws ActionExecutionException, InterruptedException;
317 }
318
319 /**
320 * Abstract implementation of {@link ActionStepOrResult} that declares final implementations for
mschaller37cc33a2021-03-24 19:37:06 -0700321 * {@link #isDone} (to return false) and {@link #get} (to throw {@link IllegalStateException}).
ulfjackd5767c82019-02-21 04:21:57 -0800322 *
323 * <p>The framework prevents concurrent calls to {@link #run}, so implementations can keep state
324 * without having to lock. Note that there may be multiple calls to {@link #run} from different
325 * threads, as long as they do not overlap in time.
326 */
Googler9d7a78d2024-03-22 08:52:52 -0700327 abstract static non-sealed class ActionStep implements ActionStepOrResult {
ulfjackd5767c82019-02-21 04:21:57 -0800328 @Override
329 public final boolean isDone() {
330 return false;
331 }
332
333 @Override
334 public final ActionExecutionValue get() {
ulfjackd19d4ba2019-02-21 02:08:40 -0800335 throw new IllegalStateException();
336 }
337 }
338
339 /**
340 * Represents a finished action with a specific value. We specifically avoid anonymous inner
341 * classes to not accidentally retain a reference to the ActionRunner.
342 */
ulfjackd5767c82019-02-21 04:21:57 -0800343 private static final class Finished implements ActionStepOrResult {
ulfjackd19d4ba2019-02-21 02:08:40 -0800344 private final ActionExecutionValue value;
345
ulfjackd5767c82019-02-21 04:21:57 -0800346 Finished(ActionExecutionValue value) {
ulfjackd19d4ba2019-02-21 02:08:40 -0800347 this.value = value;
348 }
349
ulfjackd5767c82019-02-21 04:21:57 -0800350 @Override
351 public boolean isDone() {
352 return true;
353 }
354
355 @Override
356 public ActionStepOrResult run(SkyFunction.Environment env) {
357 throw new IllegalStateException();
358 }
359
360 @Override
ulfjackd19d4ba2019-02-21 02:08:40 -0800361 public ActionExecutionValue get() {
362 return value;
363 }
364 }
365
366 /**
367 * Represents a finished action with an exception. We specifically avoid anonymous inner classes
368 * to not accidentally retain a reference to the ActionRunner.
369 */
ulfjackd5767c82019-02-21 04:21:57 -0800370 private static final class Exceptional implements ActionStepOrResult {
ulfjackd19d4ba2019-02-21 02:08:40 -0800371 private final Exception e;
372
ulfjackd5767c82019-02-21 04:21:57 -0800373 Exceptional(ActionExecutionException e) {
ulfjackd19d4ba2019-02-21 02:08:40 -0800374 this.e = e;
375 }
376
ulfjackd5767c82019-02-21 04:21:57 -0800377 Exceptional(InterruptedException e) {
ulfjackd19d4ba2019-02-21 02:08:40 -0800378 this.e = e;
379 }
380
ulfjackd5767c82019-02-21 04:21:57 -0800381 @Override
382 public boolean isDone() {
383 return true;
384 }
385
386 @Override
387 public ActionStepOrResult run(SkyFunction.Environment env) {
388 throw new IllegalStateException();
389 }
390
391 @Override
ulfjackd19d4ba2019-02-21 02:08:40 -0800392 public ActionExecutionValue get() throws ActionExecutionException, InterruptedException {
Googler3beaaaf2024-04-25 18:37:43 -0700393 if (e instanceof InterruptedException interruptedException) {
394 throw interruptedException;
ulfjackd19d4ba2019-02-21 02:08:40 -0800395 }
396 throw (ActionExecutionException) e;
397 }
398 }
mschaller37cc33a2021-03-24 19:37:06 -0700399
400 /**
401 * Represents an action state that is obsolete. Any non-primary shared actions observing this
402 * state must restart (see {@link #scheduleRestart}.
403 */
404 private static final class Obsolete implements ActionStepOrResult {
405 private static final Obsolete INSTANCE = new Obsolete();
406
407 @Override
408 public boolean isDone() {
409 return false;
410 }
411
412 @Override
413 public ActionStepOrResult run(SkyFunction.Environment env) {
414 throw new IllegalStateException();
415 }
416
417 @Override
418 public ActionExecutionValue get() {
419 throw new IllegalStateException();
420 }
421 }
ulfjackd19d4ba2019-02-21 02:08:40 -0800422}