use of com.google.devtools.build.lib.events.DelegatingEventHandler in project bazel by bazelbuild.
the class MemoizingEvaluatorTest method dirtyChildEnqueuesParentDuringCheckDependencies.
/**
* We are checking here that we are resilient to a race condition in which a value that is
* checking its children for dirtiness is signaled by all of its children, putting it in a ready
* state, before the thread has terminated. Optionally, one of its children may throw an error,
* shutting down the threadpool. The essential race is that a child about to throw signals its
* parent and the parent's builder restarts itself before the exception is thrown. Here, the
* signaling happens while dirty dependencies are being checked. We control the timing by blocking
* "top"'s registering itself on its deps.
*/
private void dirtyChildEnqueuesParentDuringCheckDependencies(final boolean throwError) throws Exception {
// Value to be built. It will be signaled to rebuild before it has finished checking its deps.
final SkyKey top = GraphTester.toSkyKey("top");
// Dep that blocks before it acknowledges being added as a dep by top, so the firstKey value has
// time to signal top. (Importantly its key is alphabetically after 'firstKey').
final SkyKey slowAddingDep = GraphTester.toSkyKey("slowDep");
// Don't perform any blocking on the first build.
final AtomicBoolean delayTopSignaling = new AtomicBoolean(false);
final CountDownLatch topSignaled = new CountDownLatch(1);
final CountDownLatch topRestartedBuild = new CountDownLatch(1);
injectGraphListenerForTesting(new Listener() {
@Override
public void accept(SkyKey key, EventType type, Order order, @Nullable Object context) {
if (!delayTopSignaling.get()) {
return;
}
if (key.equals(top) && type == EventType.SIGNAL && order == Order.AFTER) {
// top is signaled by firstKey (since slowAddingDep is blocking), so slowAddingDep
// is now free to acknowledge top as a parent.
topSignaled.countDown();
return;
}
if (key.equals(slowAddingDep) && type == EventType.ADD_REVERSE_DEP && top.equals(context) && order == Order.BEFORE) {
// If top is trying to declare a dep on slowAddingDep, wait until firstKey has
// signaled top. Then this add dep will return DONE and top will be signaled,
// making it ready, so it will be enqueued.
TrackingAwaiter.INSTANCE.awaitLatchAndTrackExceptions(topSignaled, "first key didn't signal top in time");
}
}
}, /*deterministic=*/
true);
// Value that is modified on the second build. Its thread won't finish until it signals top,
// which will wait for the signal before it enqueues its next dep. We prevent the thread from
// finishing by having the listener to which it reports its warning block until top's builder
// starts.
final SkyKey firstKey = GraphTester.skyKey("first");
tester.set(firstKey, new StringValue("biding"));
tester.set(slowAddingDep, new StringValue("dep"));
final AtomicInteger numTopInvocations = new AtomicInteger(0);
tester.getOrCreate(top).setBuilder(new NoExtractorFunction() {
@Override
public SkyValue compute(SkyKey key, SkyFunction.Environment env) throws InterruptedException {
numTopInvocations.incrementAndGet();
if (delayTopSignaling.get()) {
// The reporter will be given firstKey's warning to emit when it is requested as a dep
// below, if firstKey is already built, so we release the reporter's latch beforehand.
topRestartedBuild.countDown();
}
// top's builder just requests both deps in a group.
env.getValuesOrThrow(ImmutableList.of(firstKey, slowAddingDep), SomeErrorException.class);
return env.valuesMissing() ? null : new StringValue("top");
}
});
reporter = new DelegatingEventHandler(reporter) {
@Override
public void handle(Event e) {
super.handle(e);
if (e.getKind() == EventKind.WARNING) {
if (!throwError) {
TrackingAwaiter.INSTANCE.awaitLatchAndTrackExceptions(topRestartedBuild, "top's builder did not start in time");
}
}
}
};
// First build : just prime the graph.
EvaluationResult<StringValue> result = tester.eval(/*keepGoing=*/
false, top);
assertFalse(result.hasError());
assertEquals(new StringValue("top"), result.get(top));
assertEquals(2, numTopInvocations.get());
// Now dirty the graph, and maybe have firstKey throw an error.
String warningText = "warning text";
tester.getOrCreate(firstKey, /*markAsModified=*/
true).setHasError(throwError).setWarning(warningText);
tester.invalidate();
delayTopSignaling.set(true);
result = tester.eval(/*keepGoing=*/
false, top);
if (throwError) {
assertTrue(result.hasError());
// No successfully evaluated values.
assertThat(result.keyNames()).isEmpty();
ErrorInfo errorInfo = result.getError(top);
assertThat(errorInfo.getRootCauses()).containsExactly(firstKey);
assertEquals("on the incremental build, top's builder should have only been used in error " + "bubbling", 3, numTopInvocations.get());
} else {
assertEquals(new StringValue("top"), result.get(top));
assertFalse(result.hasError());
assertEquals("on the incremental build, top's builder should have only been executed once in " + "normal evaluation", 3, numTopInvocations.get());
}
assertContainsEvent(eventCollector, warningText);
assertEquals(0, topSignaled.getCount());
assertEquals(0, topRestartedBuild.getCount());
}
Aggregations