Skip to content

AsyncStreamResponse can hang when the subscriber executor rejects work #973

Description

@sylvesterkaczmarek

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.

Activity

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions