Description
AsyncStreamResponse.subscribe(handler, executor) can leave onCompleteFuture() permanently pending when the supplied executor rejects the stream-delivery task.
CompletableFuture.whenCompleteAsync(..., executor) does not necessarily throw from the subscribe() call. When the source future is already completed and executor.execute(...) rejects, Java returns an exceptionally completed dependent future. AsyncStreamResponse.toAsync() currently discards that dependent future, so the rejection is never observed.
The result is that the handler never runs, onCompleteFuture() remains pending, and the underlying StreamResponse is not closed by the subscription path.
Reproduction
On current main at 1992a4a, add this focused case to AsyncStreamResponseTest:
val future = CompletableFuture.completedFuture(streamResponse)
val asyncStreamResponse = future.toAsync(executor)
val rejected = RejectedExecutionException("executor rejected")
val rejectingExecutor = Executor { throw rejected }
asyncStreamResponse.subscribe(handler, rejectingExecutor)
val completionError = catchThrowable {
asyncStreamResponse.onCompleteFuture().get(100, TimeUnit.MILLISECONDS)
}
assertThat(completionError)
.isInstanceOf(ExecutionException::class.java)
.hasCause(rejected)
verify(streamResponse, times(1)).close()
Current result: the assertion receives TimeoutException, showing that onCompleteFuture() never settles.
Root cause
toAsync() calls:
this@toAsync.whenCompleteAsync({ ... }, executor)
and ignores the returned CompletableFuture. Executor-dispatch failures therefore bypass the callback body and every existing completion/cleanup path.
Expected behavior
If dispatch to the subscriber executor fails, the asynchronous stream should settle exceptionally with the executor failure and close its underlying response. The handler should not be invoked on a different thread as a fallback.
Description
AsyncStreamResponse.subscribe(handler, executor)can leaveonCompleteFuture()permanently pending when the supplied executor rejects the stream-delivery task.CompletableFuture.whenCompleteAsync(..., executor)does not necessarily throw from thesubscribe()call. When the source future is already completed andexecutor.execute(...)rejects, Java returns an exceptionally completed dependent future.AsyncStreamResponse.toAsync()currently discards that dependent future, so the rejection is never observed.The result is that the handler never runs,
onCompleteFuture()remains pending, and the underlyingStreamResponseis not closed by the subscription path.Reproduction
On current
mainat1992a4a, add this focused case toAsyncStreamResponseTest:Current result: the assertion receives
TimeoutException, showing thatonCompleteFuture()never settles.Root cause
toAsync()calls:and ignores the returned
CompletableFuture. Executor-dispatch failures therefore bypass the callback body and every existing completion/cleanup path.Expected behavior
If dispatch to the subscriber executor fails, the asynchronous stream should settle exceptionally with the executor failure and close its underlying response. The handler should not be invoked on a different thread as a fallback.