openai / openai/openai-java

AsyncStreamResponse can hang when the subscriber executor rejects work

Open
#973 0 comments 0 reactions 0 assignees View on GitHub

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.