Cancellation is not propagated to upstream future or publisher in a few cases
See original GitHub issueHttpResponse gets aborted in the following cases:
RequestTimeoutExceptionClosedSessionException
Usually, a cancellation signal gets propagated once HttpResponse is aborted but that’s not true in a few cases.
Following code summarizes when it does and doesn’t…:
It would be nice to know if this behavior is something that requires a fix.
Server.builder()
.requestTimeoutMillis(1000L)
// `Mono` gets cancellation signal if `HttpResponse` is aborted.
.service("/http-response-from-publisher",
(ctx, req) -> HttpResponse.of(
Mono.just(ResponseHeaders.of(200))
.delayElement(Duration.ofMillis(200))
.doOnNext(ignored -> System.out.println("Running..."))
.flatMap(data -> Mono.<HttpData>error(new RuntimeException()))
.retry()))
// `Mono` doesn't get cancellation signal though the returned `HttpResponse` is aborted
// and keeps running forever since `DeferredHttpResponse` doesn't propagate cancellation
// to the `CompletionStage`.
.service("/http-response-from-future",
(ctx, req) -> HttpResponse.from(
Mono.just(ResponseHeaders.of(200))
.delayElement(Duration.ofMillis(200L))
.doOnNext(ignored -> System.out.println("Running..."))
.flatMap(data -> Mono.<HttpData>error(new RuntimeException()))
.retry()
.map(HttpResponse::of)
.toFuture()))
.annotatedService(new Object() {
// `Mono` doesn't get cancellation signal since `AbstractCollectingSubscriber` and
// `ResponseConversionUtil#aggregateFrom` doesn't propagate cancellation to upstream.
@Get("/single-value-publisher")
@ProducesJson
public Mono<String> singleValuePublisher() {
return Mono.just("hello, armeria!")
.delayElement(Duration.ofMillis(200L))
.doOnNext(ignored -> System.out.println("Running..."))
.flatMap(data -> Mono.<String>error(new RuntimeException()))
.retry();
}
// `Flux` gets cancellation signal normally.
@Get("/multi-value-publisher")
@ProducesJsonSequences
public Flux<String> multiValuePublisher() {
return Flux.just("hello,", " armeria!")
.delayElements(Duration.ofMillis(200L))
.doOnNext(ignored -> System.out.println("Running..."))
.repeat();
}
// The `future` gets never cancelled as `AnnotatedService#serve0` doesn't propagate
// cancellation to the upstream future. - `AnnotatedService.java:L312`.
@Get("/future")
public CompletableFuture<String> future() {
final CompletableFuture<String> future = new CompletableFuture<>();
future.whenComplete((ignored, cause) -> {
if (future.isCancelled()) {
System.out.println("Future is cancelled!");
}
});
return future;
}
});
Server.builder()
.requestTimeoutMillis(1000L)
.annotatedService(object {
// The coroutine never gets cancelled and runs forever as `AnnotatedService#serve0` doesn't
// propagate cancellation to `KOTLIN_COROUTINES`.
@Get("/coroutine")
suspend fun coroutine(): String {
try {
while (true) {
delay(200L)
println("Running...)
}
} catch (e: CancellationException) {
println("It never reaches here")
}
return "hello, armeria!"
}
})
Issue Analytics
- State:
- Created 2 years ago
- Comments:5 (5 by maintainers)
Top Results From Across the Web
Combine Publisher does not cancel when subscribed to on a ...
I'm trying to take advantage of Combine's ability to subscribe to an upstream Publisher on a different queue ...
Read more >Cancel Combine Future - Using Swift
I want to use Combine.Future in an API I'm working on. I am trying to figure out how to wire up the cancellation...
Read more >Using Combine
Combine allows for publishers to specify the scheduler used when either receiving from an upstream publisher (in the case of operators), ...
Read more >missionary.core — missionary b.26 - cljdoc
A cancelled publisher cancels its flow, transfers and discards all of its remaining values without backpressure, and all of its current and future...
Read more >Error Handling with Combine and SwiftUI - Peter Friese
In other cases, ignoring the error is not an option. ... Some errors require the user's action - for example if saving a...
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found

In most cases, we should use
CancelledSubscriptionExceptionfor consistency. However,HttpResponse.from(mono.toFuture())is a bit different. Because we can not propagate a cancellation to upstream usingfuture.completeExceptionally(CancelledSubscriptionException.get()). https://github.com/reactor/reactor-core/blob/main/reactor-core/src/main/java/reactor/core/publisher/MonoToCompletableFuture.java#L38-L48It should be nice to check that a future is
MonoToCompletableFutureand callfuture.cancel()instead offuture.completeExceptionally(ex).Okay, I will try to work on that.
The other case we should have in mind is
AnnotatedServicereturningCompletionStage. If a user want to add a callback for cancellation, they will do…:But
future.isCancelled()will evaluate tofalseif the future complete exceptionally withCancelledSubscriptionException. The same goes forKOTLIN_COROUTINESI guess (not sure).Maybe it’s an overkill, though 😅.