qwen-code 守护进程中的 Workspace MCP Transport Pool:多会话共享 MCP 传输连接的实现原理
2026/9/13 4:27:55 网站建设 项目流程

qwen-code 守护进程中的 Workspace MCP Transport Pool:多会话共享 MCP 传输连接的实现原理

【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code

本篇围绕 qwen-code 守护进程(daemon)模式下的McpTransportPool展开,说明它如何让同一 workspace 内的多个 ACP 会话按(serverName + configFingerprint)元组共享同一条 MCP 传输连接,而不是为每个会话各 fork 一份 MCP 子进程。读完后你将掌握池化连接的 acquire/release/restart/drain 全生命周期、指纹与 OAuth 归一化规则、未池化(HTTP/SSE/SDK-MCP)旁路、预算护栏联动,以及 stdio 子进程后代清扫(pid-descendants)的跨平台实现细节。

一、为什么需要 MCP 传输池

在多会话守护进程场景中,每个 ACP 会话若各自启动自己的 MCP 客户端,同一个 MCP 服务器就会被 fork 出 N 份完全相同的子进程,浪费内存、CPU 和端口,还会让GET /workspace/mcp一类的资源面板失去准确的数据源。

McpTransportPool(位于 mcp-transport-pool.ts)就是 F2 系列(#4175 第 5 个提交)引入的 workspace 级连接池:同一个 runtime 内的多个 ACP 会话,对每个唯一的(serverName + configFingerprint)元组只共享一条传输连接,而不是各自 spawn MCP 子进程。它是防止"多会话 daemon 为每个会话 fork 一份所有 MCP 服务器"的主要机制。

池化模式的生效方式(见 acpAgent.ts):

  • 池启用时,每个已启动的 ACP 子进程都拥有独立的池(QwenAgent.mcpPool,见 acpAgent.ts)。
  • 生产环境会尽力预热受信任的主子进程(primary child)以兼容旧行为;受信任的次子进程(secondary)按需启动;不受信任的 secondary 既不启动子进程也不构建池。Legacy primary 路由保留既有兼容行为。
  • 池在 agent 启动时用 runtime 的 bootstrapConfig构造一次(acpAgent.ts 的构造逻辑),生命周期长于任何单个会话。
  • 条目(entry)以引用计数跟踪会话附着情况;当引用计数归零后,经过可配置的宽限期(grace period)才关闭传输。

二、职责边界

McpTransportPool的职责可以归纳为八条:

  • (name + fingerprint)获取或 spawn 一条 MCP 传输,通过spawnInFlight去重并发的冷启动获取。
  • 释放每会话引用;当最后一个引用 detach 时,为该条目武装(arm)drain 定时器。
  • 用硬性MAX_IDLE_MS上限抵御引用计数"抖动"(thrashing)——恶意或异常的客户端无法通过反复 attach/detach 让空闲传输永远存活。
  • 通过反向索引sessionToEntries维护会话到条目的映射,使releaseSession(sessionId)的复杂度为 O(refs) 而不是 O(entries)。
  • 按需重启条目(restartByName)——单条目返回{restarted, durationMs},多条目返回{entries: RestartResult[]}(F2 多条目契约)。
  • 在 daemon 关闭时按可配置超时间歇整个池(drainAll);draining 期间拒绝新的 acquire。
  • acquire时咨询WorkspaceMcpBudget,强制执行按名称的预留上限;当没有兄弟条目持有同名时,在条目关闭时释放槽位。
  • 通过SessionMcpView产出按会话过滤的 tools/prompts 快照,避免某个会话的发现(discovery)把工具注册进其他会话。

三、公开接口与配置选项

3.1 公开 surface

class McpTransportPool { constructor(cliConfig: Config, options: McpTransportPoolOptions); acquire( serverName, cfg, sessionId, sessionToolRegistry, sessionPromptRegistry, ): Promise<PooledConnection>; release(id, sessionId): void; releaseSession(sessionId): void; restartByName( name, opts?, ): Promise<RestartResult | { entries: RestartResult[] }>; drainAll(opts?): Promise<void>; getBudget(): WorkspaceMcpBudget | undefined; getSnapshot(): McpPoolSnapshot; }

McpTransportPoolOptions(定义于 mcp-transport-pool.ts):

  • workspaceContext: WorkspaceContext(必填)——所有条目共享的 daemon 绑定 workspace 上下文。
  • debugMode: boolean——透传给McpClient的调试日志开关。
  • sendSdkMcpMessage?——每会话回调(池模式下绕过 SDK MCP)。
  • pooledTransports?: ReadonlySet<McpTransportKind>——默认{stdio, websocket}(mcp-pool-key.ts 中的POOLED_TRANSPORTS_DEFAULT)。HTTP/SSE 默认不池化,因为其 headers 可能携带会话级 OAuth 状态;运维可用QWEN_SERVE_MCP_POOL_TRANSPORTS显式将其纳入池化。
  • drainDelayMs?——默认30_000
  • entryOptions?: (transport) => PoolEntryOptions——逐条目覆盖。
  • budget?: WorkspaceMcpBudget——workspace 级预算控制器;缺省意味着完全不强制(每会话McpClientManager的预算机制在池模式下本就不生效)。

3.2 内部状态

状态类型用途
entriesMap<ConnectionId, PoolEntry>connectionIdOf(name, fingerprint)为键的存活池条目。
unpooledIdsSet<ConnectionId>不在pooledTransports允许列表内的传输对应的条目。
spawnInFlightMap<ConnectionId, Promise<PoolEntry>>去重同一键的并发冷获取。
sessionToEntriesMap<string, Set<ConnectionId>>反向索引,用于 O(refs) 的releaseSession
drainingbooleandrain 互斥量——一旦置位,所有acquire调用被拒绝。
nextIndexByNameMap<string, number>按服务器名的单调entryIndex计数器(新条目出现时面板不重排)。

以上六块私有状态在 mcp-transport-pool.ts 中集中声明,与文档描述一一对应。

3.3PoolEntry逐条结构

池条目的状态机为:spawning → active ⇄ (active ↔ reconnect) → (active → draining on last detach, draining → active on attach OR draining → closed on timer),实现在 mcp-pool-entry.ts。

字段用途
localStatus: MCPServerStatusMCPServerStatus生命周期驱动。
state: PoolEntryStatespawning/active/draining/closed/failed
generation: number每次 restart 递增;订阅方据此比较以检测重连周期。
refs: Set<string>当前附着会话的 session id 集合。
subscribers: Map<string, SessionMcpView>按会话过滤的视图。
subscriberHandles: Map<string, PooledConnectionImpl>acquire返回的句柄。
toolsSnapshot[],promptsSnapshot[]池级规范快照;在toolsChanged/promptsChanged时重新发放。
drainTimer?refs.size === 0时武装;默认 30s;attach 时重置。
maxIdleTimer?首次空闲时武装;不被 acquire/release 抖动重置。默认 5 分钟。
firstIdleAt?max-idle 硬性上限的时间基准。
restartInFlight?restart()的互斥量。

3.4PoolEntryOptions与默认值

interface PoolEntryOptions { drainDelayMs: number; // 默认 30_000 maxIdleMs: number; // 默认 5 * 60_000 maxReconnectAttempts: number; // stdio/ws 默认 3;http/sse 默认 5 reconnectStrategy: | { kind: 'fixed'; delayMs: number } | { kind: 'exponential'; baseMs: number; capMs: number }; }

defaultPoolEntryOptions(transport)(mcp-pool-entry.ts)返回两类默认值:

  • stdio/ws:{fixed 5s, 3 attempts}
  • http/sse(websocket 被归入"remote"一类):{exponential 1s → 16s, 5 attempts}

远程传输获得更长的重试预算,因为其故障更常是瞬时的。值得注意的是源码中的一段注释:maxReconnectAttemptsreconnectStrategy目前没有被任何池内代码路径消费——池模式下暂无健康监控器——该分类是为将来健康监控落地做的预备,字段保留以维持设计契约(见 mcp-pool-entry.ts)。

四、核心工作流

4.1acquire:获取或冷启动

关键点:

  • 可池化判定isPoolable(mcp-pool-key.ts)执行:SDK MCP 服务器一律旁路(其设计就是每会话的);其他传输按运维的pooledTransports选择放行。
  • 预算预检:冷启动前先tryReserve(name);被拒则记录recordRefusal并抛BudgetExhaustedError。该异常类与每会话McpClientManager抛出的是同一个类(mcp-transport-pool.ts 直接从 manager 复用导入),保证 SDK 消费者看到的错误类一致。
  • spawn 失败回滚:冷启动中途崩溃会释放已预留的预算槽位(V21-4),否则会永久泄漏预留。

4.2release与 drain 时序

两个值得注意的实现细节:

  • hasNameSibling(name)(mcp-transport-pool.ts)同时遍历entries.values()spawnInFlight.keys(),后者用parseConnectionId解析。之所以不能对兄弟名用startsWith前缀匹配,是因为 MCP 服务器名可以合法地包含::——以${name}::开头的兄弟名会产生假阳性。parseConnectionId使用lastIndexOf('::')切分(mcp-pool-key.ts),该测试守护点在 mcp-pool-key.test.ts。
  • releaseSession(sessionId)sessionToEntries反向索引读取,以 O(refs) 释放该会话引用的所有条目,然后清除索引项。它被 bridge 的会话关闭路径使用,避免遍历整张条目表。

4.3restartByName

在 daemon HTTP 层,预检预算检查会在目标槽位尚未被预留且重启会使存活数超过enforce预算时,返回{restarted:false, skipped:true, reason:'budget_would_exceed'}(Wave 4 变更控制)。在 ACP 代理侧,restartByName的实际调用点见 acpAgent.ts。

4.4drainAll:守护进程关闭时排空全池

五、状态与生命周期不变量

  • 池的构造是同步的;首次acquire触发传输冷启动。
  • drainDelayMs(默认 30s)在 attach 时被取消。
  • maxIdleMs(默认 5 分钟)永不被 attach/detach 重置——它在首次空闲时开始计时,只在条目真正关闭或到点前重新附着时才停止。这是对抖动客户端的防御。
  • nextIndexByName单调递增:旧条目即使在新条目出现后也保留其分配的索引,面板读取entryIndex时不会重排。
  • spawn 失败会释放已预留的预算槽位(V21-4——没有这一步,一次在 connect 中途崩溃的冷启动会永久泄漏预留)。

六、配置项与开关

来源开关效果
环境变量QWEN_SERVE_NO_MCP_POOL=1Kill switch——QwenAgent.mcpPool保持undefined,回退到每会话McpClientManager(F2 之前的路径)。
CLI Flag--mcp-client-budget=N--mcp-budget-mode={off,warn,enforce}childEnvOverrides转发到 ACP 子进程;子进程构造WorkspaceMcpBudget并传给池。
能力标签(条件性)mcp_workspace_poolmcp_pool_restart池开启时同时通告。SDK 会对两者做预检,以分支处理池感知的响应形态。

Kill switch 的判定逻辑在 acpAgent.ts:当process.env['QWEN_SERVE_NO_MCP_POOL'] === '1'时不构建池,能力标签(mcp_workspace_pool等)也在capabilities与 serve 状态路径中联动,相关代码见 capabilities.ts 与 status.ts。

未池化条目(HTTP / SSE / SDK-MCP)

不在pooledTransports允许列表内的传输(默认包括 HTTP、SSE 与 SDK-MCP)走单独路径:createUnpooledConnection(name, cfg, sessionId, ...)(mcp-transport-pool.ts)为每个会话创建 id 形如${name}::unpooled-${entryIndex}的条目。与池化条目的差异:

  • 条目存入entries的同时也被unpooledIds: Set<ConnectionId>跟踪,使release/releaseSession可以走 close-on-detach 的快速路径(refs 上限恒为 1)。
  • 直接使用McpClient.discover(),不做池快照回放;applyTools/applyPrompts为空操作(attach()中的 W77 /skipReplay: true),因为会话注册表已经持有注册过的内容。
  • workspace 预算依然会拦截它们——F2 预算后续修复了"未池化连接绕过tryReserve"的漏洞;同样的WorkspaceMcpBudget槽位会被预留,并在条目关闭时释放(无论池化与否)。

W77 竞态(提交cb206da36createUnpooledConnectionawait client.connect()/client.discover()之前就把条目存入this.entries,但attach()成功之后才把sessionToEntries[sessionId]建索引。若在这段 connect/discover 窗口内并发执行closeStoredSession()/releaseSession(sessionId),会看到空索引,让未池化 spawn 完成,随后attach()把 tools/prompts 注册进一个已经关闭的会话。修复方案:

  • mcp-pool-entry.ts:公开的isTerminated(): boolean探针(state === 'closed' || state === 'failed')。
  • mcp-pool-entry.ts:markActive()isTerminated()时短路,避免已拆毁的条目被复活为'active'
  • 调用方(池的未池化路径)在 await 之间探测isTerminated(),若父会话已消失则中止 attach。

该竞态当时尚属潜伏(W61/W71 的每会话releaseSession钩子要到 F4 才落地),但那个钩子一到就会引爆,因此修复被提前到了 F2 系列内完成。

七、GET /workspace/mcp的池感知快照字段

池激活时,ServeWorkspaceMcpStatus的每个服务器单元格(status.ts)额外携带三个字段:

字段类型用途
disabledReason'config' \| 'budget'区分运维禁用的服务器(来自disabledMcpServersdisabled: true)与预算拒绝(status: 'error', errorKind: 'budget_exhausted')。面板可以只渲染一行服务器而无需交叉读取errors[]budgets[]
entryCountnumber>=1池模式下,同一 workspace 可能出现多个同名的PoolEntry实例,因为不同会话可能注入不同指纹(如每会话 OAuth headers)。当QWEN_SERVE_NO_MCP_POOL=1禁用池时该字段不存在。新客户端在entryCount > 1时渲染 "N entries" 徽章。
entrySummaryReadonlyArray<{entryIndex, refs, status}>逐条目明细。entryIndex是条目创建时分配的稳定不透明整数,不是原始指纹,因此快照 diff 不会泄漏 OAuth 或 env 轮换时机。refs是当前附着会话数。status让面板在聚合mcpStatus已 connected 的情况下仍能展示逐条目健康度。

(entryCount, entrySummary)总是成对广播;mcp_workspace_pool能力标签隐含这两个字段。旧版 SDK 客户端在增量式(additive)协议契约下直接忽略它们。

池快照还暴露subprocessCount,且只统计'stdio':WebSocket、HTTP、SSE 传输连接的是远程服务器,不产生本地子进程。早期版本把 WebSocket 传输计为本地子进程,导致资源面板虚高。

八、drain 覆盖两条关闭路径

池的 drain 不只挂在 SIGTERM handler 上。正常的 IDE 关闭路径(await connection.closed)同样经由drainPoolBeforeExit(acpAgent.ts)调用drainAll。从源码调用点看,三类触发场景分别对应:超时/退出路径、进程信号(signal)、以及 IDE 主动关闭(ide_close,见 acpAgent.ts)。无论 daemon 收到进程信号还是 IDE 干净地关闭连接,池都会进入draining、拒绝新 acquire、并等待条目关闭。池内的drainAll({ force: true, timeoutMs })调用点在 acpAgent.ts。

九、/mcp refresh与启动发现共用同一网关

discoverAllMcpTools(启动发现)与discoverAllMcpToolsIncremental/mcp refresh/ 热重载)在池模式下都先咨询池(mcp-client-manager.ts)。这条共享网关防止热重载意外创建每会话客户端、双计预算,或留下孤儿传输。

十、重连期间在途工具调用:MCPCallInterruptedError

当底层 MCP 传输静默断开(连接从'active'/'draining'跳到localStatus === DISCONNECTED而无显式 close)时,池将条目标记为'failed'、从pool.entries中驱逐、并在 detach 订阅视图之前发出failed事件。这个"先 emit 后 detach"的顺序很关键:订阅方能尽早收到failed事件,从而把挂起的callToolPromise 路由到MCPCallInterruptedError——卡住的await client.callTool(...)会干净地 reject 而不是永久挂起。forceShutdown使用同样的 emit-then-detach 顺序。

十一、指纹与canonicalOAuth归一化

池键来自 mcp-pool-key.ts 的fingerprint(cfg),取 SHA-256 前 16 个十六进制字符(64 bit)。在现实池规模(每 workspace 通常 < 100 条)下,生日碰撞概率低于 10^-15,可安全地直接作为 map 键。哈希覆盖所有传输定义字段:

transport, command, args, cwd, env, url, httpUrl, tcp, headers, timeout, versionNegotiation, oauth

(源码中实际还包括authProviderTypetargetAudiencetargetServiceAccount,见 mcp-pool-key.ts)。逐会话过滤与元数据字段(includeToolsexcludeToolstrustdescriptionextensionNamediscoveryTimeoutMs)被排除,因此带不同过滤器的会话可以共享同一条目。自动版本协商的 opt-in 被纳入哈希,因为它改变底层进程的建连方式。

对 OAuth 字段,canonicalOAuth(o)(mcp-pool-key.ts)哈希MCPOAuthConfig的每一个字段:clientIdclientSecret、排序后的scopes、排序后的audiencesauthorizationUrltokenUrlredirectUritokenParamNameregistrationUrl。这就是凭据隔离契约:两个仅在clientSecretaudiencesredirectUri上有差异的会话配置会得到不同指纹、无法共享条目——机密客户端(confidential client)与多受众(multi-audience)令牌部署依赖于此。

其余归一化规则:

  • scopesaudiences排序,调用方书写顺序无关;显式null归一化,使undefined字段与显式null得到相同哈希。
  • 键不包含discoveryTimeoutMs;同一键、不同超时并发的 acquire 遵循"先到者胜",与 F2 之前每会话 manager 的行为一致。
  • PoolEntrycfg: MCPServerConfig保持私有,外部需要传输族时只能用entry.transportKindgetter——这防止 env、header 鉴权与 OAuth 字段意外泄漏给消费者。

ConnectionId的形式为${name}::${fp16hex}(mcp-pool-key.ts);同名不同指纹(例如 OAuth 令牌或 env 在会话间分化)产生不同的 ConnectionId。

十二、扩展卸载依赖MAX_IDLE_MS

系统有意没有在运行时卸载 MCP 扩展的主动清理路径。那些MCPServerConfig已不再出现在合并后 workspace 设置中的孤儿条目,会在最后一个订阅者 detach 之后,被MAX_IDLE_MS硬性上限自然回收。为罕见的运维边缘场景增加同步卸载清理路径的复杂度不值得;硬性上限把卸载点之后的孤儿进程存活时间限制在默认 5 分钟内。

需要更快清理的运维可以重启 daemon,或对已下线的服务器名调用POST /workspace/mcp/:server/restart——它会走禁用服务器路径把条目拆掉。

十三、自愈可观测性

池在自愈路径上产出两类结构化诊断:

1.McpClient.lastTransportError: Error | undefined(mcp-client.ts)——McpClient.onerror把最近一次传输异常存入私有字段,并在connect()入口清空。PoolEntry的静默丢弃(silent-drop)路径读取client.getLastTransportError()并放进emit({kind:'failed', lastError}),订阅方与面板不必再去 grep stderr 找根因。

2.SweepResult(内部接口,不导出;mcp-pool-entry.ts)——sweepAndDisconnect(reason)返回Promise<SweepResult>

interface SweepResult { pidSweepError?: Error; // listDescendantPids 本身抛异常 descendantsFound?: number; // 发现的 descendant pid 数量 descendantsSignaled?: number; // 成功 SIGTERM 的数量 }

唯一消费者是statusChangeListener中的静默丢弃块。它用descendantsFound/descendantsSignaled检测"部分信号"情形(信号数少于发现数,通常是listDescendantPidssigtermPids之间进程退出或发生 EPERM)以及 sweep 错误,然后记录结构化 warning。forceShutdowndoRestart忽略该返回值,因为其 catch 路径已携带更丰富的失败信号。

十四、子进程清扫:pid-descendants快照路径

McpTransportPool关闭 stdio 子进程时,必须枚举其后代进程——npx包装器与 shell 包装器可能产生多层 fork。pid-descendants.ts 暴露listDescendantPids(rootPid) → Promise<number[]>sigtermPids(pids),供sweepAndDisconnect使用。

Linux / macOS 主路径

一次ps -A -o pid=,ppid=快照读取进程表,解析成Map<ppid, pid[]>,然后walkDescendants(tree, root)做 BFS 抽取子树。任意深度只需一次psfork。

walkDescendants维护visited: Set<number>且把root也纳入该集合,以防御 PID 复用造成的环。在快速进程更替下,快照理论上可能包含 A→B / B→A 环路;没有visited,walker 会用假数据填满MAX_DESCENDANTS配额,挤掉真正的后代。

Windows 主路径

一次Get-CimInstance Win32_Process | ConvertTo-Csv -Delimiter ","快照发出所有(ProcessId, ParentProcessId)行,然后走同一套MapwalkDescendants路径。

显式-Delimiter ","是必需的:随 Windows 附带的 PowerShell 5.1 中,ConvertTo-Csv默认使用系统 locale 的列表分隔符;德语、法语、荷兰语、意大利语等 locale 使用;,修复前的解析器^"(\d+)","(\d+)"$永远匹配不上,导致每次 daemon 关闭都退化为 per-pid CIM 过滤路径,每个子进程额外付出约 0.5–1s 的 PowerShell 启动成本。

回退路径

BusyBox < v1.28 缺少ps -o,distroless 容器可能没有ps,某些 Windows 环境会经 ACL 截断 CIM 输出。当主路径解析到零行或抛异常时,代码回退为 per-pid BFS:Linux / macOS 用pgrep -P <pid>,Windows 用Get-CimInstance -Filter "ParentProcessId=$p",其中$p是 PowerShell 变量绑定而非字符串拼接。当前Number.isInteger守卫对入口已足够;变量绑定属于纵深防御。

共享约束

  • 两条路径都受MAX_DESCENDANTS = 256MAX_DEPTH = 8限界,防止恶意或退化进程树拖垮 sweep。
  • 快照路径使用maxBuffer: 8MB,足以覆盖约 25 万进程的病态主机;Node 默认 1MB 缓冲在约 3 万进程处就会截断子进程输出。
  • 性能收益被有意保持温和(典型 200–500 进程的开发机解析 < 10ms,比 per-pidpgrep快约 2 倍)。主要收益是 fork 卫生与快照一致性:BFS 一次性看到完整子树,而旧的 per-pid 查询路径可能漏掉两次查询之间 fork 出的孙子进程。

十五、嵌入方说明与实现细节

15.1McpClientManager构造(嵌入方)

McpClientManager的构造签名是(config, toolRegistry, options?: McpClientManagerOptions)。直接导入该类的嵌入方应当传入:

new McpClientManager(config, toolRegistry, { eventEmitter, sendSdkMcpMessage, healthConfig, budgetConfig, pool, });

测试中建议优先使用mkManager(overrides?)工厂,让只关心一两个字段用例保持一行。

15.2 内部实现注记

以下 helper 是内部的,但读源码时可能遇到:

  • McpTransportPool.acquire()使用attachPooledSessionrollbackReservationOnSpawnFailure来共享快速路径 attach、spawn 后 attach 与池化 spawn-in-flight 的 catch 行为。运行时行为不变;竞态窗口不变量仍留在调用点。
  • SessionMcpView.applyTools/applyPrompts通过compileNameFilter(cfg)includeTools/excludeTools编译一次,再用compiledFilterAccepts(compiled, name)检查每个工具。导出的passesSessionFilter/passesSessionPromptFilter走同一编译路径。excludeTools是精确匹配;includeTools会剥掉首个(...)后缀,使toolName(args)匹配toolName

十六、已知限制(Caveats)

  • HTTP / SSE 传输默认不池化——除非运维显式把二者纳入QW_SERVE_MCP_POOL_TRANSPORTS(原文配置名为QWEN_SERVE_MCP_POOL_TRANSPORTS),否则每次 acquire 都会铸出只随会话存活的 fresh 条目。其 headers 可能携带会话级 OAuth 状态,默认池化有跨会话泄漏凭据的风险。
  • maxIdleMs是穿越 attach/detach 抖动的硬上限。5 分钟空闲硬上限意味着即使激进地 attach/detach 的客户端也无法把空闲传输钉住超过 5 分钟。想要钉住长寿命传输的运维应调大maxIdleMs或把服务器移出池运行。
  • 按服务器名的预算槽位意味着两个共享名称但指纹不同的池条目合计只消耗一个槽位。子进程核算通过pool.getSnapshot().subprocessCount单独暴露。
  • startsWith回归已在hasNameSibling中规避——MCP 服务器名可以合法包含::(见 mcp-pool-key.test.ts)。应始终使用parseConnectionIdlastIndexOf('::')切分,绝不用字符串前缀匹配。
  • 池 drain 是单向的——drainAll永久置draining = true;要继续工作必须新建一个池。

参考

  • 核心实现:mcp-transport-pool.ts(全文件)、mcp-pool-entry.ts(条目生命周期)、mcp-pool-key.ts(connectionIdOfparseConnectionIdfingerprint)、mcp-pool-events.ts(事件类型)、session-mcp-view.ts(每会话过滤视图)、mcp-workspace-budget.ts(预算护栏)。
  • 守护进程侧调用点:acpAgent.ts(mcpPool构造、restartByNamedrainPoolBeforeExit与 kill switch)。
  • 测试:mcp-transport-pool.test.ts、mcp-pool-key.test.ts、session-mcp-view.test.ts、mcp-workspace-budget.test.ts、pid-descendants.test.ts。
  • 设计文档:f2-mcp-transport-pool.md 第 6 节覆盖传输池状态机、重连、drain 与后代清扫路径;设计契约(v2.2,含 32 项评审合并变更日志)为权威来源,本文档所在页是开发者深度解读。
  • 预算护栏配套文档:06-mcp-budget-guardrails.md。

【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询