mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-09 04:02:35 +00:00
fix(code-runtime-python): bound reply and call backlogs, snapshot binding metadata, and compact the reply queue
Review findings on the CPython backend: a child that never reads fd 3 leaves
the reply pipe full forever, so the drain loop waits on 'drain' while every
call frame it keeps sending resolves a binding and queues another reply —
the backlog (and the binding results it pins) would grow until the wall
clock. sendReply now caps the pending backlog at MAX_PENDING_REPLIES and
settles the run as worker-exit past it, mirroring the frame cap; a child
flooding calls against a binding that never settles would otherwise bypass
that cap (pendingReplies grows only after the await), so the dispatcher
counts in-flight binding calls before dispatch and releases the slot in the
async body's finally, capping outstanding closures at the same bound. The
drain also compacts its consumed prefix (replyQueue.splice(0, head)) once
head reaches the bound, so a drain that stays alive without emptying cannot
grow the backing store linearly with cumulative throughput.
The completion-value meter counted lone surrogates with
_SURROGATE.findall(folded), materializing one single-character string per
surrogate: a surrogate-dense value near the budget (millions of surrogates,
each serializing to six bytes) allocated millions of objects before the meter
returned, defeating the meter's counting-without-building contract. The count
is now the length difference between folded and the without string the meter
already computes; a standalone equivalence check confirms it matches findall
across lone-high, lone-low, paired, astral, and mixed cases.
validateBindings read namespace.global/errorClass.name/memberNameProperty
several times and retained the original errorClass object for the boot
frame, whose JSON.stringify re-read it after validation: a stateful getter
could throw or change between the two stages, turning the seam-misuse
rejection into a worker-exit or injecting an unvalidated name. Each field is
now read once into a plain value and the bindings map stores a plain
{ name, memberNameProperty } copy, so validation and the boot frame see
identical values.
Regression tests: a hostile child floods 5000 sequential valid calls without
reading fd 3 and the run settles worker-exit with the reply-queue message
before maxWallMs; a 3,000,000-surrogate completion succeeds at an
18,000,002-byte budget and reports output-limit one byte under; a 5000-call
flood against a never-settling binding settles worker-exit with the
call-backlog message; getter-backed namespace metadata that throws or
changes on a second read boots and runs with each field read exactly once; a
two-wave flood whose replies exceed the writable high-water mark drives the
drain past the compaction bound mid-delivery and verifies all 1524 replies
arrive. README Known Limitations gains the reply-backlog and call-backlog
bounds (en/zh, pairing re-recorded); a new Agent Note registers the findings.
This commit is contained in:
@@ -1725,10 +1725,19 @@ def _json_str_cost(text: str) -> int:
|
||||
except UnicodeEncodeError:
|
||||
pass
|
||||
folded = _SURROGATE_PAIR.sub(_combine_surrogate_pair, text)
|
||||
lone = len(_SURROGATE.findall(folded))
|
||||
# Remove the lone surrogates first, then count them as the length
|
||||
# difference: `_SURROGATE.findall(folded)` materialized one single-character
|
||||
# string PER surrogate, so a surrogate-dense value near the budget
|
||||
# (millions of lone surrogates, each serializing to six bytes) allocated
|
||||
# millions of objects before the meter returned -- an RLIMIT_AS death
|
||||
# surfacing as `exception` instead of the promised `output-limit`. After
|
||||
# pair-combining, every remaining surrogate is lone and exactly one code
|
||||
# point, so the removed length is the count, and the `without` string is
|
||||
# needed for the meter anyway.
|
||||
without = _SURROGATE.sub("", folded)
|
||||
lone = len(folded) - len(without)
|
||||
# Six ASCII bytes per lone surrogate; the remainder is ordinary text whose
|
||||
# own quotes are dropped here because the outer call adds them once.
|
||||
without = _SURROGATE.sub("", folded)
|
||||
return _json_string_cost(without.encode("utf-8")) + lone * 6
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user