Search in sources :

Example 1 with DelegatingEventHandler

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());
}
Also used : Order(com.google.devtools.build.skyframe.NotifyingHelper.Order) Listener(com.google.devtools.build.skyframe.NotifyingHelper.Listener) EventType(com.google.devtools.build.skyframe.NotifyingHelper.EventType) ErrorInfoSubjectFactory.assertThatErrorInfo(com.google.devtools.build.skyframe.ErrorInfoSubjectFactory.assertThatErrorInfo) CountDownLatch(java.util.concurrent.CountDownLatch) AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) DelegatingEventHandler(com.google.devtools.build.lib.events.DelegatingEventHandler) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) Environment(com.google.devtools.build.skyframe.SkyFunction.Environment) MoreAsserts.assertContainsEvent(com.google.devtools.build.lib.testutil.MoreAsserts.assertContainsEvent) Event(com.google.devtools.build.lib.events.Event) NotComparableStringValue(com.google.devtools.build.skyframe.GraphTester.NotComparableStringValue) StringValue(com.google.devtools.build.skyframe.GraphTester.StringValue)

Aggregations

DelegatingEventHandler (com.google.devtools.build.lib.events.DelegatingEventHandler)1 Event (com.google.devtools.build.lib.events.Event)1 MoreAsserts.assertContainsEvent (com.google.devtools.build.lib.testutil.MoreAsserts.assertContainsEvent)1 ErrorInfoSubjectFactory.assertThatErrorInfo (com.google.devtools.build.skyframe.ErrorInfoSubjectFactory.assertThatErrorInfo)1 NotComparableStringValue (com.google.devtools.build.skyframe.GraphTester.NotComparableStringValue)1 StringValue (com.google.devtools.build.skyframe.GraphTester.StringValue)1 EventType (com.google.devtools.build.skyframe.NotifyingHelper.EventType)1 Listener (com.google.devtools.build.skyframe.NotifyingHelper.Listener)1 Order (com.google.devtools.build.skyframe.NotifyingHelper.Order)1 Environment (com.google.devtools.build.skyframe.SkyFunction.Environment)1 CountDownLatch (java.util.concurrent.CountDownLatch)1 AtomicBoolean (java.util.concurrent.atomic.AtomicBoolean)1 AtomicInteger (java.util.concurrent.atomic.AtomicInteger)1