// Copyright 2014 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.truth.Truth.assertThat; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.Uninterruptibles; import com.google.devtools.build.lib.testutil.TestUtils; import com.google.devtools.build.lib.util.Pair; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * Safely await {@link CountDownLatch}es in tests, storing any exceptions that happen. Callers * should call {@link #assertNoErrors} at the end of each test method, either manually or using an * {@code @After} hook. */ public class TrackingAwaiter { public static final TrackingAwaiter INSTANCE = new TrackingAwaiter(); private TrackingAwaiter() {} private final ConcurrentLinkedQueue<Pair<String, Throwable>> exceptionsThrown = new ConcurrentLinkedQueue<>(); /** * This method fixes a race condition with simply calling {@link CountDownLatch#await}. If this * thread is interrupted before {@code latch.await} is called, then {@code latch.await} will throw * an {@link InterruptedException} without checking the value of the latch at all. This leads to a * race condition in which this thread will throw an InterruptedException if it is slow calling * {@code latch.await}, but it will succeed normally otherwise. * * <p>To avoid this, we wait for the latch uninterruptibly. In the end, if the latch has in fact * been released, we do nothing, although the interrupted bit is set, so that the caller can * decide to throw an InterruptedException if it wants to. If the latch was not released, then * this was not a race condition, but an honest-to-goodness interrupt, and we propagate the * exception onward. */ private static void waitAndMaybeThrowInterrupt(CountDownLatch latch, String errorMessage) throws InterruptedException { if (Uninterruptibles.awaitUninterruptibly(latch, TestUtils.WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { // Latch was released. We can ignore the interrupt state. return; } if (!Thread.currentThread().isInterrupted()) { // Nobody interrupted us, but latch wasn't released. Failure. throw new AssertionError(errorMessage); } else { // We were interrupted before the latch was released. Propagate this interruption. throw new InterruptedException(); } } /** Threadpools can swallow exceptions. Make sure they don't get lost. */ public void awaitLatchAndTrackExceptions(CountDownLatch latch, String errorMessage) { try { waitAndMaybeThrowInterrupt(latch, errorMessage); } catch (Throwable e) { // We would expect e to be InterruptedException or AssertionError, but we leave it open so // that any throwable gets recorded. exceptionsThrown.add(Pair.of(errorMessage, e)); // Caller will assert exceptionsThrown is empty at end of test and fail, even if this is // swallowed. Throwables.propagate(e); } } /** Allow arbitrary errors to be recorded here for later throwing. */ public void injectExceptionAndMessage(Throwable throwable, String message) { exceptionsThrown.add(Pair.of(message, throwable)); } public void assertNoErrors() { List<Pair<String, Throwable>> thisEvalExceptionsThrown = ImmutableList.copyOf(exceptionsThrown); exceptionsThrown.clear(); assertThat(thisEvalExceptionsThrown).isEmpty(); } }