Skip to content

[Detail Bug] AppleMaps: resolving multiple autocomplete URLs can starve common thread pool and ignore interruption #67

@detail-app

Description

@detail-app

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions