Skip to content

Commit

Permalink
Split out an AbstractInMemoryNodeEntry class with behavior that is …
Browse files Browse the repository at this point in the history
…common to both `IncrementalNodeEntry` and the future standalone `EdgelessNodeEntry`.

`AbstractInMemoryNodeEntry` is implemented in terms of a `SkyKey`, `SkyValue`, and `DirtyBuildingState`. These are all the fields that the future `EdgelessInMemoryNodeEntry` will need.

PiperOrigin-RevId: 554834378
Change-Id: Ic75b342f4333e46d409eef8f31e9153cbb7a5ec6
  • Loading branch information
justinhorvitz authored and Copybara-Service committed Aug 8, 2023
1 parent 8449901 commit ef37e5c
Show file tree
Hide file tree
Showing 3 changed files with 298 additions and 261 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
// Copyright 2023 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 static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableSet;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;

/**
* Partial implementation of {@link InMemoryNodeEntry} containing behavior common to both {@link
* IncrementalInMemoryNodeEntry} and {@link EdgelessInMemoryNodeEntry}. All operations on this class
* are thread-safe.
*
* <p>Care was taken to provide certain compound operations to avoid certain check-then-act races.
* That means this class is somewhat closely tied to the exact Evaluator implementation.
*
* <p>Consider the example with two threads working on two nodes, where one depends on the other,
* say b depends on a. If a completes first, it's done. If it completes second, it needs to signal
* b, and potentially re-schedule it. If b completes first, it must exit, because it will be
* signaled (and re-scheduled) by a. If it completes second, it must signal (and re-schedule)
* itself. However, if the Evaluator supported re-entrancy for a node, then this wouldn't have to be
* so strict, because duplicate scheduling would be less problematic.
*
* <p>During its life, a node can go through states as follows:
*
* <ol>
* <li>Non-existent
* <li>Just created or marked as affected ({@link #isDone} is false; {@link #isDirty} is false)
* <li>Evaluating ({@link #isDone} is false; {@link #isDirty} is true)
* <li>Done ({@link #isDone} is true; {@link #isDirty} is false)
* </ol>
*
* <p>The "just created" state is there to allow the {@link ProcessableGraph#createIfAbsentBatch}
* and {@link NodeEntry#addReverseDepAndCheckIfDone} methods to be separate. All callers have to
* call both methods in that order if they want to create a node. The second method returns the
* NEEDS_SCHEDULING state only on the first time it was called. A caller that gets NEEDS_SCHEDULING
* back from that call must start the evaluation of this node, while any subsequent callers must
* not.
*
* <p>An entry is set to ALREADY_EVALUATING as soon as it is scheduled for evaluation. Thus, even a
* node that is never actually built (for instance, a dirty node that is verified as clean) is in
* the ALREADY_EVALUATING state until it is DONE.
*
* <p>From the DONE state, the node can go back to the "marked as affected" state.
*/
abstract class AbstractInMemoryNodeEntry implements InMemoryNodeEntry {

private final SkyKey key;

/** Actual data stored in this entry when it is done. */
@Nullable protected volatile SkyValue value;

/**
* Tracks state of this entry while it is evaluating (either on its initial build or after being
* marked dirty).
*/
@Nullable protected volatile DirtyBuildingState dirtyBuildingState;

AbstractInMemoryNodeEntry(SkyKey key) {
this.key = checkNotNull(key);
}

@Override
public final SkyKey getKey() {
return key;
}

private boolean isEvaluating() {
return dirtyBuildingState != null;
}

@Override
public boolean isDone() {
return value != null && dirtyBuildingState == null;
}

@Override
public final synchronized boolean isReadyToEvaluate() {
return !isDone()
&& isEvaluating()
&& (dirtyBuildingState.isReady(getNumTemporaryDirectDeps())
|| key.supportsPartialReevaluation());
}

@Override
public final synchronized boolean hasUnsignaledDeps() {
checkState(!isDone(), this);
checkState(isEvaluating(), this);
return !dirtyBuildingState.isReady(getNumTemporaryDirectDeps());
}

@Override
public synchronized boolean isDirty() {
return !isDone() && dirtyBuildingState != null;
}

@Override
public synchronized boolean isChanged() {
return !isDone() && dirtyBuildingState != null && dirtyBuildingState.isChanged();
}

@Override
public final synchronized SkyValue getValue() {
checkState(isDone(), "no value until done. ValueEntry: %s", this);
return ValueWithMetadata.justValue(value);
}

@Override
@Nullable
public final SkyValue getValueMaybeWithMetadata() {
return value;
}

@Override
@Nullable
public final synchronized ErrorInfo getErrorInfo() {
checkState(isDone(), "no errors until done. NodeEntry: %s", this);
return ValueWithMetadata.getMaybeErrorInfo(value);
}

@Override
public final synchronized void addExternalDep() {
checkNotNull(dirtyBuildingState, this);
dirtyBuildingState.addExternalDep();
}

@Override
public final synchronized void forceRebuild() {
checkNotNull(dirtyBuildingState, this);
checkState(isEvaluating(), this);
dirtyBuildingState.forceRebuild(getNumTemporaryDirectDeps());
}

@Override
public final synchronized DirtyState getDirtyState() {
checkNotNull(dirtyBuildingState, this);
return dirtyBuildingState.getDirtyState();
}

@Override
public final synchronized List<SkyKey> getNextDirtyDirectDeps() throws InterruptedException {
checkState(!hasUnsignaledDeps(), this);
checkNotNull(dirtyBuildingState, this);
checkState(dirtyBuildingState.isEvaluating(), "Not evaluating during getNextDirty? %s", this);
return dirtyBuildingState.getNextDirtyDirectDeps();
}

@Override
public final synchronized Iterable<SkyKey> getAllDirectDepsForIncompleteNode()
throws InterruptedException {
checkState(!isDone(), this);
if (!isDirty()) {
return getTemporaryDirectDeps().getAllElementsAsIterable();
} else {
// There may be duplicates here. Make sure everything is unique.
ImmutableSet.Builder<SkyKey> result = ImmutableSet.builder();
for (List<SkyKey> group : getTemporaryDirectDeps()) {
result.addAll(group);
}
result.addAll(
dirtyBuildingState.getAllRemainingDirtyDirectDeps(/* preservePosition= */ false));
return result.build();
}
}

@Override
public final synchronized ImmutableSet<SkyKey> getAllRemainingDirtyDirectDeps()
throws InterruptedException {
checkNotNull(dirtyBuildingState, this);
checkState(dirtyBuildingState.isEvaluating(), "Not evaluating for remaining dirty? %s", this);
if (isDirty()) {
DirtyState dirtyState = dirtyBuildingState.getDirtyState();
checkState(
dirtyState == DirtyState.REBUILDING || dirtyState == DirtyState.FORCED_REBUILDING, this);
return dirtyBuildingState.getAllRemainingDirtyDirectDeps(/* preservePosition= */ true);
} else {
return ImmutableSet.of();
}
}

@Override
public final synchronized void markRebuilding() {
checkNotNull(dirtyBuildingState, this).markRebuilding();
}

final GroupedDeps newGroupedDeps() {
// If the key opts into partial reevaluation, tracking deps with a HashSet is worth the extra
// memory cost -- see SkyFunctionEnvironment.PartialReevaluation.
return key.supportsPartialReevaluation() ? new GroupedDeps.WithHashSet() : new GroupedDeps();
}

abstract int getNumTemporaryDirectDeps();

@Override
public final synchronized boolean noDepsLastBuild() {
checkState(isEvaluating(), this);
return dirtyBuildingState.noDepsLastBuild();
}

@Override
public final synchronized void removeUnfinishedDeps(Set<SkyKey> unfinishedDeps) {
getTemporaryDirectDeps().remove(unfinishedDeps);
}

@Override
public final synchronized void addSingletonTemporaryDirectDep(SkyKey dep) {
getTemporaryDirectDeps().appendSingleton(dep);
}

@Override
public final synchronized void addTemporaryDirectDepGroup(List<SkyKey> group) {
getTemporaryDirectDeps().appendGroup(group);
}

@Override
public final synchronized void addTemporaryDirectDepsInGroups(
Set<SkyKey> deps, List<Integer> groupSizes) {
getTemporaryDirectDeps().appendGroups(deps, groupSizes);
}

@Override
public final int getPriority() {
var snapshot = dirtyBuildingState;
if (snapshot == null) {
return Integer.MAX_VALUE;
}
return snapshot.getPriority();
}

@Override
public final int depth() {
var snapshot = dirtyBuildingState;
if (snapshot == null) {
return 0;
}
return snapshot.depth();
}

@Override
public final void updateDepthIfGreater(int proposedDepth) {
var snapshot = dirtyBuildingState;
if (snapshot == null) {
return;
}
snapshot.updateDepthIfGreater(proposedDepth);
}

@Override
public final void incrementEvaluationCount() {
var snapshot = dirtyBuildingState;
if (snapshot == null) {
return;
}
snapshot.incrementEvaluationCount();
}

protected synchronized MoreObjects.ToStringHelper toStringHelper() {
return MoreObjects.toStringHelper(this)
.add("key", key)
.add("value", value)
.add("dirtyBuildingState", dirtyBuildingState);
}

@Override
public final synchronized String toString() {
return toStringHelper().toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,8 @@ private void checkFinishedBuildingWhenAboutToSetValue() {
* DirtyBuildingState#getNumOfGroupsInLastBuildDirectDeps()}.
*/
final void signalDep(
IncrementalInMemoryNodeEntry entry,
AbstractInMemoryNodeEntry entry,
NodeVersion version,
Version childVersion,
@Nullable SkyKey childForDebugging) {
// Synchronization isn't needed here because the only caller is InMemoryNodeEntry, which does it
Expand All @@ -217,7 +218,7 @@ final void signalDep(
}

// childVersion > version.lastEvaluated() means the child has changed since the last evaluation.
boolean childChanged = !childVersion.atMost(entry.version.lastEvaluated());
boolean childChanged = !childVersion.atMost(version.lastEvaluated());
if (childChanged) {
dirtyState = DirtyState.NEEDS_REBUILDING;
} else if (dirtyState == DirtyState.CHECK_DEPENDENCIES
Expand Down
Loading

0 comments on commit ef37e5c

Please sign in to comment.