Răsfoiți Sursa

fix(code-runtime-python): treat an absent start-time reading as reaped, not recycled

The PID-reuse guard refused to signal whenever the current reading differed
from the one taken at spawn, including when it was ABSENT. On Linux a reaped
leader has no /proc/<pid>/stat, so every teardown after the leader exited
skipped SIGTERM/SIGKILL while the group it led still held survivors -- the
exact case the process-group teardown exists to reap. Three same-group survivor
tests went red on the coverage lane; they pass on Darwin because the reader
always returns undefined there, leaving the guard inert.

Only a present-and-different reading now blocks the signal. Verified on the
self-hosted Linux box: a reaped leader with live survivors allows the signal, a
pid whose start time differs still blocks it, and a live matching process is
signalled.
Chinesezjc 1 lună în urmă
părinte
comite
2ad93da755

+ 12 - 3
packages/code-runtime/code-runtime-python/src/index.ts

@@ -1416,9 +1416,18 @@ export class PythonCodeRuntime extends CodeRuntime {
           if (child.pid === undefined) return
           // A pid alone cannot answer this: `process.kill(pid, 0)` succeeds just
           // as well for a REPLACEMENT process holding the recycled number. Only
-          // the start time distinguishes the two, so a reading that no longer
-          // matches means the group is not this run's and must not be signalled.
-          if (leaderStarted !== undefined && readProcessStart(child.pid) !== leaderStarted) return
+          // the start time distinguishes the two, so a reading that DISAGREES
+          // means the number now belongs to another process and must not be
+          // signalled.
+          //
+          // An ABSENT reading is the ordinary case, not a mismatch: once the
+          // leader is reaped its `/proc/<pid>/stat` is gone, while the group it
+          // led can still hold survivors that this teardown exists to reap. So
+          // only a present-and-different reading blocks the signal; undefined
+          // falls through, which is also the behavior on platforms with no
+          // `/proc` to read.
+          const nowStarted = readProcessStart(child.pid)
+          if (leaderStarted !== undefined && nowStarted !== undefined && nowStarted !== leaderStarted) return
           process.kill(-child.pid, sig)
         } catch {
           // ESRCH — the process already died. Nothing to do.

+ 7 - 1
packages/code-runtime/code-runtime-python/tests/runtime.spec.ts

@@ -399,7 +399,13 @@ describe('PythonCodeRuntime — process identity', () => {
       expect(own).toBeDefined()
       expect(readProcessStart(process.pid)).toBe(own)
       // Pid 0 is never a readable /proc entry, so the guard degrades to
-      // undefined rather than throwing on a teardown path.
+      // undefined rather than throwing on a teardown path. This is also the
+      // reading a REAPED leader produces -- its /proc entry is gone while the
+      // group it led can still hold survivors -- so `undefined` must NOT be
+      // treated as an identity mismatch. Reading it as one refused the SIGKILL
+      // that the same-group survivor tests depend on, which is why they went red
+      // on Linux while passing on Darwin (where the reader always returns
+      // undefined and the guard is inert).
       expect(readProcessStart(0)).toBeUndefined()
     } else {
       // Darwin has no /proc: the reader reports undefined, and `killGroup` then