fix(code-runtime-python): drain the reply queue by head cursor, not shift()

Each shift() re-slices the remaining array, so draining a large gather of
wide bindings awaiting fd 3's drain was O(n^2). Reading by a head index into
the array keeps the drain linear; the finally still discards everything.
This commit is contained in:
Chinesezjc
2026-08-31 14:33:31 +08:00
committed by Tianyi Cui
parent e0e1aa307d
commit da38c16912
@@ -1475,13 +1475,20 @@ export class PythonCodeRuntime extends CodeRuntime {
const drainReplies = async (): Promise<void> => {
if (draining) return
draining = true
let head = 0
try {
while (replyQueue.length > 0) {
while (head < replyQueue.length) {
// Needs the run to settle between two queued frames. Measured queue
// depths reach 11 without the wall clock landing inside that window.
/* v8 ignore next -- see above; not schedulable from a test. */
if (settled) break
const payload = replyQueue.shift() as ReplyMessage
// Read by index, not `shift()`: a large `asyncio.gather` of wide
// 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.
const payload = replyQueue[head] 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
// being serialized.