openai / openai/openai-java

fix: closing a withOptions client closes shared resources

Open
#850 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

withOptions() returns a derived client/view, but the derived client shares several resources with the original client and then treats them as independently owned. Closing the derived client can therefore shut down the original client's HTTP transport, streaming executor, and sleeper.

The public API documents withOptions() as returning a view and says that the original service is not modified, so callers reasonably expect the original client to remain usable after closing a short-lived derived client.

Reproduction

A minimal reproduction can use a custom HttpClient whose close() records a flag:

val transport = RecordingHttpClient()
val original =
    ClientOptions.builder()
        .httpClient(transport)
        .apiKey(test)
        .build()
val derived = original.toBuilder().build()

derived.close()

// Expected: transport is still open and original can execute requests.
// Actual: transport.close() has been called, and the original now uses a closed transport.
check(!transport.closed)

The same lifecycle is exposed through the public clients:

OpenAIClient original = OpenAIOkHttpClient.builder().apiKey(test).build();
OpenAIClient derived = original.withOptions(options -> options.baseUrl(https://example.test));
derived.close();

// The original should still be usable, but its shared transport/executor has been closed.
original.models().list();

Code reference

  • openai-java-core/src/main/kotlin/com/openai/core/ClientOptions.kt:222-228 copies originalHttpClient, streamHandlerExecutor, and sleeper into a new builder.
  • openai-java-core/src/main/kotlin/com/openai/core/ClientOptions.kt:257-259 wraps the supplied HTTP client, and the class documentation says the options object owns and closes it.
  • openai-java-core/src/main/kotlin/com/openai/core/ClientOptions.kt:773-777 closes the HTTP client, executor, and sleeper unconditionally.
  • openai-java-core/src/main/kotlin/com/openai/client/OpenAIClientImpl.kt:146-147 and OpenAIClientAsyncImpl.kt:166-167 implement withOptions() by building a new client from clientOptions.toBuilder().
  • openai-java-core/src/main/kotlin/com/openai/client/OpenAIClient.kt:61-66 and OpenAIClientAsync.kt:61-66 document the result as a view whose original service is not modified.

Expected behavior

Closing a derived client should not invalidate the original client or any resources still owned by it. Resource ownership should either be shared through a lifecycle holder/reference count, or derived views should be non-owning and leave shared resources to the original owner.

This should be covered for sync and async clients, including closing the derived client before the original and closing the original before the derived.

Why it matters

This can cause intermittent failures in applications that create per-request clients with withOptions() and close them promptly: later requests through the long-lived original client may fail because the HTTP transport is closed, the streaming executor rejects tasks, or the sleeper is no longer usable. It also makes the documented view semantics unsafe.

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 ClientOptions.kt, especially the builder copying at lines 222-228, ownership setup at 257-259, and close logic at 773-777; then trace withOptions() in OpenAIClientImpl.kt and OpenAIClientAsyncImpl.kt. Verify that derived and original clients remain usable in both closing orders for sync and async clients, including transport, executor, and sleeper lifecycle.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, kotlin
Domain
api
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.