test(code-runtime-python): drive the inherited SIGXCPU path with a wrapper; fix the zh outer wire sentence

The review's two follow-ups on the inherited-SIGXCPU fix: (1) a discriminating
case — pythonBin points at a wrapper that ignores SIGXCPU before exec'ing
python3, so the child genuinely inherits the ignore; with cpuSeconds: 1 the
busy loop must end as timeout (the bootstrap reset restored SIG_DFL), and
reverting the reset leaves it running to the wall — verified red. (2) The zh
README's OUTER wire section now carries the truncation-exception sentence
(the previous commit had duplicated it in the inner section instead); the
duplicate is removed, and the settlement note registers the inherited-SIGXCPU
reset.
This commit is contained in:
Chinesezjc
2026-08-31 15:05:23 +08:00
committed by Tianyi Cui
parent 7b9db83f86
commit c4fe0320fb
6 changed files with 28 additions and 8 deletions
@@ -1,4 +1,4 @@
import { existsSync, readdirSync, realpathSync, statSync } from 'node:fs'
import { existsSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from 'node:fs'
import { mkdtemp, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { basename, dirname, join } from 'node:path'
@@ -638,6 +638,26 @@ describe('PythonCodeRuntime — inherited resource limits', () => {
expect(result.value).toBeUndefined()
}, 20_000)
it('reports a timeout when the interpreter was started with SIGXCPU ignored (inherited state)', async () => {
// The child inherits the host's SIGXCPU disposition: a wrapper that
// ignores SIGXCPU before exec'ing python3 hands the child a soft
// RLIMIT_CPU that cannot stop it. The bootstrap resets SIGXCPU to SIG_DFL
// before model code runs, so a busy loop still ends as a timeout rather
// than running to the hard limit and being misclassified as worker-exit.
const wrapper = join(tmpdir(), `dsh-xcpu-ignore-${process.pid}.sh`)
writeFileSync(wrapper, '#!/bin/sh\ntrap "" XCPU\nexec python3 "$@"\n', { mode: 0o755 })
try {
const { runtime } = await setup({ maxWallMs: 30_000, cpuSeconds: 1, pythonBin: wrapper })
const result = await runtime.run({
program: ['while True: pass'].join('\n'),
bindings: [],
})
expect(result.error?.kind).toBe('timeout')
} finally {
rmSync(wrapper, { force: true })
}
}, 20_000)
it('reports a timeout when a program traps AND masks SIGXCPU and returns past the soft limit', async () => {
// The mask-only case exercises the unblock; the trap+mask combination is
// the harder one: a program that installed a custom handler AND masked the