blob: 964099b4d661dfb8afd2088587e09c2c5b039fb6 [file] [log] [blame]
// Copyright 2022 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.skyframe;
import com.google.auto.value.AutoOneOf;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.skyframe.SkyFunction.Environment.ClassToInstanceMapSkyKeyComputeState;
import com.google.devtools.build.skyframe.SkyFunction.Environment.SkyKeyComputeState;
import com.google.devtools.build.skyframe.SkyFunction.Reset;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
/**
* Contains the causes describing why a node, which opted into partial reevaluation, is getting
* reevaluated.
*
* <p>Accessible via {@link SkyKeyComputeState}. Nodes opting into partial reevaluation must access
* compute states via {@link ClassToInstanceMapSkyKeyComputeState}.
*
* <p>A node's mailbox may be in one of three general states:
*
* <ol>
* <li>"freshly initialized",
* <li>containing causes for the node's partial reevaluation, or,
* <li>empty of such causes.
* </ol>
*
* <p>See {@link Kind} for details.
*
* <p>The "Mailbox" naming convention comes from actor models, where concurrent processors of work
* coordinate by sending each other messages that get stored in "mailboxes" until consumed; see
* https://wikipedia.org/wiki/Erlang_(programming_language)#Concurrency_and_distribution_orientation
* for discussion.
*/
public class PartialReevaluationMailbox implements SkyKeyComputeState {
/** Will be {@code null} only before the first call to {@link #getMail()}. */
@GuardedBy("this")
@Nullable
private ImmutableList.Builder<SkyKey> signaledDeps;
@GuardedBy("this")
private boolean other;
/** General states that a mailbox may be in. */
public enum Kind {
/**
* Represents the first time a mailbox is accessed by its node. A mailbox may also be in this
* state because the mailbox's data was dropped due to memory pressure, or because of other
* Skyframe nodes completing in error. A {@link SkyFunction} that observes this state should
* (re)evaluate "from scratch"; its other {@link SkyKeyComputeState} data will be in a freshly
* initialized state too.
*/
FRESHLY_INITIALIZED,
/**
* Represents a nonempty set of causes for a node's reevaluation. See {@link Causes} for
* details.
*/
CAUSES,
/**
* Represents an empty set of causes for a node's reevaluation.
*
* <p>Reading from a mailbox, via {@link #getMail()}, empties it. Thereafter, it will no longer
* be {@link #FRESHLY_INITIALIZED}, unless Skyframe drops its {@link SkyKeyComputeState}.
* Reading empties its list of signaled dep keys and sets its {@link Causes#other} flag back to
* {@code false}.
*
* <p>This empty state may be observed during a reevaluation, even from a reevaluation's first
* read from its mailbox. When an event occurs that may cause a reevaluation (e.g., when a dep
* completes) adding that cause (e.g., that dep's key) to a parent's mailbox can race with that
* parent reading its mailbox if the parent is reevaluating at the same time. If such an add
* wins the race, then the parent consumes the cause during that reevaluation. The event may
* then schedule a subsequent reevaluation for that parent, which is necessary to handle the
* case in which the add lost the race. If no other causes get added before the parent reads its
* mailbox in that subsequent reevaluation, then the mailbox may be empty.
*/
EMPTY,
}
/**
* A mailbox's detailed state, including whether it was freshly initialized, and the causes it
* contains for its node's partial reevaluation, if any.
*/
@AutoOneOf(Kind.class)
public abstract static class Mail {
public abstract Kind kind();
abstract void freshlyInitialized();
abstract void empty();
public abstract Causes causes();
static Mail ofFreshlyInitialized() {
return AutoOneOf_PartialReevaluationMailbox_Mail.freshlyInitialized();
}
static Mail ofEmpty() {
return AutoOneOf_PartialReevaluationMailbox_Mail.empty();
}
static Mail ofCauses(Causes causes) {
return AutoOneOf_PartialReevaluationMailbox_Mail.causes(causes);
}
}
/**
* A nonempty set of causes for a node's partial reevaluation.
*
* <p>A dep which a parent node previously requested and observed to not be done will have its key
* added to that parent's mailbox after the dep completes and before the dep signals the parent.
* {@link #signaledDeps} returns that list of keys.
*
* <p>Skyframe may enqueue a node for evaluation for several other reasons, such as when the node
* declared an external dependency (via {@link SkyFunction.Environment#dependOnFuture}) that
* completes, or when the node's {@link SkyFunction#compute} method returns a {@link Reset} value
* and the node is restarted. In some of these cases (e.g. returning a {@link Reset} value), the
* node's {@link SkyKeyComputeState} will be invalidated, which also drops its mailbox, and the
* next time that mailbox is read it will return a "freshly initialized" state. But in others
* (e.g. an external dependency completes), the node's {@link SkyKeyComputeState} is retained. In
* any of these cases in which a node is enqueued for evaluation and its mailbox is retained, a
* flag will be set in the node's mailbox to indicate that the node's {@link SkyFunction} should
* try its best to make progress, by, e.g., checking whether its external dep futures have
* completed, checking whether its previously requested deps are done, or reevaluating from
* scratch. ({@link Causes#other}) returns the value of that flag.
*/
@AutoValue
public abstract static class Causes {
static Causes create(ImmutableList<SkyKey> signaledDeps, boolean other) {
return new AutoValue_PartialReevaluationMailbox_Causes(signaledDeps, other);
}
/**
* {@link SkyKey}s of previously requested deps which have completed since the last time the
* mailbox was read.
*/
public abstract ImmutableList<SkyKey> signaledDeps();
/**
* Whether Skyframe enqueued a reevaluation for any other reason besides a dep completing
* normally, in such a way that the dep's key would be added to {@link #signaledDeps}.
*/
public abstract boolean other();
}
private PartialReevaluationMailbox() {}
public static PartialReevaluationMailbox from(ClassToInstanceMapSkyKeyComputeState computeState) {
return computeState.getInstance(
PartialReevaluationMailbox.class, PartialReevaluationMailbox::new);
}
/** Used by Skyframe to record that a dep has signaled a node opting into partial reevaluation. */
synchronized void signal(SkyKey dep) {
if (signaledDeps != null) {
signaledDeps.add(dep);
}
}
/**
* Used by Skyframe to record that a node opting into partial reevaluation has been enqueued for
* evaluation in contexts where that happens for reasons other than a dep signaling it.
*/
synchronized void enqueuedNotByDeps() {
other = true;
}
/** Gets and clears the current causes for a node's partial reevaluation. */
public Mail getMail() {
@Nullable ImmutableList.Builder<SkyKey> signaledDeps;
boolean other;
ImmutableList.Builder<SkyKey> newBuilder = new ImmutableList.Builder<>();
synchronized (this) {
signaledDeps = this.signaledDeps;
this.signaledDeps = newBuilder;
other = this.other;
this.other = false;
}
if (signaledDeps == null) {
return Mail.ofFreshlyInitialized();
}
ImmutableList<SkyKey> signaledDepsList = signaledDeps.build();
if (signaledDepsList.isEmpty() && !other) {
return Mail.ofEmpty();
}
return Mail.ofCauses(Causes.create(signaledDepsList, other));
}
}