`PhantomReachableExecutorService` is never cleaned up because its Cleaner action captures the wrapper
维护者通常 1 天内回复
还没有人认领这个 Issue。
评估
调研方向
从 PhantomReachableExecutorService 以及 issue 中展示的 closeWhenPhantomReachable 注册开始。检查 action 如何捕获 delegate,然后查找有关 executor 清理和弱引用的现有测试。当 wrapper 变得不可达后能够被回收,并且其 executor 能够关闭,即表示完成;建议的修复尚未编译或运行。
由索引模型根据 Issue 内容生成。
描述
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(...).
- 主要语言
- Kotlin
- 星标
- 1.6k
- 派生
- 275
- 平均合并
- 13 小时 3 分钟
- 30 天内合并 PR
- 174
环境准备
在浏览器里用你自己的 GitHub 账号启动这个项目的开发容器。
- 没有 Dockerfile 或 Docker Compose 文件
- 没有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
openai/openai-java 的其他 Issue
-
HttpRequest.url() logs spaces in path segments as literal plus signs可能已有人在做 @sylvesterkaczmarek 于 78 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 78/100
openai/openai-java#886 · 2 条评论 ·
维护者通常 1 天内回复
-
Make diarized transcription duration and task optional可能已有人在做 @wskr00 于 109 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 68/100
openai/openai-java#802 · 2 条评论 · 3 个 reaction ·
维护者通常 1 天内回复
-
难度 4/5 3-5 天 新手友好度 30/100
openai/openai-java#1175 ·
维护者通常 1 天内回复
-
难度 3/5 半天 新手友好度 42/100
openai/openai-java#1160 ·
维护者通常 1 天内回复
-
难度 5/5 一周以上 新手友好度 35/100
openai/openai-java#956 ·
维护者通常 1 天内回复
查看 openai/openai-java 的全部 Issue
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 72/100
openwallet-foundation/multipaz#2161 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 65/100
kushagrasinghx/BitChord#1498 ·
维护者通常 1 天内回复
-
bug
难度 2/5 1-3 小时 新手友好度 70/100
AChep/keyguard-app#1665 ·
维护者通常 1 天内回复
-
needs-triage
难度 2/5 1-3 小时 新手友好度 67/100
neutraltone/settee#157 ·
维护者通常 1 天内回复
-
bug 🐞 python 🐍
难度 2/5 1-3 小时 新手友好度 83/100
valkey-io/valkey-glide#7560 ·
维护者通常 1 天内回复