use of com.google.devtools.build.skyframe.SkyFunction.Environment in project bazel by bazelbuild.
the class MemoizingEvaluatorTest method cycleAboveIndependentCycle.
/** @see ParallelEvaluatorTest#cycleAboveIndependentCycle() */
@Test
public void cycleAboveIndependentCycle() throws Exception {
makeGraphDeterministic();
SkyKey aKey = GraphTester.toSkyKey("a");
final SkyKey bKey = GraphTester.toSkyKey("b");
SkyKey cKey = GraphTester.toSkyKey("c");
final SkyKey leafKey = GraphTester.toSkyKey("leaf");
// When aKey depends on leafKey and bKey,
tester.getOrCreate(aKey).setBuilder(new SkyFunction() {
@Nullable
@Override
public SkyValue compute(SkyKey skyKey, Environment env) throws InterruptedException {
env.getValues(ImmutableList.of(leafKey, bKey));
return null;
}
@Nullable
@Override
public String extractTag(SkyKey skyKey) {
return null;
}
});
// And bKey depends on cKey,
tester.getOrCreate(bKey).addDependency(cKey);
// And cKey depends on aKey and bKey in that order,
tester.getOrCreate(cKey).addDependency(aKey).addDependency(bKey);
// And leafKey is a leaf node,
tester.set(leafKey, new StringValue("leafy"));
// Then when we evaluate,
EvaluationResult<StringValue> result = tester.eval(/*keepGoing=*/
true, aKey);
// aKey has an error,
assertEquals(null, result.get(aKey));
if (cyclesDetected()) {
// And both cycles were found underneath aKey: the (aKey->bKey->cKey) cycle, and the
// aKey->(bKey->cKey) cycle. This is because cKey depended on aKey and then bKey, so it pushed
// them down on the stack in that order, so bKey was processed first. It found its cycle, then
// popped off the stack, and then aKey was processed and found its cycle.
assertThatEvaluationResult(result).hasErrorEntryForKeyThat(aKey).hasCycleInfoThat().containsExactly(new CycleInfo(ImmutableList.of(aKey, bKey, cKey)), new CycleInfo(ImmutableList.of(aKey), ImmutableList.of(bKey, cKey)));
} else {
assertThatEvaluationResult(result).hasErrorEntryForKeyThat(aKey).hasCycleInfoThat().hasSize(1);
}
// When leafKey is changed, so that aKey will be marked as NEEDS_REBUILDING,
tester.set(leafKey, new StringValue("crunchy"));
// And cKey is invalidated, so that cycle checking will have to explore the full graph,
tester.getOrCreate(cKey, /*markAsModified=*/
true);
tester.invalidate();
// Then when we evaluate,
EvaluationResult<StringValue> result2 = tester.eval(/*keepGoing=*/
true, aKey);
// Things are just as before.
assertEquals(null, result2.get(aKey));
if (cyclesDetected()) {
assertThatEvaluationResult(result).hasErrorEntryForKeyThat(aKey).hasCycleInfoThat().containsExactly(new CycleInfo(ImmutableList.of(aKey, bKey, cKey)), new CycleInfo(ImmutableList.of(aKey), ImmutableList.of(bKey, cKey)));
} else {
assertThatEvaluationResult(result).hasErrorEntryForKeyThat(aKey).hasCycleInfoThat().hasSize(1);
}
}
use of com.google.devtools.build.skyframe.SkyFunction.Environment in project bazel by bazelbuild.
the class MemoizingEvaluatorTest method checkNotComparableNotPruned.
private void checkNotComparableNotPruned(boolean hasEvent) throws Exception {
initializeTester();
SkyKey parent = GraphTester.toSkyKey("parent");
SkyKey child = GraphTester.toSkyKey("child");
NotComparableStringValue notComparableString = new NotComparableStringValue("not comparable");
if (hasEvent) {
tester.getOrCreate(child).setConstantValue(notComparableString).setWarning("shmoop");
} else {
tester.getOrCreate(child).setConstantValue(notComparableString);
}
final AtomicInteger parentEvaluated = new AtomicInteger();
final String val = "some val";
tester.getOrCreate(parent).addDependency(child).setComputedValue(new ValueComputer() {
@Override
public SkyValue compute(Map<SkyKey, SkyValue> deps, Environment env) throws InterruptedException {
parentEvaluated.incrementAndGet();
return new StringValue(val);
}
});
assertStringValue(val, tester.evalAndGet(/*keepGoing=*/
false, parent));
assertThat(parentEvaluated.get()).isEqualTo(1);
if (hasEvent) {
assertContainsEvent(eventCollector, "shmoop");
} else {
assertEventCount(0, eventCollector);
}
tester.resetPlayedEvents();
tester.getOrCreate(child, /*markAsModified=*/
true);
tester.invalidate();
assertStringValue(val, tester.evalAndGet(/*keepGoing=*/
false, parent));
assertThat(parentEvaluated.get()).isEqualTo(2);
if (hasEvent) {
assertContainsEvent(eventCollector, "shmoop");
} else {
assertEventCount(0, eventCollector);
}
}
use of com.google.devtools.build.skyframe.SkyFunction.Environment in project bazel by bazelbuild.
the class MemoizingEvaluatorTest method cachedErrorCausesRestart.
/**
* Tests that a race between a node being marked clean and another node requesting it is benign.
* Here, we first evaluate errorKey, depending on invalidatedKey. Then we invalidate
* invalidatedKey (without actually changing it) and evaluate errorKey and topKey together.
* Through forced synchronization, we make sure that the following sequence of events happens:
*
* <ol>
* <li>topKey requests errorKey;
* <li>errorKey is marked clean;
* <li>topKey finishes its first evaluation and registers its deps;
* <li>topKey restarts, since it sees that its only dep, errorKey, is done;
* <li>topKey sees the error thrown by errorKey and throws the error, shutting down the
* threadpool;
* </ol>
*/
@Test
public void cachedErrorCausesRestart() throws Exception {
// TrackingProgressReceiver does unnecessary examination of node values.
initializeTester(new TrackingProgressReceiver() {
@Override
public void evaluated(SkyKey skyKey, Supplier<SkyValue> skyValueSupplier, EvaluationState state) {
evaluated.add(skyKey);
}
});
final SkyKey errorKey = GraphTester.toSkyKey("error");
SkyKey invalidatedKey = GraphTester.toSkyKey("invalidated");
final SkyKey topKey = GraphTester.toSkyKey("top");
tester.getOrCreate(errorKey).addDependency(invalidatedKey).setHasError(true);
tester.getOrCreate(invalidatedKey).setConstantValue(new StringValue("constant"));
final CountDownLatch topSecondEval = new CountDownLatch(2);
final CountDownLatch topRequestedError = new CountDownLatch(1);
final CountDownLatch errorMarkedClean = new CountDownLatch(1);
injectGraphListenerForTesting(new Listener() {
@Override
public void accept(SkyKey key, EventType type, Order order, Object context) {
if (errorKey.equals(key) && type == EventType.MARK_CLEAN) {
if (order == Order.BEFORE) {
TrackingAwaiter.INSTANCE.awaitLatchAndTrackExceptions(topRequestedError, "top didn't request");
} else {
errorMarkedClean.countDown();
TrackingAwaiter.INSTANCE.awaitLatchAndTrackExceptions(topSecondEval, "top didn't restart");
// Make sure that the other thread notices the error and interrupts this thread.
try {
Thread.sleep(TestUtils.WAIT_TIMEOUT_MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
}, /*deterministic=*/
false);
EvaluationResult<StringValue> result = tester.eval(/*keepGoing=*/
false, errorKey);
assertThatEvaluationResult(result).hasError();
assertThatEvaluationResult(result).hasErrorEntryForKeyThat(errorKey).hasExceptionThat().isNotNull();
tester.getOrCreate(topKey).setBuilder(new SkyFunction() {
@Nullable
@Override
public SkyValue compute(SkyKey skyKey, Environment env) throws SkyFunctionException, InterruptedException {
topSecondEval.countDown();
env.getValue(errorKey);
topRequestedError.countDown();
assertThat(env.valuesMissing()).isTrue();
TrackingAwaiter.INSTANCE.awaitLatchAndTrackExceptions(errorMarkedClean, "error not marked clean");
return null;
}
@Nullable
@Override
public String extractTag(SkyKey skyKey) {
return null;
}
});
tester.getOrCreate(invalidatedKey, /*markAsModified=*/
true);
tester.invalidate();
EvaluationResult<StringValue> result2 = tester.eval(/*keepGoing=*/
false, errorKey, topKey);
assertThatEvaluationResult(result2).hasError();
assertThatEvaluationResult(result2).hasErrorEntryForKeyThat(errorKey).hasExceptionThat().isNotNull();
assertThatEvaluationResult(result2).hasErrorEntryForKeyThat(topKey).hasExceptionThat().isNotNull();
assertThatEvaluationResult(result2).hasErrorEntryForKeyThat(topKey).rootCauseOfExceptionIs(errorKey);
}
use of com.google.devtools.build.skyframe.SkyFunction.Environment in project bazel by bazelbuild.
the class MemoizingEvaluatorTest method nodeInvalidatedThenDoubleCycle.
@Test
public void nodeInvalidatedThenDoubleCycle() throws InterruptedException {
makeGraphDeterministic();
// When topKey depends on depKey, and both are top-level nodes in the graph,
final SkyKey topKey = skyKey("bKey");
final SkyKey depKey = skyKey("aKey");
tester.getOrCreate(topKey).addDependency(depKey).setConstantValue(new StringValue("a"));
tester.getOrCreate(depKey).setConstantValue(new StringValue("b"));
// Then evaluation is as expected.
EvaluationResult<StringValue> result1 = tester.eval(/*keepGoing=*/
true, topKey, depKey);
assertThatEvaluationResult(result1).hasEntryThat(topKey).isEqualTo(new StringValue("a"));
assertThatEvaluationResult(result1).hasEntryThat(depKey).isEqualTo(new StringValue("b"));
assertThatEvaluationResult(result1).hasNoError();
// When both nodes acquire self-edges, with topKey still also depending on depKey, in the same
// group,
tester.getOrCreate(depKey, /*markAsModified=*/
true).addDependency(depKey);
tester.getOrCreate(topKey, /*markAsModified=*/
true).setConstantValue(null).removeDependency(depKey).setBuilder(new SkyFunction() {
@Nullable
@Override
public SkyValue compute(SkyKey skyKey, Environment env) throws SkyFunctionException, InterruptedException {
env.getValues(ImmutableList.of(topKey, depKey));
assertThat(env.valuesMissing()).isTrue();
return null;
}
@Nullable
@Override
public String extractTag(SkyKey skyKey) {
return null;
}
});
tester.invalidate();
// Then evaluation is as expected -- topKey has removed its dep on depKey (since depKey was not
// done when topKey found its cycle), and both topKey and depKey have cycles.
EvaluationResult<StringValue> result2 = tester.eval(/*keepGoing=*/
true, topKey, depKey);
if (cyclesDetected()) {
assertThatEvaluationResult(result2).hasErrorEntryForKeyThat(topKey).hasCycleInfoThat().containsExactly(new CycleInfo(ImmutableList.of(topKey)));
assertThatEvaluationResult(result2).hasDirectDepsInGraphThat(topKey).containsExactly(topKey);
assertThatEvaluationResult(result2).hasErrorEntryForKeyThat(depKey).hasCycleInfoThat().containsExactly(new CycleInfo(ImmutableList.of(depKey)));
} else {
assertThatEvaluationResult(result2).hasErrorEntryForKeyThat(topKey).hasCycleInfoThat().hasSize(1);
assertThatEvaluationResult(result2).hasErrorEntryForKeyThat(depKey).hasCycleInfoThat().hasSize(1);
}
// When the nodes return to their original, error-free state,
tester.getOrCreate(topKey, /*markAsModified=*/
true).setBuilder(null).addDependency(depKey).setConstantValue(new StringValue("a"));
tester.getOrCreate(depKey, /*markAsModified=*/
true).removeDependency(depKey);
tester.invalidate();
// Then evaluation is as expected.
EvaluationResult<StringValue> result3 = tester.eval(/*keepGoing=*/
true, topKey, depKey);
assertThatEvaluationResult(result3).hasEntryThat(topKey).isEqualTo(new StringValue("a"));
assertThatEvaluationResult(result3).hasEntryThat(depKey).isEqualTo(new StringValue("b"));
assertThatEvaluationResult(result3).hasNoError();
}
use of com.google.devtools.build.skyframe.SkyFunction.Environment in project bazel by bazelbuild.
the class MemoizingEvaluatorTest method raceConditionWithNoKeepGoingErrors_InflightError.
@Test
public void raceConditionWithNoKeepGoingErrors_InflightError() throws Exception {
// Given a graph of two nodes, errorKey and otherErrorKey,
final SkyKey errorKey = GraphTester.toSkyKey("errorKey");
final SkyKey otherErrorKey = GraphTester.toSkyKey("otherErrorKey");
final CountDownLatch errorCommitted = new CountDownLatch(1);
final CountDownLatch otherStarted = new CountDownLatch(1);
final CountDownLatch otherDone = new CountDownLatch(1);
final AtomicInteger numOtherInvocations = new AtomicInteger(0);
final AtomicReference<String> bogusInvocationMessage = new AtomicReference<>(null);
final AtomicReference<String> nonNullValueMessage = new AtomicReference<>(null);
tester.getOrCreate(errorKey).setBuilder(new SkyFunction() {
@Override
public SkyValue compute(SkyKey skyKey, Environment env) throws SkyFunctionException {
// Given that errorKey waits for otherErrorKey to begin evaluation before completing
// its evaluation,
TrackingAwaiter.INSTANCE.awaitLatchAndTrackExceptions(otherStarted, "otherErrorKey's SkyFunction didn't start in time.");
// And given that errorKey throws an error,
throw new GenericFunctionException(new SomeErrorException("error"), Transience.PERSISTENT);
}
@Override
public String extractTag(SkyKey skyKey) {
return null;
}
});
tester.getOrCreate(otherErrorKey).setBuilder(new SkyFunction() {
@Override
public SkyValue compute(SkyKey skyKey, Environment env) throws SkyFunctionException, InterruptedException {
otherStarted.countDown();
int invocations = numOtherInvocations.incrementAndGet();
// And given that otherErrorKey waits for errorKey's error to be committed before
// trying to get errorKey's value,
TrackingAwaiter.INSTANCE.awaitLatchAndTrackExceptions(errorCommitted, "errorKey's error didn't get committed to the graph in time");
try {
SkyValue value = env.getValueOrThrow(errorKey, SomeErrorException.class);
if (value != null) {
nonNullValueMessage.set("bogus non-null value " + value);
}
if (invocations != 1) {
bogusInvocationMessage.set("bogus invocation count: " + invocations);
}
otherDone.countDown();
// And given that otherErrorKey throws an error,
throw new GenericFunctionException(new SomeErrorException("other"), Transience.PERSISTENT);
} catch (SomeErrorException e) {
fail();
return null;
}
}
@Override
public String extractTag(SkyKey skyKey) {
return null;
}
});
injectGraphListenerForTesting(new Listener() {
@Override
public void accept(SkyKey key, EventType type, Order order, Object context) {
if (key.equals(errorKey) && type == EventType.SET_VALUE && order == Order.AFTER) {
errorCommitted.countDown();
TrackingAwaiter.INSTANCE.awaitLatchAndTrackExceptions(otherDone, "otherErrorKey's SkyFunction didn't finish in time.");
}
}
}, /*deterministic=*/
false);
// When the graph is evaluated in noKeepGoing mode,
EvaluationResult<StringValue> result = tester.eval(/*keepGoing=*/
false, errorKey, otherErrorKey);
// Then the result reports that an error occurred because of errorKey,
assertTrue(result.hasError());
assertEquals(errorKey, result.getError().getRootCauseOfException());
// And no value is committed for otherErrorKey,
assertNull(tester.driver.getExistingErrorForTesting(otherErrorKey));
assertNull(tester.driver.getExistingValueForTesting(otherErrorKey));
// And no value was committed for errorKey,
assertNull(nonNullValueMessage.get(), nonNullValueMessage.get());
// And the SkyFunction for otherErrorKey was evaluated exactly once.
assertEquals(numOtherInvocations.get(), 1);
assertNull(bogusInvocationMessage.get(), bogusInvocationMessage.get());
// NB: The SkyFunction for otherErrorKey gets evaluated exactly once--it does not get
// re-evaluated during error bubbling. Why? When otherErrorKey throws, it is always the
// second error encountered, because it waited for errorKey's error to be committed before
// trying to get it. In fail-fast evaluations only the first failing SkyFunction's
// newly-discovered-dependencies are registered. Therefore, there won't be a reverse-dep from
// errorKey to otherErrorKey for the error to bubble through.
}
Aggregations