Skip to content

[Bug]:StudioManager在关闭的时候,未关闭所有资源 #828

@6XianSheng

Description

@6XianSheng

PR:修复 StudioManager.shutdown() 时 Tracing 资源泄漏

一、问题描述

StudioManager.shutdown() 被调用时,Tracing 相关资源(包括 TracerRegistry.tracer 及底层的 SdkTracerProvider / BatchSpanProcessor从未被清理,导致:

  1. BatchSpanProcessor_WorkerThread 线程持续累积:当应用重复执行 StudioManager.init() + shutdown() 时(例如每条消息都 init-shutdown 的模式)。
  2. 每次 init 都会创建一个新的 TelemetryTracer(内含新的 SdkTracerProvider + BatchSpanProcessor + Worker 线程),并通过 TracerRegistry.register() 覆盖原 tracer,旧的 tracer 从未被 shutdown
  3. 旧的 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-1BatchSpanProcessor_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 清理逻辑,属于行为增强,非破坏性变更。

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Projects

    Status

    Backlog

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions