fix(code-runtime-python): close the log-fragment OOM, CPU classification, and done-send transitive-dependency findings

Addresses the bot's v16 review on the settlement-path code:
- critical: _LogStream._pending now seals the fragment list past a chunk cap
  (like the host captureStray seal), so a newline-free single-character drip no
  longer accumulates one list slot per write and OOMs on its own accounting.
- _clamped lowers a soft==hard result by one unit (when hard >= 2) so a
  dual-limit ulimit -t leaves SIGXCPU a window to fire and a definite CPU
  overrun is reported as a timeout, not a worker-exit.
- send_done wraps its encode+write in a try and, on any throw from a rebound
  transitive name (_dump_scalar/os), writes a fixed pre-encoded done frame via
  the import-time captured os.write, so a settled exception verdict is never
  downgraded to worker-exit.
- drainReplies clears the consumed replyQueue slot so a wide written payload is
  released immediately, bounding host memory to the current backlog under
  sustained fd-3 backpressure.
Tests added for each (fragment cap drip, dual-limit CPU overrun, transitive-name
rebind done frame).
This commit is contained in:
Chinesezjc
2026-08-31 14:33:31 +08:00
committed by Tianyi Cui
parent add4a2fb6f
commit dcbce50ec2
3 changed files with 167 additions and 6 deletions
@@ -1486,8 +1486,15 @@ export class PythonCodeRuntime extends CodeRuntime {
// bindings awaiting fd 3's `drain` can queue many frames, and each
// `shift()` re-slices the remaining array (O(n) per pop, O(n²) over
// the whole drain). A head cursor keeps the cost linear; the `finally`
// below discards everything consumed once the drain ends.
// below discards everything consumed once the drain ends. The consumed
// slot is CLEARED here (not just advanced past) so a wide payload the
// pipe has already taken is released immediately: under sustained
// backpressure the drain loop can live across many `await drain`
// ticks, and leaving the slot set would pin the written value's bytes
// in `replyQueue` for the whole busy period, making host memory grow
// with cumulative processing rather than the current backlog.
const payload = replyQueue[head] as ReplyMessage
replyQueue[head] = undefined as unknown as ReplyMessage
head += 1
// Encode inside the loop, not up front: a queued reply the run no
// longer needs is dropped by the `settled` check above without ever