39 函数级剖析:配置热重载与重启判定
核心文件:
src/gateway/config-reload.tssrc/gateway/server-reload-handlers.tssrc/config/types.gateway.tssrc/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 字段与热更动作对应关系
| 计划字段 | 触发热更动作 |
|---|---|
reloadHooks | resolveHooksConfig |
restartHeartbeat | heartbeatRunner.updateConfig |
restartCron | cron.stop/start |
restartBrowserControl | browserControl.stop/start |
restartGmailWatcher | stopGmailWatcher/startGmailWatcher |
restartChannels (Set) | 逐个 stopChannel/startChannel |
restartGateway = true | 不做热更,走 requestGatewayRestart |
八、自检清单
GatewayReloadMode定义在config/types.gateway.ts:161,不在 config-reload.ts。- debounce 延迟默认 300 ms,可通过
gateway.reload.debounceMs配置。 BASE_RELOAD_RULES_TAIL是兜底规则,与BASE_RELOAD_RULES通过listReloadRules()合并。applyHotReload步骤 4 的resetDirectoryCache()无条件调用(不依赖任何 plan 字段)。requestGatewayRestart等待空闲轮询:pollMs=500,maxWaitMs=30_000。
九、开发避坑
"hot"模式下restartGateway=true的变化被忽略:只告警,不重启。如果配置了"hot"却发现某些配置变化没生效,检查是否触发了需要重启的规则。- channel 粒度重启不保证消息不丢:重启窗口内来的消息可能丢失,consumer 需要有重连逻辑。
resetDirectoryCache()在每次热更时都调用:这是必要的,否则旧缓存导致新配置读取失败。- 超时强制重启不会再检查空闲:30 秒后直接发 SIGUSR1,活跃任务会被强制中断。