PR:修复 StudioManager.shutdown() 时 Tracing 资源泄漏
一、问题描述
当 StudioManager.shutdown() 被调用时,Tracing 相关资源(包括 TracerRegistry.tracer 及底层的 SdkTracerProvider / BatchSpanProcessor)从未被清理,导致:
BatchSpanProcessor_WorkerThread 线程持续累积:当应用重复执行 StudioManager.init() + shutdown() 时(例如每条消息都 init-shutdown 的模式)。
- 每次 init 都会创建一个新的
TelemetryTracer(内含新的 SdkTracerProvider + BatchSpanProcessor + Worker 线程),并通过 TracerRegistry.register() 覆盖原 tracer,旧的 tracer 从未被 shutdown。
- 旧的
BatchSpanProcessor 工作线程持续运行,因为 SdkTracerProvider.close() / BatchSpanProcessor.shutdown() 从未被调用。
二、错误使用示例
我方项目采用 「每次 pushMessage 都 init + shutdown」 的用法,导致线上 BatchSpanProcessor_WorkerThread 线程暴涨。
2.1 错误用法代码
// AgentStudioServiceImpl.pushMessage() - 错误用法
// 每次推送消息都会执行:init → push → shutdown
@Override
public boolean pushMessage(Msg msg, String projectName) {
// ...
synchronized (STUDIO_LOCK) {
try {
// 1. 每次推送都初始化 Studio 连接
StudioManager.init()
.studioUrl(studioUrl)
.project(finalProjectName)
.runName("push_" + System.currentTimeMillis())
.initialize()
.block(Duration.ofSeconds(INIT_TIMEOUT_SECONDS));
// 2. 推送消息
StudioClient client = StudioManager.getClient();
client.pushMessage(msg).block(Duration.ofSeconds(PUSH_TIMEOUT_SECONDS));
return true;
} catch (Exception e) {
// ...
} finally {
// 3. 每次推送后都 shutdown
StudioManager.shutdown();
Thread.sleep(3000);
}
}
}
2.2 调用链
AiModelNode.executeAiModel() // 每次 AI 模型调用
→ agentStudioService.pushChatResponseAsync(chatResponse, ...) // 异步推送
→ pushMessage(msg, projectName)
→ StudioManager.init()...initialize().block() // 创建新 BatchSpanProcessor + WorkerThread
→ client.pushMessage(msg)
→ StudioManager.shutdown() // 只关 HTTP/WS,不关 BatchSpanProcessor
2.3 现象
- 线上
BatchSpanProcessor_WorkerThread-1、BatchSpanProcessor_WorkerThread-2、… 线程数持续增加。
- 线程状态:
TIMED_WAITING on AbstractQueuedSynchronizer$ConditionObject,位于 ArrayBlockingQueue.poll。
2.4 正确用法(官方推荐)
- 应用启动时
init() 一次
- 使用
StudioManager.getClient() 多次 pushMessage()
- 应用退出时
shutdown() 一次
三、根因分析
StudioManager.shutdown() 仅关闭 HTTP 和 WebSocket 客户端,未处理 TracerRegistry 或 tracing 资源。
TracerRegistry 没有 unregister() / resetToNoop() 等 API。
TelemetryTracer 未持有 SdkTracerProvider 引用,也没有 shutdown() 方法,无法释放 OpenTelemetry 资源。
四、修复方案
4.1 为 Tracer 接口增加 shutdown()(agentscope-core)
文件: agentscope-core/src/main/java/io/agentscope/core/tracing/Tracer.java
public interface Tracer {
// ... 现有方法 ...
/**
* 关闭当前 tracer 并释放资源(如 SpanProcessor、Exporter)。
* 默认实现为空。若实现类创建了 OpenTelemetry 资源(如 SdkTracerProvider),
* 应重写此方法并调用对应的 close()。
*/
default void shutdown() {}
}
4.2 为 TracerRegistry 增加 resetToNoop()(agentscope-core)
文件: agentscope-core/src/main/java/io/agentscope/core/tracing/TracerRegistry.java
/**
* 将全局 tracer 重置为 {@link NoopTracer},并对前一个 tracer 执行 shutdown
*(若其实现了资源清理,例如 {@link io.agentscope.core.tracing.telemetry.TelemetryTracer})。
* 会释放 OpenTelemetry 资源(SdkTracerProvider、BatchSpanProcessor、工作线程)并关闭 tracing hook。
*
* <p>通常在 Studio 集成关闭时调用。
*/
public static void resetToNoop() {
Tracer previous = tracer;
if (previous != null && !(previous instanceof NoopTracer)) {
previous.shutdown();
}
tracer = new NoopTracer();
disableTracingHook();
}
4.3 为 TelemetryTracer 增加 shutdown() 并持有 SdkTracerProvider(agentscope-extensions-studio)
文件: agentscope-extensions-studio/src/main/java/io/agentscope/core/tracing/telemetry/TelemetryTracer.java
构造与字段:
public class TelemetryTracer implements Tracer {
private final io.opentelemetry.api.trace.Tracer tracer;
@Nullable private final SdkTracerProvider sdkTracerProvider;
public TelemetryTracer(io.opentelemetry.api.trace.Tracer tracer) {
this(tracer, null);
}
private TelemetryTracer(io.opentelemetry.api.trace.Tracer tracer,
@Nullable SdkTracerProvider sdkTracerProvider) {
this.tracer = tracer;
this.sdkTracerProvider = sdkTracerProvider;
}
@Override
public void shutdown() {
if (sdkTracerProvider != null) {
sdkTracerProvider.close();
}
}
// ... 其余保持不变 ...
}
Builder.build() 修改:
TracerProvider tracerProvider =
SdkTracerProvider.builder()
.addSpanProcessor(
BatchSpanProcessor.builder(exporterBuilder.build()).build())
.setSampler(Sampler.alwaysOn())
.build();
return new TelemetryTracer(
tracerProvider.get(INSTRUMENTATION_NAME, Version.VERSION),
tracerProvider);
需视工程约定添加 @Nullable 等相关导入。
4.4 在 StudioManager.shutdown() 中调用 TracerRegistry.resetToNoop()(agentscope-extensions-studio)
文件: agentscope-extensions-studio/src/main/java/io/agentscope/core/studio/StudioManager.java
import io.agentscope.core.tracing.TracerRegistry;
public static void shutdown() {
if (client != null) {
client.shutdown();
}
if (wsClient != null) {
wsClient.close();
}
config = null;
client = null;
wsClient = null;
// 释放 tracing 资源(SdkTracerProvider、BatchSpanProcessor、工作线程)
// 并将全局 tracer 重置为 NoopTracer
TracerRegistry.resetToNoop();
}
五、向后兼容
Tracer.shutdown() 为 default 空实现,现有实现不受影响。
TracerRegistry.resetToNoop() 为新增接口,未调用的场景行为不变。
StudioManager.shutdown() 增加 tracing 清理逻辑,属于行为增强,非破坏性变更。
PR:修复 StudioManager.shutdown() 时 Tracing 资源泄漏
一、问题描述
当
StudioManager.shutdown()被调用时,Tracing 相关资源(包括TracerRegistry.tracer及底层的SdkTracerProvider/BatchSpanProcessor)从未被清理,导致:BatchSpanProcessor_WorkerThread线程持续累积:当应用重复执行StudioManager.init()+shutdown()时(例如每条消息都 init-shutdown 的模式)。TelemetryTracer(内含新的SdkTracerProvider+BatchSpanProcessor+ Worker 线程),并通过TracerRegistry.register()覆盖原 tracer,旧的 tracer 从未被 shutdown。BatchSpanProcessor工作线程持续运行,因为SdkTracerProvider.close()/BatchSpanProcessor.shutdown()从未被调用。二、错误使用示例
我方项目采用 「每次 pushMessage 都 init + shutdown」 的用法,导致线上
BatchSpanProcessor_WorkerThread线程暴涨。2.1 错误用法代码
2.2 调用链
2.3 现象
BatchSpanProcessor_WorkerThread-1、BatchSpanProcessor_WorkerThread-2、… 线程数持续增加。TIMED_WAITINGonAbstractQueuedSynchronizer$ConditionObject,位于ArrayBlockingQueue.poll。2.4 正确用法(官方推荐)
init()一次StudioManager.getClient()多次pushMessage()shutdown()一次三、根因分析
StudioManager.shutdown()仅关闭 HTTP 和 WebSocket 客户端,未处理TracerRegistry或 tracing 资源。TracerRegistry没有unregister()/resetToNoop()等 API。TelemetryTracer未持有SdkTracerProvider引用,也没有shutdown()方法,无法释放 OpenTelemetry 资源。四、修复方案
4.1 为
Tracer接口增加shutdown()(agentscope-core)文件:
agentscope-core/src/main/java/io/agentscope/core/tracing/Tracer.java4.2 为
TracerRegistry增加resetToNoop()(agentscope-core)文件:
agentscope-core/src/main/java/io/agentscope/core/tracing/TracerRegistry.java4.3 为
TelemetryTracer增加shutdown()并持有SdkTracerProvider(agentscope-extensions-studio)文件:
agentscope-extensions-studio/src/main/java/io/agentscope/core/tracing/telemetry/TelemetryTracer.java构造与字段:
Builder.build() 修改:
需视工程约定添加
@Nullable等相关导入。4.4 在
StudioManager.shutdown()中调用TracerRegistry.resetToNoop()(agentscope-extensions-studio)文件:
agentscope-extensions-studio/src/main/java/io/agentscope/core/studio/StudioManager.java五、向后兼容
Tracer.shutdown()为 default 空实现,现有实现不受影响。TracerRegistry.resetToNoop()为新增接口,未调用的场景行为不变。StudioManager.shutdown()增加 tracing 清理逻辑,属于行为增强,非破坏性变更。