`PhantomReachableExecutorService` is never cleaned up because its Cleaner action captures the wrapper

Aperta Adatta ai principianti
#1,337 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
2/5
Tempo stimato
1-3 ore
Idoneità per principianti
78/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
java, kotlin
Ambito
backend

Direzione di ricerca

Inizia con PhantomReachableExecutorService e la registrazione closeWhenPhantomReachable mostrata nell’issue. Verifica come l’azione acquisisce il delegate, quindi cerca i test esistenti sulla pulizia dell’executor e sui riferimenti deboli. Il lavoro è completato quando il wrapper può essere raccolto e il suo executor arrestato dopo che il wrapper diventa irraggiungibile; la correzione suggerita non è stata compilata né eseguita.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Problem

PhantomReachableExecutorService is meant to shut down its executor once the wrapper is only phantom reachable.
It can't, because the action registered with the Cleaner holds a strong reference to the wrapper:

internal class PhantomReachableExecutorService(private val executorService: ExecutorService) :
    ExecutorService {
    init {
        closeWhenPhantomReachable(this) { executorService.shutdown() }
    }

Here executorService compiles to a read of the private property through this.
The generated lambda has a this$0 field, as javap -p -c 'com.openai.core.PhantomReachableExecutorService$1' shows on 4.49.0, 4.80.0 and 4.99.0:

final com.openai.core.PhantomReachableExecutorService this$0;
...
invokestatic  com/openai/core/PhantomReachableExecutorService.access$getExecutorService$p
invokeinterface java/util/concurrent/ExecutorService.shutdown:()V

The Cleaner holds the action strongly, so the wrapper and its executor stay reachable for the life of the JVM.

Impact

ClientOptions.Builder.build() creates this wrapper by default, and streamHandlerExecutor(...) applies it to any ExecutorService.
So every client strands one wrapper and one ThreadPoolExecutor, whether or not it ever streams.
Calling close() shuts the pool down but doesn't release it.
This matters to applications that build clients repeatedly, for example one per tenant API key.

Reproduction

Build 2,000 sync and async client pairs, discard them, then take a class histogram with jcmd <pid> GC.class_histogram, which forces a full GC.

Client setup Retained PhantomReachableExecutorService
Default 4,000
Default, calling close() on each client 4,000
streamHandlerExecutor given a plain Executor 0

Clients, sleepers and HTTP client wrappers were released in all three.

Suggested fix

Make the action capture the delegate rather than the wrapper, for example by registering it from a function that doesn't have this in scope, or by copying the delegate into a local first:

init {
    val delegate = executorService
    closeWhenPhantomReachable(this) { delegate.shutdown() }
}

This suggestion hasn't been compiled or run.
A regression test could register a wrapper, drop it, and assert through a WeakReference that it's collected after System.gc(), or that the delegate's isShutdown becomes true.

Workaround

Pass an Executor that isn't an ExecutorService to ClientOptions.Builder.streamHandlerExecutor(...).

Lingua principale
Kotlin
Stelle
1.6k
Fork
275
Merge medio
13h 3m
PR unite (30g)
174

Preparare l'ambiente

Apri in Codespaces

Avvia il container di sviluppo del progetto nel browser, con il tuo account GitHub.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di openai/openai-java

Tutte le issue di openai/openai-java

Issue simili

Altre issue su Kotlin

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.