Skip to content

Mono.fromFuture cancellation should ignore a wrapped CancellationException #4415

Open

Description

@chemicL

MonoCompletionStage ignores a future failure that arrives after the Mono was cancelled only when it is exactly a CancellationException. However, sometimes a CompletionException(CancellationException) can be delivered instead and is handled as Operators.onErrorDropped. It should also be ignored.

java.net.http.HttpClient.sendAsync is an example of this. Its MinimalFuture.cancel() aborts the exchange, and the returned future fails with CompletionException(CancellationException: Request cancelled). So cancelling Mono.fromFuture(httpClient.sendAsync(...)) before the response arrives logs Operator called default onErrorDropped at ERROR, with a stack trace, for a cancellation the Mono requested itself.

Reproducer (no HTTP needed):

Hooks.onErrorDropped(e -> System.out.println("DROPPED: " + e));

// A dependent stage, with suppressCancel
CompletableFuture<String> source = new CompletableFuture<>();
Mono.fromFuture(source.thenApply(Function.identity()), true).subscribe().dispose();
source.cancel(true);
// DROPPED: java.util.concurrent.CompletionException: java.util.concurrent.CancellationException

// A future whose cancel() propagates upstream first, as java.net.http's MinimalFuture does
CompletableFuture<String> upstream = new CompletableFuture<>();
CompletableFuture<String> future = new CompletableFuture<>() {
    @Override public boolean cancel(boolean mayInterrupt) {
        upstream.cancel(mayInterrupt);
        return super.cancel(mayInterrupt);
    }
};
upstream.whenComplete((v, e) -> { if (e != null) future.completeExceptionally(new CompletionException(e)); });
Mono.fromFuture(future).subscribe().dispose();
// DROPPED: java.util.concurrent.CompletionException: java.util.concurrent.CancellationException

Expected: no dropped error.

Potential fix: unwrap before the check in the cancelled branch:

Throwable cause = e instanceof CompletionException && e.getCause() != null ? e.getCause() : e;

and ignore it when cause is a CancellationException.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions