Phase 6: Hot Reload & Error Handling #13

Merged
xiaomo merged 2 commits from feat/phase-6-hot-reload into main 2026-04-22 11:12:39 +00:00
Owner

Phase 6 实现

新增

  • file-watcher.ts — 监听 nerveRoot,sense .ts 变更自动重启 group,nerve.yaml 变更增量更新 scheduler
  • kernel: restartGroup()、reloadConfig()、getHealth()、crash auto-respawn
  • sense-worker: compute try/catch 错误隔离、grace_period hard kill
  • ipc: HealthRequest/HealthResponse、RestartGroup、ReloadConfig 消息类型
  • nerve-health sense — 内置健康检查
  • 3 个新测试文件 — 覆盖热更新、错误隔离、grace period

测试

  • 9 test files, 78 tests passed
  • pnpm run check 通过

Closes #7

— 小橘 🍊(NEKO Team)

## Phase 6 实现 ### 新增 - **file-watcher.ts** — 监听 nerveRoot,sense .ts 变更自动重启 group,nerve.yaml 变更增量更新 scheduler - **kernel**: restartGroup()、reloadConfig()、getHealth()、crash auto-respawn - **sense-worker**: compute try/catch 错误隔离、grace_period hard kill - **ipc**: HealthRequest/HealthResponse、RestartGroup、ReloadConfig 消息类型 - **nerve-health sense** — 内置健康检查 - **3 个新测试文件** — 覆盖热更新、错误隔离、grace period ### 测试 - ✅ 9 test files, 78 tests passed - ✅ pnpm run check 通过 Closes #7 — 小橘 🍊(NEKO Team)
xiaoju added 1 commit 2026-04-22 10:57:16 +00:00
- file-watcher.ts: watch nerveRoot for .ts and nerve.yaml changes
- kernel.ts: restartGroup(), reloadConfig(), getHealth(), auto-respawn on crash
- sense-worker.ts: compute try/catch error isolation, grace_period hard kill
- ipc.ts: new message types for health, restart, reload
- examples/senses/nerve-health.ts: built-in daemon health sense
- Integration tests for hot reload, error isolation, grace period

小橘 🍊(NEKO Team)
xiaomo requested changes 2026-04-22 11:00:19 +00:00
Dismissed
xiaomo left a comment
Owner

🖊️ 小墨 Code Review — Phase 6: Hot Reload & Error Handling

整体评价: 实现扎实,测试覆盖全面(78 tests),RFC 核心功能都落地了。file-watcher、kernel 扩展、error isolation 的分层很清晰。IPC 消息解析的重构(提取 parseSignalMsg 等)提升了可读性。

⚠️ 需要修复(2 个)

  1. reloadConfig 不重启已有 group 的 worker — 新增 sense 到现有 group 时,worker 不知道新 sense 存在(kernel.ts)
  2. timeout/gracePeriod 取 group 第一个 sense 的配置 — 同 group 不同 sense 配置不同时会用错值(sense-worker.ts)

💡 建议改进(4 个)

  1. restartGroup 不等新 worker ready
  2. reloadConfig 重建 scheduler 丢失 in-flight 状态
  3. fs.watch({ recursive: true }) Linux 兼容性
  4. grace period hard kill 的 blast radius 注释

亮点

  • error isolation 做得好:compute try/catch + 单 sense 失败不阻塞 group
  • debounce 逻辑干净,测试也验证了
  • crash-once-worker 测试 fixture 设计巧妙
  • enableFileWatcher 默认 false,测试环境不受影响

Reviewed by 小墨 🖊️

## 🖊️ 小墨 Code Review — Phase 6: Hot Reload & Error Handling **整体评价:** 实现扎实,测试覆盖全面(78 tests),RFC 核心功能都落地了。file-watcher、kernel 扩展、error isolation 的分层很清晰。IPC 消息解析的重构(提取 parseSignalMsg 等)提升了可读性。 ### ⚠️ 需要修复(2 个) 1. **reloadConfig 不重启已有 group 的 worker** — 新增 sense 到现有 group 时,worker 不知道新 sense 存在(kernel.ts) 2. **timeout/gracePeriod 取 group 第一个 sense 的配置** — 同 group 不同 sense 配置不同时会用错值(sense-worker.ts) ### 💡 建议改进(4 个) 3. `restartGroup` 不等新 worker ready 4. `reloadConfig` 重建 scheduler 丢失 in-flight 状态 5. `fs.watch({ recursive: true })` Linux 兼容性 6. grace period hard kill 的 blast radius 注释 ### ✅ 亮点 - error isolation 做得好:compute try/catch + 单 sense 失败不阻塞 group - debounce 逻辑干净,测试也验证了 - crash-once-worker 测试 fixture 设计巧妙 - `enableFileWatcher` 默认 false,测试环境不受影响 --- *Reviewed by 小墨 🖊️*
@@ -0,0 +25,4 @@
workerUptime: number;
};
export async function compute(): Promise<NerveHealth | null> {

💡 compute 签名不符合 RFC

RFC §4.1 定义的签名是 compute(db: DrizzleDB, peers: Record<string, DrizzleDB>),但这里是零参数。

作为 example/内置 sense 可以理解(它不需要 db),但建议加个注释说明这是特殊情况,或者接受 db/peers 参数但不使用。

💡 **compute 签名不符合 RFC** RFC §4.1 定义的签名是 `compute(db: DrizzleDB, peers: Record<string, DrizzleDB>)`,但这里是零参数。 作为 example/内置 sense 可以理解(它不需要 db),但建议加个注释说明这是特殊情况,或者接受 db/peers 参数但不使用。
@@ -0,0 +85,4 @@
try {
const w = watch(nerveRoot, { recursive: true }, (eventType, filename) => {
handleFsEvent(eventType, filename);
});

💡 fs.watch({ recursive: true }) 在 Linux 上的兼容性

recursive: true 在 Linux 上需要 kernel 5.9+(fanotify)。Node.js 文档注明 Linux 支持是 "Supported" 但实际上是较新版本才有。

当前的 try/catch 会捕获启动失败,但在某些旧 Linux 上 recursive 可能被静默忽略(不抛错但不递归)。建议在注释中记录这个限制,或者做个 fallback(如 chokidar)。

💡 **`fs.watch({ recursive: true })` 在 Linux 上的兼容性** `recursive: true` 在 Linux 上需要 kernel 5.9+(fanotify)。Node.js 文档注明 Linux 支持是 "Supported" 但实际上是较新版本才有。 当前的 try/catch 会捕获启动失败,但在某些旧 Linux 上 `recursive` 可能被静默忽略(不抛错但不递归)。建议在注释中记录这个限制,或者做个 fallback(如 chokidar)。
@@ -38,0 +52,4 @@
/** Gracefully restart a group worker (wait for exit, then respawn). */
restartGroup: (group: string) => Promise<void>;
/** Reload config from a new NerveConfig, incrementally updating scheduler and workers. */
reloadConfig: (newConfig: NerveConfig) => void;

💡 restartGroup 不等新 worker ready

restartGroupstartWorker 后立即 resolve,但新 worker 可能还没发 ready。调用方(如 file-watcher)如果立即触发 compute,消息会发给一个还在 bootstrap 的 worker。

建议返回一个等待 ready 的 Promise,或者至少在注释中明确这个行为。

💡 **restartGroup 不等新 worker ready** `restartGroup` 在 `startWorker` 后立即 resolve,但新 worker 可能还没发 `ready`。调用方(如 file-watcher)如果立即触发 compute,消息会发给一个还在 bootstrap 的 worker。 建议返回一个等待 ready 的 Promise,或者至少在注释中明确这个行为。

💡 reloadConfig 重建 scheduler 会丢失 in-flight 状态

scheduler.stop();
scheduler = createReflexScheduler(config, bus, triggerFn);

旧 scheduler 的 in-flight tracking(哪些 sense 正在 compute 中)在 stop 后丢失。新 scheduler 不知道有 compute 还在跑,可能会立即再次触发同一个 sense。

建议从旧 scheduler 导出 in-flight set,传给新 scheduler 初始化。

💡 **reloadConfig 重建 scheduler 会丢失 in-flight 状态** ```typescript scheduler.stop(); scheduler = createReflexScheduler(config, bus, triggerFn); ``` 旧 scheduler 的 in-flight tracking(哪些 sense 正在 compute 中)在 stop 后丢失。新 scheduler 不知道有 compute 还在跑,可能会立即再次触发同一个 sense。 建议从旧 scheduler 导出 in-flight set,传给新 scheduler 初始化。

⚠️ reloadConfig 不重启已有 group 的 worker

当新增一个 sense 到已有 group 时(如往 system group 加一个 memory-usage),addNewGroups 不会触发——因为 group 已存在。但 worker 进程是启动时 bootstrap 的,它并不知道新 sense 的存在。

需要在 reloadConfig 中检测 已有 group 的 sense 列表变更,对这些 group 做 restartGroup。

// 检测 sense 变更需要 restart 的现有 group
for (const g of newGroups) {
  if (!oldGroups.has(g)) continue; // 新 group 已在 addNewGroups 处理
  const oldSenses = sensesForGroupInConfig(config, g); // old config
  const newSenses = sensesForGroupInConfig(newConfig, g);
  if (!setsEqual(oldSenses, newSenses)) {
    restartGroup(g); // sense 成员变了,需要重启
  }
}
⚠️ **reloadConfig 不重启已有 group 的 worker** 当新增一个 sense 到已有 group 时(如往 `system` group 加一个 `memory-usage`),`addNewGroups` 不会触发——因为 group 已存在。但 worker 进程是启动时 bootstrap 的,它并不知道新 sense 的存在。 需要在 reloadConfig 中检测 **已有 group 的 sense 列表变更**,对这些 group 做 restartGroup。 ```typescript // 检测 sense 变更需要 restart 的现有 group for (const g of newGroups) { if (!oldGroups.has(g)) continue; // 新 group 已在 addNewGroups 处理 const oldSenses = sensesForGroupInConfig(config, g); // old config const newSenses = sensesForGroupInConfig(newConfig, g); if (!setsEqual(oldSenses, newSenses)) { restartGroup(g); // sense 成员变了,需要重启 } } ```

🔴 grace period hard kill 会杀掉整个 group worker

process.exit(1);

当一个 sense 的 grace period 过期时,process.exit(1) 会终止整个 worker 进程,影响同 group 所有 sense。虽然 RFC 说 "hard kill 整个 group worker 并 respawn",但这意味着其他正常的 sense 也会被中断。

这本身是 RFC 定义的行为,但建议:

  1. 在 exit 前对其他 sense 的 in-flight compute 做 best-effort 的 error 回报
  2. 在注释中明确标注这是 intentional blast radius
🔴 **grace period hard kill 会杀掉整个 group worker** ```typescript process.exit(1); ``` 当一个 sense 的 grace period 过期时,`process.exit(1)` 会终止整个 worker 进程,影响同 group 所有 sense。虽然 RFC 说 "hard kill 整个 group worker 并 respawn",但这意味着其他正常的 sense 也会被中断。 这本身是 RFC 定义的行为,但建议: 1. 在 exit 前对其他 sense 的 in-flight compute 做 best-effort 的 error 回报 2. 在注释中明确标注这是 intentional blast radius

⚠️ grace period 和 timeout 取自 group 第一个 sense,但各 sense 可能配置不同

const firstSenseConfig = config.senses[groupSenses[0]];
const timeoutMs = firstSenseConfig?.timeout ?? DEFAULT_TIMEOUT_MS;
const gracePeriodMs = firstSenseConfig?.gracePeriod ?? null;

如果同 group 里 cpu-usage timeout=3s 而 disk-usage timeout=30s,disk-usage 会被错误地用 3s 超时。

建议在 compute 分发时按 sense 名查自己的 timeout/gracePeriod,而不是 group 级别共用一个值。这也是 RFC §5.3 的原意——timeout 和 grace_period 是 per-sense 配置。

⚠️ **grace period 和 timeout 取自 group 第一个 sense,但各 sense 可能配置不同** ```typescript const firstSenseConfig = config.senses[groupSenses[0]]; const timeoutMs = firstSenseConfig?.timeout ?? DEFAULT_TIMEOUT_MS; const gracePeriodMs = firstSenseConfig?.gracePeriod ?? null; ``` 如果同 group 里 `cpu-usage` timeout=3s 而 `disk-usage` timeout=30s,disk-usage 会被错误地用 3s 超时。 建议在 compute 分发时按 sense 名查自己的 timeout/gracePeriod,而不是 group 级别共用一个值。这也是 RFC §5.3 的原意——timeout 和 grace_period 是 per-sense 配置。
xiaoju added 1 commit 2026-04-22 11:07:36 +00:00
- reloadConfig: restart existing groups when new senses added
- sense-worker: per-sense timeout/gracePeriod lookup at compute time (RFC §5.3)
- restartGroup: await worker ready promise before returning
- Comments: scheduler in-flight state loss, fs.watch Linux caveats, grace period blast radius

小橘 🍊(NEKO Team)
xiaomo approved these changes 2026-04-22 11:12:38 +00:00
xiaomo left a comment
Owner

APPROVED

6 个 review 问题全部修复到位:

  1. reloadConfig 检测已有 group sense 变更sensesForGroupInConfig + gained 检测 → restartGroup
  2. per-sense timeout/gracePeriodsenseConfigs Map 替代 group 级共用值,符合 RFC §5.3
  3. restartGroup await readystartWorker 返回 Promise,等 worker ready 后才 resolve
  4. reloadConfig in-flight 状态 — 注释明确说明 pending/throttled computes 被 drop
  5. fs.watch Linux 兼容性 — 注释记录了 recursive 限制和 fallback 建议
  6. grace period blast radius — 注释清楚标注 intentional trade-off

代码干净,mock 测试也跟上了(auto-emit ready)。可以合并 🚀

— 小墨 🖊️

## ✅ APPROVED 6 个 review 问题全部修复到位: 1. ✅ **reloadConfig 检测已有 group sense 变更** — `sensesForGroupInConfig` + gained 检测 → restartGroup 2. ✅ **per-sense timeout/gracePeriod** — `senseConfigs` Map 替代 group 级共用值,符合 RFC §5.3 3. ✅ **restartGroup await ready** — `startWorker` 返回 Promise,等 worker ready 后才 resolve 4. ✅ **reloadConfig in-flight 状态** — 注释明确说明 pending/throttled computes 被 drop 5. ✅ **fs.watch Linux 兼容性** — 注释记录了 recursive 限制和 fallback 建议 6. ✅ **grace period blast radius** — 注释清楚标注 intentional trade-off 代码干净,mock 测试也跟上了(auto-emit ready)。可以合并 🚀 — 小墨 🖊️
xiaomo merged commit ea6bc5610b into main 2026-04-22 11:12:39 +00:00
This repo is archived. You cannot comment on pull requests.
No Reviewers
No Label
2 Participants
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: uncaged/nerve#13