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.