Detail Bug Report
https://app.detail.dev/org_befd6425-a158-4e24-9d4d-1e5c08769515/bugs/bug_452fdd44-e694-47ae-adec-98fb74cfd1ea
Summary
- Context:
AppleMaps provides a resolveCompletionUrls method to fetch details for multiple autocomplete results in parallel.
- Bug: The method uses
CompletableFuture.supplyAsync without a dedicated executor, causing blocking HTTP calls to run in the ForkJoinPool.commonPool().
- Actual vs. expected: It blocks common pool threads with synchronous I/O and uses
CompletableFuture::join which 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.
Detail Bug Report
https://app.detail.dev/org_befd6425-a158-4e24-9d4d-1e5c08769515/bugs/bug_452fdd44-e694-47ae-adec-98fb74cfd1ea
Summary
AppleMapsprovides aresolveCompletionUrlsmethod to fetch details for multiple autocomplete results in parallel.CompletableFuture.supplyAsyncwithout a dedicated executor, causing blocking HTTP calls to run in theForkJoinPool.commonPool().CompletableFuture::joinwhich is uninterruptible; it should use a dedicated I/O executor and support interruption.Code with Bug
Explanation
CompletableFuture.supplyAsync(Supplier)defaults toForkJoinPool.commonPool(). Here each task executesgateway.resolveCompletionUrl(...), which (inHttpAppleMapsGateway) ultimately performs a synchronoushttpClient.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::joinblocks 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.History
This bug was introduced in commit 27841cf. This commit added the
AppleMapsfacade and theresolveCompletionUrlshelper method, which incorrectly utilized the defaultForkJoinPool.commonPool()for blocking I/O operations and used uninterruptiblejoin()calls.