mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-09 04:02:35 +00:00
fix(code-runtime-python): drop a late binding resolution before snapshotting it
`sendReply` already refuses to write after the run settled, but only after `snapshotJsonValue` walked and copied the resolution. Binding resolution carries no seam-level byte cap, so a binding resolving a wide value after `maxWallMs`, an abort, or dispose settled the run spent host heap building a frame that was then discarded. The check moves ahead of the snapshot. Also in this change: - `readProcessStart` moved after `messageOf`. Inserting it between `messageOf`'s JSDoc and its body left that function undocumented and the orphaned block reading as a second doc for the reader; `verify-export-jsdoc` does not catch it because `messageOf` is not exported. - The README pair adds the disposed-runtime rejection to `run()`'s public contract, which `src/index.ts` has enforced all along. - Known Limitations records three deferred constraints that until now existed only in review discussion: the combined log-and-value peak the load gate does not model, the host-side per-member expansion of a wide binding reply (owned by `packages/core/session`, and shared with the worker-thread backend), and the absence of fd-3 backpressure for concurrent replies. - The Agent Note's same-group section records the teardown identity guard and its two rulings, including why an ABSENT start-time reading proceeds rather than withholding the signal, and that reading it as a mismatch is what turned the three same-group heartbeat cases red on Linux.
This commit is contained in:
@@ -316,6 +316,17 @@ const GROUP_REAP_POLL_MS = 50
|
||||
* @returns The value's message or string form; a fixed placeholder when its own
|
||||
* conversion throws.
|
||||
*/
|
||||
function messageOf(error: unknown): string {
|
||||
try {
|
||||
return String(error instanceof Error ? error.message : error)
|
||||
} catch {
|
||||
// Swallows only a throw from the value's own `message` getter or string
|
||||
// conversion. Nothing else runs inside the try, and the placeholder is a
|
||||
// literal, so this cannot throw again.
|
||||
return '<unrenderable rejection value>'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A process's start time, as the identity half of (pid, started).
|
||||
*
|
||||
@@ -347,17 +358,6 @@ export function readProcessStart(pid: number): string | undefined {
|
||||
}
|
||||
}
|
||||
|
||||
function messageOf(error: unknown): string {
|
||||
try {
|
||||
return String(error instanceof Error ? error.message : error)
|
||||
} catch {
|
||||
// Swallows only a throw from the value's own `message` getter or string
|
||||
// conversion. Nothing else runs inside the try, and the placeholder is a
|
||||
// literal, so this cannot throw again.
|
||||
return '<unrenderable rejection value>'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve `pythonBin` to an absolute path against the CURRENT process `PATH`,
|
||||
* BEFORE the child spawns with an empty environment. A basename (the default
|
||||
@@ -1347,6 +1347,14 @@ export class PythonCodeRuntime extends CodeRuntime {
|
||||
void (async () => {
|
||||
try {
|
||||
const resolved = await fn(message.args)
|
||||
// Drop a reply the run no longer needs BEFORE snapshotting it.
|
||||
// `sendReply` also checks `settled`, but only after this value has
|
||||
// been walked and copied: a binding that resolves a wide value
|
||||
// after `maxWallMs`, an abort, or dispose already settled the run
|
||||
// would spend host heap on a frame that is then discarded, and
|
||||
// binding resolution carries no seam-level byte cap to bound it.
|
||||
// oxlint-disable-next-line typescript/no-unnecessary-condition -- the run can settle while this binding is awaited.
|
||||
if (settled) return
|
||||
// The seam requires a lossy resolution to REJECT descriptively,
|
||||
// not silently coerce: a raw JSON.stringify would turn NaN/
|
||||
// Infinity into null and drop undefined fields. Snapshot through
|
||||
|
||||
Reference in New Issue
Block a user