From 7b9db83f86648b775f5a2dc8491c3ec0fd408629 Mon Sep 17 00:00:00 2001 From: Chinesezjc Date: Thu, 27 Aug 2026 02:13:53 +0800 Subject: [PATCH] fix(code-runtime-python): reset the inherited SIGXCPU disposition and mask at startup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reviewer's standing issue: the child inherits the host's SIGXCPU disposition and signal mask — if the host ignores or blocks SIGXCPU, the soft RLIMIT_CPU fires but cannot stop the child, and the hard limit's SIGKILL then classifies a definite CPU overrun as worker-exit instead of a timeout. The bootstrap now resets SIGXCPU to SIG_DFL and unblocks it before any model code runs (the settle-time enforcer already restores SIG_DFL for a program that traps or masks the signal mid-run; this closes the inherited-state gap). The zh README's outer wire section also gains the truncation-exception sentence to match the en side. --- .../code-runtime/code-runtime-python/README.i18n.yaml | 2 +- .../code-runtime/code-runtime-python/README.zh.md | 2 +- .../code-runtime/code-runtime-python/py/bootstrap.py | 11 +++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/code-runtime/code-runtime-python/README.i18n.yaml b/packages/code-runtime/code-runtime-python/README.i18n.yaml index c210ed082d..85ca81a637 100644 --- a/packages/code-runtime/code-runtime-python/README.i18n.yaml +++ b/packages/code-runtime/code-runtime-python/README.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/code-runtime/code-runtime-python/README.md README.md: 2e22a5122f891b8317e13ee395ce222805b01b29 -README.zh.md: 0f8773bc8e8292cfc3f0b09395d46ff75b17392c +README.zh.md: 028d2f5f7b57f1ca4da46557ba66511992227379 diff --git a/packages/code-runtime/code-runtime-python/README.zh.md b/packages/code-runtime/code-runtime-python/README.zh.md index 0f8773bc8e..028d2f5f7b 100644 --- a/packages/code-runtime/code-runtime-python/README.zh.md +++ b/packages/code-runtime/code-runtime-python/README.zh.md @@ -55,7 +55,7 @@ kind: "package-reference" ### wire 契约 -帧为 `boot`/`run`(宿主 → 子进程)与 `boot-ack`/`call`/`log`/`done` 加每个 call 一个 `reply`(子进程 → 宿主)。`log` 帧的 `truncated` 标志标记的就是子进程账本自己的截断标记帧,因此宿主在与子进程相同的点停止捕获,而不是从自己的预算推断。`log` 帧的 `open` 标志标记由显式 flush 提交的未结束行:宿主把下一个 log 帧合并进同一条目,因此 `print('a', end='', flush=True); print('b')` 读回为一条 `'ab'` 条目而不是假换行(拆分计费算术在 fd-3 协议 Agent Note 的 wire-contract 段)。合并的唯一例外是截断:当后续超预算帧触发账本时,已计费的前缀作为独立条目先提交,截断 marker 跟在后面(marker 保持末位,无重复计费)。`done.error.kind` 为 `exception`、`invalid-output`、`output-limit` 之一;墙钟/CPU 预算、中止与基底死亡在宿主侧观察,不以帧形式携带。 +帧为 `boot`/`run`(宿主 → 子进程)与 `boot-ack`/`call`/`log`/`done` 加每个 call 一个 `reply`(子进程 → 宿主)。`log` 帧的 `truncated` 标志标记的就是子进程账本自己的截断标记帧,因此宿主在与子进程相同的点停止捕获,而不是从自己的预算推断。`log` 帧的 `open` 标志标记由显式 flush 提交的未结束行:宿主把下一个 log 帧合并进同一条目,因此 `print('a', end='', flush=True); print('b')` 读回为一条 `'ab'` 条目而不是假换行(拆分计费算术在 fd-3 协议 Agent Note 的 wire-contract 段)。合并的唯一例外是截断:当后续超预算帧触发账本时,已计费的前缀作为独立条目先提交,截断 marker 跟在后面(marker 保持末位,无重复计费)。合并的唯一例外是截断:当后续超预算帧触发账本时,已计费的前缀作为独立条目先提交,截断 marker 跟在后面(marker 保持末位,无重复计费)。`done.error.kind` 为 `exception`、`invalid-output`、`output-limit` 之一;墙钟/CPU 预算、中止与基底死亡在宿主侧观察,不以帧形式携带。 ### 无损 JSON 跨越 diff --git a/packages/code-runtime/code-runtime-python/py/bootstrap.py b/packages/code-runtime/code-runtime-python/py/bootstrap.py index 3c3ec087dc..ad1f7c2dbc 100644 --- a/packages/code-runtime/code-runtime-python/py/bootstrap.py +++ b/packages/code-runtime/code-runtime-python/py/bootstrap.py @@ -1195,6 +1195,17 @@ async def _run(channel: ProtocolChannel) -> None: # can `except ToolCallError as e:` and read the member property. namespaces[declared["name"]] = error_class + # The child inherits the host's SIGXCPU disposition and signal mask. If + # the host ignores or blocks SIGXCPU, the soft RLIMIT_CPU fires but cannot + # stop the child — the hard limit's SIGKILL then classifies a definite CPU + # overrun as substrate death (worker-exit) instead of a timeout. Reset to + # the default disposition and unblock before any model code runs (the + # settle-time enforcer already restores SIG_DFL for a program that traps or + # masks the signal mid-run; this closes the inherited-state gap). + signal.signal(signal.SIGXCPU, signal.SIG_DFL) + if getattr(signal, "pthread_sigmask", None) is not None: + signal.pthread_sigmask(signal.SIG_UNBLOCK, (signal.SIGXCPU,)) + channel.send_sync({"type": "boot-ack"}) # 3. Start a reply-pump task before the run message: replies can arrive