ExecutorService.invokeAny returns one successfully completed Callable result and cancels unfinished tasks when it returns.
Java ExecutorService invokeAny: return one successful result from competing tasks
Operational contract
A bounded set of replica lookups can race under a three-second deadline. Failure of one Callable does not prevent another from succeeding. If all fail, invokeAny raises ExecutionException; if no success arrives before the deadline, it raises TimeoutException. The caller owns the executor and its queue limits. Cancellation of losers requests interruption but does not guarantee their network or database work stopped. Use only for safe duplicate reads or operations with explicit idempotency.
Failure case
The east receipt replica throws, and the west replica returns a record. invokeAny returns west's value. If both throw, it raises an error instead of returning null, and unfinished tasks receive cancellation requests.
Java code
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class ReceiptReplicaSelection {
public static String read(ExecutorService executor,
List<Callable<String>> replicaReads)
throws InterruptedException, ExecutionException, TimeoutException {
Objects.requireNonNull(executor);
Objects.requireNonNull(replicaReads);
if (replicaReads.isEmpty() || replicaReads.size() > 3)
throw new IllegalArgumentException("Expected one to three replicas");
return executor.invokeAny(replicaReads, 3, TimeUnit.SECONDS);
}
}Performance and ownership cost
Submitting R calls uses O(R) queue and task bookkeeping, with R capped at three. Concurrent reads may reduce latency but multiply backend load. The timeout bounds waiting for a successful result, not guaranteed completion of cancelled loser cleanup.
Common Mistakes
- Do not confuse the first finished task with the first successful task.
- Do not use competing non-idempotent writes as replicas.
- Do not assume a timeout means every backend task has stopped.
