Skip to content

fix(runtime): settle Pi RPC shutdown on close when the spawn itself failed - #219

Merged
eddiearc merged 2 commits into
eddiearc:mainfrom
itswl:fix/pi-rpc-settle-shutdown-on-close
Sep 13, 2026
Merged

eddiearc merged 2 commits into
eddiearc:mainfrom
itswl:fix/pi-rpc-settle-shutdown-on-close

Conversation

@itswl

@itswl itswl commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

What

PiRpcClient.beginShutdown() waits on the child's exit event. Node emits error + close and never exit when the spawn itself fails, so a failed pi spawn made close() take ~4 seconds (two shutdownGraceMs timers, since kill() on a failed spawn returns false) — and with nothing else keeping the event loop alive, the promise never settled at all. this.exited was also only set in the exit handler, so the existing fast-path (if (this.exited) return Promise.resolve()) could not trigger.

Change

  • src/runtime/pi-rpc-client.ts: treat close as a terminal event alongside exit — both in the constructor's termination handler (this.exited + fail-closed) and in beginShutdown() (finish on either event). Normal exitclose sequences and the SIGTERM→SIGKILL escalation for stubborn children are unchanged.
  • src/runtime/runtime-adapters.ts: the local ProcessLike child-process contract gains the matching close overload.
  • test/unit/runtime/pi-rpc-client.test.mjs: FailedSpawnProcess double (emits error + close, no exit, kill() returns false) asserting shutdown settles in under a second instead of waiting out the grace timers.

Validation

  • Repro before the change (real pi binary, nonexistent cwd, live event loop): t=3ms request rejected: Pi RPC process failed: spawn <path> ENOENTt=9ms child close code=-2t=4003ms close resolved. After the change, the catalog call fails in 50ms with the same error.
  • bun run build
  • bun run typecheck
  • bun run licenses:check ✅ · bun run publication:check:tree
  • bun test test/unit/runtime/pi-rpc-client.test.mjs test/unit/runtime/pi-model-catalog.test.mjs → 21 pass / 0 fail
  • bun test test/unit/runtime/ → 232 pass / 0 fail
  • bun run test:unit → 896 pass / 1 skip / 1 fail. The remaining failure, three-Agent live acceptance is opt-in, hermetic by default, and fixture-verifiable, reproduces on unmodified main in this environment and is unrelated to this change; a one-off real dashboard … SIGINT timeout in an earlier run did not recur and passes standalone in ~320 ms.

Fixes #218

🤖 Generated with Claude Code

…ailed

Node emits "error" + "close" (never "exit") when a spawn fails, so
beginShutdown() waited out both shutdownGraceMs timers — and with nothing
else keeping the loop alive, close() never settled at all. Treat "close"
as a terminal event alongside "exit" so failed spawns settle promptly,
keeping the SIGTERM/SIGKILL escalation for stubborn children unchanged.

Co-Authored-By: Claude Code <noreply@anthropic.com>
@itswl

itswl commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

补充一条真实场景的耗时证据:在 0.5.7 的真实 setup 里,pi 探测因 spawn 失败(cwd 缺失)而失败时,调用方等了 4020ms 才拿到错误——正是 beginShutdown 等不到 exit、由两个 shutdownGraceMs 定时器兜底结算所致;进程内还有其它句柄(setup 交互),所以这次不是永不 settle,而是固定 4 秒延迟。

@eddiearc eddiearc left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

MERGE-WITH-NITS(Fable 5.1 审合)

Node 失败 spawn 只有 error+close、没有 exit;shutdown 只等 exit 会拖满 ~4s grace。把 close 也当终态是对症修复,FailedSpawnProcess 用例到位。SIGTERM→SIGKILL 路径未动。

请先于 #224 合(同改 pi-rpc-client + 测末尾)。缺 package.json bump,跟整批 release 策略一起定即可。

@eddiearc
eddiearc merged commit 127ac1b into eddiearc:main Sep 13, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Pi RPC 在 spawn 失败时 shutdown 等 "exit"(Node 只发 "error"+"close"):close() 延迟约 4 秒,孤立进程里永不 settle

2 participants