Skip to content

fix(broker): wait for old process exit before forking idle-reclaim replacement (#3330) - #3343

Open
Battleplus wants to merge 4 commits into
makecindy:mainfrom
Battleplus:fix/3330-worker-idle-restart
Open

fix(broker): wait for old process exit before forking idle-reclaim replacement (#3330)#3343
Battleplus wants to merge 4 commits into
makecindy:mainfrom
Battleplus:fix/3330-worker-idle-restart

Conversation

@Battleplus

Copy link
Copy Markdown
Contributor

这次改了什么

摘要

修复 on-demand Node 插件 worker 空闲回收后二次启动超时的问题。

变更类型

  • Bug fix

范围

包含

  • nodeRuntimeBroker.tsstopWorker 增加 draining exit Promise,ensureWorker 在 fork 前等待旧进程退出
  • nodeRuntimeBroker.test.ts:新增 idle-stop-then-restart 回归测试

不包含

  • 不改变 resident worker 行为
  • 不改变 stopAndWait(原位更新)行为
  • 不改变 destroyAll 行为

根因

stopWorker 调用 kill('SIGTERM') 后不等待真实 exit,而 ensureWorker 只检查 workersstartingWorkers,不检查 liveProcesses。在 Windows 上,旧 UtilityProcess 可能仍持有文件锁或端口,导致新 bootstrap 10s 超时。

修复

  1. stopWorker:kill 后创建 draining exit Promise(监听 exit 事件 + 安全网超时)
  2. ensureWorker:fork 前检查 drainingExits,如有则 await 旧进程退出
  3. 遵循已有 stopAndWait 模式,但 scope 限定为 per-key idle reclaim

测试

cd apps/desktop && npx vitest run src/main/cindy-brain/__tests__/nodeRuntimeBroker.test.ts

55/55 通过,包含新增的 idle-stopped worker waits for old process exit before forking replacement 测试。

@Battleplus
Battleplus requested a review from a team as a code owner August 24, 2026 11:28
…eplacement (makecindy#3330)

When an on-demand Node worker is idle-stopped, stopWorker kills the
process but does not wait for actual exit. The next ensureWorker call
only checks workers and startingWorkers maps — not liveProcesses — so
it forks a new process while the old one is still shutting down.

On Windows, the old UtilityProcess may still hold file locks or ports
that prevent the new bootstrap from succeeding, causing a 10s timeout
and PROCESS_START_FAILED.

Fix: in stopWorker, record a draining exit Promise that resolves when
the child process actually exits (with a safety-net timeout). In
ensureWorker, before forking, check drainingExits and await the old
process exit. This follows the existing stopAndWait pattern but is
scoped to per-key idle reclaim.

Regression test: idle-stopped worker waits for old process exit before
forking replacement, verifying no new child is created until the old
one exits.

Signed-off-by: Battleplus <battleplus@users.noreply.github.com>
Signed-off-by: Battleplus <3559424769@qq.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 429b82caee

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/main/cindy-brain/nodeRuntimeBroker.ts Outdated
@greptile-apps

greptile-apps Bot commented Aug 24, 2026

Copy link
Copy Markdown

Greptile Summary

此 PR 为按需 Node 插件 worker 的空闲回收增加退出等待,避免旧进程尚未退出时立即 fork replacement。

  • stopWorker 为每个 key 记录有界的退出 Promise。
  • ensureWorker 在创建 replacement 前等待旧进程退出。
  • 新增空闲停止后重启的回归测试。

Confidence Score: 4/5

当前仍不宜合并,因为同一 key 的并发重启请求可能绕过启动去重并创建多个 replacement worker。

draining 完成后,代码先删除退出等待状态,之后才登记新的启动 Promise;在这一间隙进入的并发请求既看不到 draining 状态,也看不到进行中的启动,因而此前报告的重复 fork 问题仍然存在。

Files Needing Attention: apps/desktop/src/main/cindy-brain/nodeRuntimeBroker.ts

Important Files Changed

Filename Overview
apps/desktop/src/main/cindy-brain/nodeRuntimeBroker.ts 增加旧 worker 退出等待与超时安全网,但同 key 的并发 replacement 启动协调仍需处理。
apps/desktop/src/main/cindy-brain/tests/nodeRuntimeBroker.test.ts 新增单个重启请求等待旧进程退出的生命周期回归测试。

Sequence Diagram

sequenceDiagram
  participant R as 新请求
  participant B as NodeRuntimeBroker
  participant O as 旧 Worker
  participant N as Replacement Worker
  B->>O: SIGTERM
  B->>B: 记录 draining exit Promise
  R->>B: handleRequest
  B->>B: await draining
  O-->>B: exit
  B->>N: fork replacement
  N-->>B: spawn / ready
  B-->>R: 返回结果
Loading

Reviews (4): Last reviewed commit: "fix(broker): fix TS errors in idle-recla..." | Re-trigger Greptile

Comment thread apps/desktop/src/main/cindy-brain/nodeRuntimeBroker.ts Outdated
@Battleplus
Battleplus force-pushed the fix/3330-worker-idle-restart branch from 429b82c to 2c5fe27 Compare August 24, 2026 11:31

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2c5fe27469

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/main/cindy-brain/nodeRuntimeBroker.ts
@MagicLizi MagicLizi added awaiting-discussion 等待维护者讨论(review-pr) touches:plugin-base 改动碰到插件基座(review-pr 自动维护,仅展示) labels Aug 24, 2026
@MagicLizi

Copy link
Copy Markdown
Contributor

这个 PR 改了插件 Node worker 的回收/再启动,属于插件基座,已进入维护者确认(基座改动不因 bugfix 豁免)。

请维护者在本 PR 上 Approve;需要修改请 Request Changes。讨论 issue:#3350

…d stuck processes

Two P1 race conditions from Codex review:

1. Draining fallback resolves even when old process is still alive (after
   PROCESS_STOP_GRACE_MS + 1000ms timeout), allowing ensureWorker to fork
   a replacement while the old process holds file locks / ports. Now rejects
   instead, so the caller gets an error rather than a collision.

2. drainingExits.delete() was called before await draining, so a second
   concurrent ensureWorker call would see no draining marker and fork
   immediately. Now the delete happens after the await, so concurrent
   requests share the same drain wait.

Fixes P1 from Codex review on makecindy#3343.

Signed-off-by: Battleplus <3559424769@qq.com>
Comment thread apps/desktop/src/main/cindy-brain/nodeRuntimeBroker.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b57a6d0ee5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/main/cindy-brain/nodeRuntimeBroker.ts Outdated
Comment thread apps/desktop/src/main/cindy-brain/nodeRuntimeBroker.ts
Replace `removeListener` (not in NodeWorkerProcess interface) with
`invoked` flag pattern. Replace `reject` (not declared in Promise
constructor) with `resolve` — the draining promise must always resolve
so the replacement fork can proceed.

Fixes type errors from commit b57a6d0.

Signed-off-by: Battleplus <3559424769@qq.com>

@MagicLizi MagicLizi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

格式门未通过

  • Description 缺段落:怎么验证的 / 风险

请按仓库 PR 模板补全这两段后再请求审查。这是 P1 格式问题,补全前本流程不会进入代码审查。

@MagicLizi MagicLizi added status:awaiting-author 等作者修改(review-pr 自动维护,仅展示) and removed awaiting-discussion 等待维护者讨论(review-pr) labels Aug 25, 2026
Close the concurrent restart window so callers share one replacement startup promise, and add a deterministic regression test.
@MagicLizi MagicLizi added the awaiting-discussion 等待维护者讨论(review-pr) label Aug 27, 2026
@MagicLizi

Copy link
Copy Markdown
Contributor

@Battleplus 👋 这个 PR 现在在等维护者确认,确认之前流程不会合并它 —— 不是卡住了,也不是在等你再改一版(你推的改动流程都读到了,判的就是最新一版代码)。

  • 在拦的是:维护者确认门(插件基座改动(影响全部已装插件))。
  • 讨论 issue:维护者确认:#3343 插件 Node worker 空闲回收后再启动 #3350
  • 通过方式只有一个:维护者在本 PR 上 Approve。维护者觉得要改会直接 Request Changes,那时候球才回到你手里。
  • 这期间如果还有 review 意见没处理完、CI 没过,照常修就行,不影响这条等待。

这条是流程自动发的状态提醒(同一版代码只发一次),不用回复。

@MagicLizi MagicLizi added touches:core 改动碰到架构核心路径(review-pr 自动维护,仅展示) and removed status:awaiting-author 等作者修改(review-pr 自动维护,仅展示) labels Aug 27, 2026
@MagicLizi
MagicLizi dismissed their stale review August 27, 2026 17:13

这些问题已在当前 head 的后续 commit 中修复(格式门/安全门已通过),自动 dismiss 旧的 CHANGES_REQUESTED 以解除合并阻塞。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting-discussion 等待维护者讨论(review-pr) touches:core 改动碰到架构核心路径(review-pr 自动维护,仅展示) touches:plugin-base 改动碰到插件基座(review-pr 自动维护,仅展示)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants