فهرست منبع

test(code-runtime-python): pin the reap-poll group-emptied arm with a dispose-timing case

The review's premise that the group-emptied arm could not be pinned was
incorrect; the same-group reap case already exercises it. This adds the missing
seam-observable case: dispose() while a setsid orphan holds the pipes and the
run is unresolved — settle kills the child, the group empties (the orphan is in
its own session), and the poll finalizes promptly instead of waiting out the
60 s grace. The v8 ignore on that arm is removed.
Chinesezjc 1 ماه پیش
والد
کامیت
bc08f405cc

+ 0 - 2
packages/code-runtime/code-runtime-python/src/index.ts

@@ -1825,8 +1825,6 @@ export class PythonCodeRuntime extends CodeRuntime {
         // final hard bound where nothing more can be done.
         let hardDeadline = 0
         const pollGroup = (): void => {
-          /* v8 ignore next -- the group-emptied arm needs the close-driven settle to win the
-           * race against the grace SIGKILL; no seam-observable test pins that interleaving. */
           if (groupEmpty()) {
             // The group is gone; the grace SIGKILL is moot. Cancel it (it may not
             // have fired yet) and finalize. graceTimer is always defined here:

+ 25 - 0
packages/code-runtime/code-runtime-python/tests/runtime.spec.ts

@@ -2952,6 +2952,31 @@ describe('PythonCodeRuntime — budgets, termination, disposal', () => {
     expect(still).toBe(true)
   }, 20_000)
 
+  it('dispose resolves promptly when the kill empties the group and an orphan holds the pipes', async () => {
+    // The group-emptied arm of the reap poll: dispose() drives settle, kill()
+    // kills the still-running child, and a setsid orphan holds the pipes open
+    // so close never fires — the poll must run, see the group empty (the
+    // orphan escaped into its own session), cancel the pending grace SIGKILL,
+    // and finalize immediately. A prompt resolve proves the arm ran
+    // (fail-before: dropping clearTimeout/finalize from that arm leaves dispose
+    // waiting for the never-firing grace escalation and blows the bound).
+    const { runtime, fiber } = await setup({ maxWallMs: 60_000, graceMs: 60_000 })
+    const start = Date.now()
+    const runPromise = runtime.run({
+      program: [
+        'import subprocess, sys, time',
+        'subprocess.Popen([sys.executable, "-c", "import time; time.sleep(30)"],',
+        '                 start_new_session=True)',
+        'time.sleep(30)',
+      ].join('\n'),
+      bindings: [],
+    })
+    await fiber.dispose()
+    const result = await runPromise
+    expect(result.error?.kind).toBe('abort')
+    expect(Date.now() - start).toBeLessThan(5_000)
+  }, 20_000)
+
   it('dispose awaits reaping of a same-group survivor from a completed run', async () => {
     // The quiescence contract also holds for a run that ALREADY resolved: the run
     // stays tracked in `live` until its process group is reaped, so a `dispose()`