Skip to content

39 函数级剖析:配置热重载与重启判定

核心文件:

  • src/gateway/config-reload.ts
  • src/gateway/server-reload-handlers.ts
  • src/config/types.gateway.ts
  • src/infra/restart.ts

模块定位

配置热重载系统监听文件变化,将 diff 路径映射为"热更/重启/忽略"计划,然后精确执行对应动作——避免因小改动导致整个 gateway 重启。

一、GatewayReloadMode(精确枚举)

ts
// src/config/types.gateway.ts  行 161

export type GatewayReloadMode = "off" | "restart" | "hot" | "hybrid";
模式行为
"off"关闭自动处理,配置变化不触发任何操作
"restart"有变化就重启(不尝试热更)
"hot"仅热重载;需重启的变化被忽略并告警
"hybrid"能热更就热更,不行再重启(默认值

默认配置:

ts
// src/gateway/config-reload.ts  行 42-45

const DEFAULT_RELOAD_SETTINGS: GatewayReloadSettings = {
  mode: "hybrid",
  debounceMs: 300,  // 配置变化后等待 300ms 再处理(防抖)
};

二、执行链路

文件变化(chokidar)


debounce 300ms(避免频繁触发)


读取配置快照(loadConfig)


diffConfigPaths(...)   // 计算具体改动路径


buildGatewayReloadPlan(changedPaths)  // 路径 → 计划

    ├─ plan.restartGateway = false → applyHotReload(plan, nextConfig)

    └─ plan.restartGateway = true  → requestGatewayRestart(plan, nextConfig)

三、buildGatewayReloadPlan

ts
// src/gateway/config-reload.ts  行 145-215

// 返回类型(共 11 个字段)
export type GatewayReloadPlan = {
  changedPaths: string[];
  restartGateway: boolean;
  restartReasons: string[];
  hotReasons: string[];
  reloadHooks: boolean;
  restartGmailWatcher: boolean;
  restartBrowserControl: boolean;
  restartCron: boolean;
  restartHeartbeat: boolean;
  restartChannels: Set<ChannelKind>;
  noopPaths: string[];
};

export function buildGatewayReloadPlan(changedPaths: string[]): GatewayReloadPlan {

四、BASE_RELOAD_RULES(精确内容)

ts
// src/gateway/config-reload.ts  行 46-66

const BASE_RELOAD_RULES: ReloadRule[] = [
  { prefix: "gateway.remote", kind: "none" },                          // 远程发现:noop
  { prefix: "gateway.reload",  kind: "none" },                         // reload 自身:noop
  { prefix: "hooks.gmail",    kind: "hot", actions: ["restart-gmail-watcher"] },
  { prefix: "hooks",          kind: "hot", actions: ["reload-hooks"] },
  { prefix: "agents.defaults.heartbeat", kind: "hot", actions: ["restart-heartbeat"] },
  { prefix: "agent.heartbeat", kind: "hot", actions: ["restart-heartbeat"] },
  { prefix: "cron",           kind: "hot", actions: ["restart-cron"] },
  { prefix: "browser",        kind: "hot", actions: ["restart-browser-control"] },
];

规则集组合方式(listReloadRules() 内部):

[...BASE_RELOAD_RULES, ...channelReloadRules, ...BASE_RELOAD_RULES_TAIL]
  • BASE_RELOAD_RULES:前缀精确匹配,先命中先生效
  • channelReloadRules:各 channel 插件声明自己的配置前缀(可扩展)
  • BASE_RELOAD_RULES_TAIL:兜底规则(未匹配到的路径),通常触发 restartGateway

五、applyHotReload(精确调用链)

ts
// src/gateway/server-reload-handlers.ts  行 52-133

// 按顺序依次执行以下操作:
1. setGatewaySigusr1RestartPolicy({ allowExternal: ... })
2. if (plan.reloadHooks)        → resolveHooksConfig(nextConfig)
3. if (plan.restartHeartbeat)   → nextState.heartbeatRunner.updateConfig(nextConfig)
4.                                resetDirectoryCache()         // 无条件调用
5. if (plan.restartCron)        → cron.stop() + buildGatewayCronService(...) + cron.start()
6. if (plan.restartBrowserControl) → browserControl.stop() + startBrowserControlServerIfEnabled()
7. if (plan.restartGmailWatcher) → stopGmailWatcher() + startGmailWatcher(nextConfig)
8. if (plan.restartChannels.size > 0)
stopChannel(name) + startChannel(name)  // 按 channel 粒度
9.                                setCommandLaneConcurrency(CommandLane.Cron, ...)
                                  setCommandLaneConcurrency(CommandLane.Main, ...)
                                  setCommandLaneConcurrency(CommandLane.Subagent, ...)  // 无条件
10.                               params.setState(nextState)   // 最后提交新状态

按 channel 粒度重启: 只重启受影响的 channel,其他 channel 保持在线。

六、requestGatewayRestart(等待空闲)

ts
// src/gateway/server-reload-handlers.ts  行 135-195

const requestGatewayRestart = (
  plan: GatewayReloadPlan,
  nextConfig: ReturnType<typeof loadConfig>,
) => {

等待空闲检查三类活跃任务:

ts
getTotalQueueSize()          // command queue 中的操作数
getTotalPendingReplies()     // 待发送回复数
getActiveEmbeddedRunCount()  // 嵌入式 agent 运行数

空闲等待参数(精确常量):

ts
// src/infra/restart.ts  行 16-17

const DEFAULT_DEFERRAL_POLL_MS = 500;         // 每 500ms 轮询一次
const DEFAULT_DEFERRAL_MAX_WAIT_MS = 30_000;  // 最多等待 30 秒
  • totalActive === 0:立刻执行重启
  • 若有活跃任务:每 500ms 检查一次,最多等待 30 秒
  • 超时后强制重启(防止永久等待)

七、GatewayReloadPlan 字段与热更动作对应关系

计划字段触发热更动作
reloadHooksresolveHooksConfig
restartHeartbeatheartbeatRunner.updateConfig
restartCroncron.stop/start
restartBrowserControlbrowserControl.stop/start
restartGmailWatcherstopGmailWatcher/startGmailWatcher
restartChannels (Set)逐个 stopChannel/startChannel
restartGateway = true不做热更,走 requestGatewayRestart

八、自检清单

  1. GatewayReloadMode 定义在 config/types.gateway.ts:161,不在 config-reload.ts。
  2. debounce 延迟默认 300 ms,可通过 gateway.reload.debounceMs 配置。
  3. BASE_RELOAD_RULES_TAIL 是兜底规则,与 BASE_RELOAD_RULES 通过 listReloadRules() 合并。
  4. applyHotReload 步骤 4 的 resetDirectoryCache() 无条件调用(不依赖任何 plan 字段)。
  5. requestGatewayRestart 等待空闲轮询:pollMs=500,maxWaitMs=30_000。

九、开发避坑

  1. "hot" 模式下 restartGateway=true 的变化被忽略:只告警,不重启。如果配置了 "hot" 却发现某些配置变化没生效,检查是否触发了需要重启的规则。
  2. channel 粒度重启不保证消息不丢:重启窗口内来的消息可能丢失,consumer 需要有重连逻辑。
  3. resetDirectoryCache() 在每次热更时都调用:这是必要的,否则旧缓存导致新配置读取失败。
  4. 超时强制重启不会再检查空闲:30 秒后直接发 SIGUSR1,活跃任务会被强制中断。

用工程视角拆解 AI 智能体框架