Two factors are important for below algorithm to work efficiently
- The blocking http call is sensitive to interrupt ,so as soon as it gets one ,it terminates with interruption and throws InterruptedException
- The shutdownNow from executors will cancel execution of waiting threads,interrupts the executing thread as mentioned in doc
Attempts to stop all actively executing tasks, halts the processing of waiting tasks, and returns a list of the tasks that were awaiting execution.
This method does not wait for actively executing tasks to terminate. Use awaitTermination to do that.
There are no guarantees beyond best-effort attempts to stop processing actively executing tasks. For example, typical implementations will cancel via Thread.interrupt, so any task that fails to respond to interrupts may never terminate.
We check on interrupt status before making the blocking call so that we don't miss on the already interrupted status
import org.apache.commons.lang3.RandomUtils;
...
public static void main(String[] args) throws InterruptedException, ExecutionException {
Predicate<CompletableFuture> isCompletedExceptionally = CompletableFuture::isCompletedExceptionally;
Predicate<CompletableFuture> isNotCompletedExceptionally = isCompletedExceptionally.negate();
Stream<String> streamIds = IntStream.rangeClosed(1, 1000).mapToObj(String::valueOf);
CountDownLatch latch = new CountDownLatch(1);//only one result to succeed
ExecutorService executor = Executors.newFixedThreadPool(4);
List<CompletableFuture<String>> stringFutureResults = streamIds
.map(id -> CompletableFuture.supplyAsync(() -> hitEndpoint(id, latch), executor))
.collect(Collectors.toList());
latch.await();//waits for atleast one api call to succeed
executor.shutdownNow();//This will stop accepting new tasks and interrupts already executing tasks
Optional<CompletableFuture<String>> resultFuture = stringFutureResults.stream()
.filter(isNotCompletedExceptionally)
//.filter(CompletableFuture::isDone) // thread executing hitEndpoint never got a chance to return
.filter(future -> future.join() != null)
.findFirst();
System.out.println(resultFuture.get().get());
}
private static String hitEndpoint(String id, CountDownLatch latch) {
try {
if (latch.getCount()>0 && !Thread.currentThread().isInterrupted()){
//api call
Thread.sleep(RandomUtils.nextLong(100,900));//Generally a http call is sensitive to interrupt
latch.countDown();
return "Api Response"+id;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
Thread.currentThread().interrupt();//Its response is not required
return null;
}