From da38c1691284a3776cd69b7dccab9a11cf066eb5 Mon Sep 17 00:00:00 2001 From: Chinesezjc Date: Thu, 20 Aug 2026 12:44:02 +0800 Subject: [PATCH] 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. --- .../code-runtime/code-runtime-python/src/index.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/code-runtime/code-runtime-python/src/index.ts b/packages/code-runtime/code-runtime-python/src/index.ts index 00e5da63fb..855a04b0cf 100644 --- a/packages/code-runtime/code-runtime-python/src/index.ts +++ b/packages/code-runtime/code-runtime-python/src/index.ts @@ -1475,13 +1475,20 @@ export class PythonCodeRuntime extends CodeRuntime { const drainReplies = async (): Promise => { 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.