Skip to content

Future.await timeout/interrupt poisons the Future for all consumers #3273

Description

@pivovarit

Problem

FutureImpl._await calls tryComplete() on timeout and interrupt, permanently marking the shared Future as failed for all consumers - even though the underlying computation may still succeed.

Timeout poisoning

If thread T1 calls f.await(1, SECONDS) and T2 calls f.await(10, SECONDS), T1's shorter timeout permanently marks the Future as failed with TimeoutException for ALL consumers, including T2. The actual computation result is discarded.

// FutureImpl.java, _await -> block()
if (System.nanoTime() - start > duration) {
    tryComplete(Try.failure(new TimeoutException(...)));
}

Interrupt poisoning

Same pattern: an interrupt to any awaiting thread calls tryComplete(Try.failure(new ExecutionException(new InterruptedException()))), permanently failing the Future for all consumers.

if (waitingThread.isInterrupted()) {
    tryComplete(Try.failure(new ExecutionException(new InterruptedException())));
}

Expected behavior

Timeout and interrupt are local to the waiting thread - they mean "I don't want to wait anymore", not "the Future has failed." _await should return without calling tryComplete(), leaving the Future in its current state.

Callers can use isCompleted() to distinguish between completion and timeout.

Breaking change

This is a semver-major change. The current (buggy) behavior is documented in Future.await's Javadoc and existing code may rely on future.await(timeout).isFailure() returning true with a TimeoutException cause to detect timeouts. After the fix, callers would need to use !future.await(timeout).isCompleted() instead.

Fix

In FutureImpl._await's block() method, replace both tryComplete(...) calls (timeout + interrupt) with return true to exit the blocking loop without poisoning the Future.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions