mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-11 04:00:38 +00:00
fix(code-runtime-python): clear stray buffers on truncation and drain the original std streams
Addresses the review's two carried warnings and the comment suggestion: - Once the ledger truncates, every arm that marks it (admit()'s two ceilings and the child-marker frame arm) now clears both stray pipes' buffered output wholesale, so the end-path flushStray sees empty buffers instead of concat+decoding doomed data near a 256 MiB maxLogBytes; captureStray's newline loop re-checks the flag before re-retaining the residual. - The child runs with -u (unbuffered), so sys.__stdout__/sys.__stderr__ writes are visible to stray capture immediately; the settlement flush still drains the original std streams before the done frame as a guard. A regression test writes through sys.__stdout__/sys.__stderr__ without an explicit flush and asserts both bytes land in logs. C-ext stdio remains an accepted residual, recorded in the README Known Limitations (en + zh). - The ledger-comment arithmetic now states the exact boundary (serializes to exactly maxLogBytes; without the reserved byte it would be maxLogBytes + 1) in both host and child. Note (en + zh) registers the stray-clear and -u/settlement-drain mechanisms and the new test; pairings re-recorded; corpus passes 1029.
This commit is contained in:
@@ -4304,6 +4304,31 @@ describe('PythonCodeRuntime — hostile peer', () => {
|
||||
expect(result.logs.join('')).toContain('stray stderr')
|
||||
})
|
||||
|
||||
it('flushes bytes written through sys.__stdout__/sys.__stderr__ before the done frame', async () => {
|
||||
// The bootstrap only replaces sys.stdout/sys.stderr with the _LogStream;
|
||||
// sys.__stdout__/sys.__stderr__ are the original block-buffered wrappers
|
||||
// over fd 1/2. A program that writes through them without an explicit flush
|
||||
// would lose those bytes when the host SIGTERMs the child right after the
|
||||
// done frame (the default SIGTERM disposition terminates without
|
||||
// interpreter finalization). The settlement flush now drains the original
|
||||
// std streams before sending the done frame, so the bytes land in the
|
||||
// kernel pipe buffer and the host's stray capture records them.
|
||||
const { runtime } = await setup()
|
||||
const result = await runtime.run({
|
||||
program: [
|
||||
'import sys',
|
||||
'sys.__stdout__.write("orig stdout\\n")',
|
||||
'sys.__stderr__.write("orig stderr\\n")',
|
||||
'return "done"',
|
||||
].join('\n'),
|
||||
bindings: [],
|
||||
})
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.value).toBe('done')
|
||||
expect(result.logs.join('')).toContain('orig stdout')
|
||||
expect(result.logs.join('')).toContain('orig stderr')
|
||||
}, 15_000)
|
||||
|
||||
it('escalates to SIGKILL when the program traps SIGTERM and ignores the grace period', async () => {
|
||||
// A program that traps SIGTERM should still die: the kill() escalation
|
||||
// fires SIGKILL after graceMs. The full run reports either timeout (wall)
|
||||
|
||||
Reference in New Issue
Block a user