AsyncStreamResponse can hang when the subscriber executor rejects work
Nobody has claimed this yet.
- Dominant language
- Kotlin
- Stars
- 1.5k
- Forks
- 264
- Avg merge
- 9h 46m
- Merged PRs (30d)
- 96
Description
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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with AsyncStreamResponse.toAsync() and the focused AsyncStreamResponseTest reproduction described in the issue. Trace the ignored whenCompleteAsync result when the subscriber executor rejects work, then verify that onCompleteFuture() settles exceptionally, the underlying StreamResponse is closed once, and the handler is not invoked on a fallback thread.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, kotlin
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 74/100