docs(code-runtime-python): drop the placebo dispose test and finish the remaining doc drift

The review showed the added dispose case was a placebo (dispose in the same
tick as run means SIGTERM hits the group before the program body runs; the
group-emptied arm is already deterministically covered by the same-group
survivor case, which this removes the v8 ignore for). The test is deleted; the
stale silently-discards comment in the boundary test now says rejects; the
README Known Limitations gains the late-log-frame-drop and host-side
binding-value-memory entries. Pairing re-recorded.
This commit is contained in:
Chinesezjc
2026-08-31 15:02:38 +08:00
committed by Tianyi Cui
parent bc08f405cc
commit 666ff2855e
4 changed files with 9 additions and 29 deletions
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# 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: 3d2b7631393a08d1980fc0a3fdf9c89e73077593
README.zh.md: f91d712b947dcd6b57651f7744c5f49754190019
README.md: 557ff7f58b70fcbc5cd9d7901c0c6b66d123c0eb
README.zh.md: d98fdcc52a05541c66e050445ebc04264686451b
@@ -112,6 +112,8 @@ These limits define what the package does and does not cover; they are current p
- **The cross-language guard covers the executed surfaces and the frame field shapes, not the field types** — the mirror e2e compares required/optional field sets, not that `cpuSeconds` is an `int` on both sides; a type-level drift is caught by review plus the backend's real-subprocess suite.
- **A descendant that escapes the child's process group with `setsid()` is not reaped by the group teardown** — `kill(-pid)` cannot reach it; the run still settles on the value the done frame decided, and the close-deadline backstop forces settlement if the orphan holds the pipes open, but the orphan itself outlives the fiber until it exits on its own.
- **A `log` frame that arrives after settlement is dropped** — once the run has settled, host-side capture is closed; a late fd-3 `log` frame (from a thread that outlived the done frame) is discarded rather than appended to `logs`.
- **The host-side memory bound for a binding VALUE is the frame parse cap, not a seam budget** — a binding reply's value is rebuilt host-side and billed against `maxValueBytes`; an intermediate binding value has no seam-level byte cap and is bounded by the lossless-JSON serialization cost and process memory (see the binding-argument entry).
- **`run()` is one-shot** — `logs` become available only after `CodeRunResult` resolves; there is no streaming-log or progress interface for output produced by a running program.
- **No state persists across runs** — every request executes in a fresh subprocess; a persistent REPL-style kernel stays deferred until a backend brings its own logging scheme.
- **An fd-3 frame whose raw length exceeds 64 MiB settles the run as a worker-exit** — `maxLogBytes`/`maxValueBytes` are load-bounded to the same parser cap so an honest child's frames always fit; a model-constructed binding ARGUMENT above 64 MiB (a value with no seam-level budget) trips the same cap — an accepted residual of the OOM guard.
@@ -112,6 +112,8 @@ kind: "package-reference"
- **跨语言 guard 覆盖执行的表面与帧字段形状,而非字段类型**——mirror e2e 比较必填/可选字段集,而非 `cpuSeconds` 在两侧是否都是 `int`;类型级漂移由评审加后端的真实子进程套件捕获。
- **以 `setsid()` 逃出子进程组后代不被组拆卸回收**——`kill(-pid)` 够不到它;运行仍按 done 帧决定的值结算,若该孤儿持有管道,close 截止兜底会强制结算,但孤儿本身在自行退出前一直存活到 fiber 之外。
- **结算后到达的 `log` 帧被丢弃**——运行一旦结算,宿主侧捕获即关闭;迟到的 fd-3 `log` 帧(来自比 done 帧存活更久的线程)会被丢弃,而不是追加到 `logs`
- **binding 值的宿主侧内存界是帧解析上限,而非 seam 预算**——binding 回复的值在宿主侧重建并按 `maxValueBytes` 计费;中间 binding 值没有 seam 级字节上限,受无损 JSON 序列化成本与进程内存约束(见 binding 实参条目)。
- **`run()` 是一次性的**——`logs` 只有在 `CodeRunResult` resolve 后才能获得;没有为运行中程序产生的输出提供流式日志或进度接口。
- **运行之间不保留状态**——每次请求都在全新子进程中执行;持久 REPL 风格内核在某个后端带来自己的日志方案之前保持延期。
- **原始长度超过 64 MiB 的 fd-3 帧会让本次运行以 worker-exit 结算**——`maxLogBytes`/`maxValueBytes` 在加载期被限制到同一解析器上限,因此诚实子进程的帧总能放得下;模型构造的超过 64 MiB 的 binding 实参(一个在 seam 层没有预算的值)会触发同一上限——这是该 OOM 防护的已接受残余。
@@ -110,9 +110,9 @@ describe('PythonCodeRuntime — seam descriptors and misuse', () => {
// bytes, so a payload occupies at most `cap + envelope` on the wire; the
// bound is `parse-cap - envelope`, not `(ceiling - envelope) / 6` (that
// divided in escape expansion the charge already counts). The receive path
// drops raw frames past the 64 MiB parse cap before decoding, so a budget
// above it would admit a config whose honest child frames the host then
// silently discards.
// rejects raw frames past the 64 MiB parse cap (the run settles as a
// worker-exit), so a budget above it would admit a config whose honest
// child frames the host then rejects.
const admissible = 64 * 1024 * 1024 - 64
const ctx = new Context()
await expect(ctx.plugin(PythonCodeRuntime, { maxLogBytes: admissible + 1 }))
@@ -2952,30 +2952,6 @@ describe('PythonCodeRuntime — budgets, termination, disposal', () => {
expect(still).toBe(true)
}, 20_000)
it('dispose resolves promptly when the kill empties the group and an orphan holds the pipes', async () => {
// The group-emptied arm of the reap poll: dispose() drives settle, kill()
// kills the still-running child, and a setsid orphan holds the pipes open
// so close never fires — the poll must run, see the group empty (the
// orphan escaped into its own session), cancel the pending grace SIGKILL,
// and finalize immediately. A prompt resolve proves the arm ran
// (fail-before: dropping clearTimeout/finalize from that arm leaves dispose
// waiting for the never-firing grace escalation and blows the bound).
const { runtime, fiber } = await setup({ maxWallMs: 60_000, graceMs: 60_000 })
const start = Date.now()
const runPromise = runtime.run({
program: [
'import subprocess, sys, time',
'subprocess.Popen([sys.executable, "-c", "import time; time.sleep(30)"],',
' start_new_session=True)',
'time.sleep(30)',
].join('\n'),
bindings: [],
})
await fiber.dispose()
const result = await runPromise
expect(result.error?.kind).toBe('abort')
expect(Date.now() - start).toBeLessThan(5_000)
}, 20_000)
it('dispose awaits reaping of a same-group survivor from a completed run', async () => {
// The quiescence contract also holds for a run that ALREADY resolved: the run