google/guava · error · RuntimeException

Unexpected interrupt while waiting for future

Error message

Unexpected interrupt while waiting for future

What it means

GcFinalization.await(Future) loops, calling System.gc() and future.get(1, SECONDS) until the future completes or the deadline expires. An InterruptedException is treated as unexpected and rethrown wrapped in a RuntimeException, because the helper assumes the awaiting thread should not be cancelled mid-wait.

Source

Thrown at android/guava-testlib/src/com/google/common/testing/GcFinalization.java:167

  public static void awaitDone(Future<?> future) {
    if (future.isDone()) {
      return;
    }
    long timeoutSeconds = timeoutSeconds();
    long deadline = System.nanoTime() + SECONDS.toNanos(timeoutSeconds);
    do {
      runFinalization();
      if (future.isDone()) {
        return;
      }
      System.gc();
      try {
        future.get(1L, SECONDS);
        return;
      } catch (CancellationException | ExecutionException ok) {
        return;
      } catch (InterruptedException ie) {
        throw new RuntimeException("Unexpected interrupt while waiting for future", ie);
      } catch (TimeoutException tryHarder) {
        /* OK */
      }
    } while (System.nanoTime() - deadline < 0);
    throw formatRuntimeException("Future not done within %d second timeout", timeoutSeconds);
  }

  /**
   * Waits until the given predicate returns true, invoking the garbage collector as necessary to
   * try to ensure that this will happen.
   *
   * @throws RuntimeException if timed out or interrupted while waiting
   */
  public static void awaitDone(FinalizationPredicate predicate) {
    if (predicate.isDone()) {
      return;
    }
    long timeoutSeconds = timeoutSeconds();

View on GitHub (pinned to 94f39958ba)

Solutions

  1. Ensure the thread running the GC finalization await is not interrupted — widen test timeouts or move the call off interruptible threads.
  2. Make the future complete promptly so the 1-second get() returns before any interrupt lands.
  3. If interruption is legitimate for your environment, do not use GcFinalization.await(future); poll the future yourself and handle InterruptedException by restoring interrupt status.
  4. Clear stray interrupt status (Thread.interrupted()) before calling await if prior code may have set it.

Example fix

// before
Thread.interrupted(); // stray status from earlier test
GcFinalization.await(future); // -> RuntimeException: Unexpected interrupt...

// after
Thread.interrupted(); // clear stray status
GcFinalization.await(future);
Defensive patterns

Strategy: try-catch

Validate before calling

// Clear stray interrupt status and widen timeouts before awaiting.
Thread.interrupted(); // clear any leaked status
assert !Thread.currentThread().isInterrupted();
GcFinalization.await(future);

Try / catch

// The helper wraps InterruptedException in RuntimeException; catch only to
// surface harness problems, not to swallow them.
try {
  GcFinalization.await(future);
} catch (RuntimeException e) {
  if (!(e.getCause() instanceof InterruptedException)) throw e;
  Thread.currentThread().interrupt(); // restore and fail loudly
  throw e;
}

Prevention

When it happens

Trigger: Calling GcFinalization.awaitFullGc()/await(future) while the running thread is concurrently interrupted (Thread.interrupt()), e.g. by a test timeout, a shutdown hook, or framework-level cancellation.

Common situations: JUnit/other test timeouts interrupting the test thread; shared executors being shut down during GC-sensitive tests; harnesses that set interrupt status between test cases.

Related errors


AI-assisted analysis of google/guava@94f39958ba (2026-08-13). Data as JSON: /api/errors/b88e9a437cea99f3. Report an issue: GitHub.