Last active
March 28, 2025 21:48
-
-
Save jocull/37b1338cac89c6c01c3f937bb5aeb36e to your computer and use it in GitHub Desktop.
Java CompletableFutures - causing a situation that will hang applications
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.codefromjames; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import java.time.Duration; | |
import java.time.Instant; | |
import java.util.List; | |
import java.util.Random; | |
import java.util.concurrent.*; | |
import java.util.stream.IntStream; | |
public class Main { | |
private static final Logger LOGGER = LoggerFactory.getLogger(Main.class); | |
private static final Random RANDOM = new Random(); | |
record Result(String string, Duration duration) { | |
} | |
public static void main(String[] args) { | |
final List<CompletableFuture<Result>> futures1 = IntStream.range(0, 1000) | |
.mapToObj(i -> { | |
final CompletableFuture<Result> f = new CompletableFuture<>(); | |
final Instant start = Instant.now(); | |
getDelayedHelloFuture() | |
.thenApply(s -> { | |
final Instant end = Instant.now(); | |
final Duration duration = Duration.between(start, end); | |
return new Result(s, duration); | |
}) | |
// TODO: The problem is here. | |
// The future we're manually managing won't be completed due to a failing step | |
// and `thenAccept` or `thenCompose` methods will be skipped due to not being | |
// explicitly designed to handle exceptions. | |
// | |
// We can use `whenComplete` or `handle` instead to properly control our | |
// manually managed future. | |
.thenCompose(result -> { | |
f.complete(result); | |
return f; | |
}); | |
// .whenComplete((result, throwable) -> { | |
// if (throwable != null) { | |
// f.completeExceptionally(throwable); | |
// } else { | |
// f.complete(result); | |
// } | |
// }); | |
return f; | |
}) | |
.toList(); | |
CompletableFuture<Void> allOf = CompletableFuture.allOf(futures1.toArray(new CompletableFuture[0])); | |
CompletableFuture<Object> handleStack = allOf.thenAccept(unused -> { | |
LOGGER.info("I accept that this is complete."); | |
}).whenComplete((unused, throwable) -> { | |
LOGGER.info("It's done.", throwable); | |
}).handle((unused, throwable) -> { | |
LOGGER.info("MANY WHELPS. HANDLE IT.", throwable); | |
return null; | |
}); | |
LOGGER.info("_WELL_? We're _waiting_!"); | |
handleStack.join(); | |
} | |
private static CompletableFuture<String> getDelayedHelloFuture() { | |
final Executor delayedExecutor = CompletableFuture.delayedExecutor(RANDOM.nextLong(50, 5000), TimeUnit.MILLISECONDS); | |
return CompletableFuture.supplyAsync(() -> { | |
if (RANDOM.nextDouble() < 0.0001) { // A low chance of failing. But with many operations... anything can happen. | |
throw new RuntimeException("Bad luck exception"); | |
} | |
return "Hello from " + Thread.currentThread().getName() + "!"; | |
}, delayedExecutor) | |
.whenComplete((s, throwable) -> { | |
LOGGER.info("Just telling you about {} / {}", s, throwable != null); | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment