-
Notifications
You must be signed in to change notification settings - Fork 0
[Detail Bug] AppleMaps: resolving multiple autocomplete URLs can starve common thread pool and ignore interruption #67
Description
Detail Bug Report
Summary
- Context:
AppleMapsprovides aresolveCompletionUrlsmethod to fetch details for multiple autocomplete results in parallel. - Bug: The method uses
CompletableFuture.supplyAsyncwithout a dedicated executor, causing blocking HTTP calls to run in theForkJoinPool.commonPool(). - Actual vs. expected: It blocks common pool threads with synchronous I/O and uses
CompletableFuture::joinwhich is uninterruptible; it should use a dedicated I/O executor and support interruption. - Impact: Can lead to "Common Pool Starvation", hanging other parts of the application that rely on the common pool (e.g., parallel streams) and preventing task cancellation.
Code with Bug
List<CompletableFuture<SearchResponse>> futures = results.stream()
.map(result -> CompletableFuture.supplyAsync(() -> gateway.resolveCompletionUrl(result.completionUrl()))) // <-- BUG 🔴 blocking I/O on common pool
.toList();
try {
return futures.stream()
.map(CompletableFuture::join) // <-- BUG 🔴 join is uninterruptible
.toList();Explanation
CompletableFuture.supplyAsync(Supplier) defaults to ForkJoinPool.commonPool(). Here each task executes gateway.resolveCompletionUrl(...), which (in HttpAppleMapsGateway) ultimately performs a synchronous httpClient.send() call. Submitting many results can saturate the common pool with blocked threads, starving other unrelated uses of the common pool and potentially hanging the application.
Additionally, collecting results with CompletableFuture::join blocks without responding to thread interruption, so callers cannot cancel the work promptly during shutdown/timeouts.
Recommended Fix
Use a dedicated executor for these blocking network calls and avoid join() in favor of interruption-aware waiting.
public List<SearchResponse> resolveCompletionUrls(List<AutocompleteResult> results, Executor executor) { // <-- FIX 🟢 accept executor for I/O
Objects.requireNonNull(results, "results");
Objects.requireNonNull(executor, "executor");
List<CompletableFuture<SearchResponse>> futures = results.stream()
.map(result -> CompletableFuture.supplyAsync(
() -> gateway.resolveCompletionUrl(result.completionUrl()),
executor))
.toList();
// ... wait for completion using interruption-aware approach (e.g., get())
}History
This bug was introduced in commit 27841cf. This commit added the AppleMaps facade and the resolveCompletionUrls helper method, which incorrectly utilized the default ForkJoinPool.commonPool() for blocking I/O operations and used uninterruptible join() calls.