Skip to content

fix(compute): reap kernel workers + cap compute subagents (follow-up to #102 thread-cap)#182

Open
KB (KB-syntheticsciences) wants to merge 1 commit into
mainfrom
fix/compute-oom-followup
Open

fix(compute): reap kernel workers + cap compute subagents (follow-up to #102 thread-cap)#182
KB (KB-syntheticsciences) wants to merge 1 commit into
mainfrom
fix/compute-oom-followup

Conversation

@KB-syntheticsciences

@KB-syntheticsciences KB (KB-syntheticsciences) commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

Context

Follow-up hardening on top of the #102 fix already merged in main. #102 is closed — 060e1dd (#178) added pythonThreadCapEnv, which caps thread/worker fan-out inside one kernel. This PR does not re-open or re-litigate that; it keeps pythonThreadCapEnv and adds the OOM axes that cap doesn't cover (orphaned workers, parallel-kernel concurrency) plus a code-level scanpy guard where main currently relies on prompt advice.

Changes

Reap orphaned workers

  • The python and R kernels now spawn detached (own process group), and every kill path — startup timeout, cell timeout, abort, idle-reap, shutdown, and process-exit — routes through Shell.killTree / killTreeSync. So joblib/BLAS worker children die with the kernel instead of lingering.
  • The negative-pid group kill only fires when the child actually leads its own group (verified via /proc on Linux); a non-detached child is killed singly, so the group signal can never reach our own process group.

Bound concurrent compute subagents

  • A small counting semaphore caps how many kernel-running subagents (biology/ml/physics) run at once (OPENSCIENCE_MAX_COMPUTE_SUBAGENTS, default 2). Nested compute calls skip the cap so a small pool can't self-deadlock.
  • acquire(signal) is abort-aware: a subagent cancelled while still queued rejects and leaves the wait queue, so release() never hands a slot to a dead waiter.

Scanpy template guard

  • regress_out (densifies + forks one dense copy per worker) now sizes n_jobs to the worker copies that fit in available RAM and skips (with guidance) only when even one won't fit. Warns on a dense adata.X. regress_out stays enabled by default — this is pure sizing/skip safety, not a behavior change.

Why each helps beyond the merged thread-cap

  • Reaping: the thread-cap limits workers per kernel but never kills them on abort/timeout — a cancelled scanpy run leaves N tens-of-GB worker procs thrashing swap. Group-kill reaps them.
  • Semaphore: the thread-cap does nothing about the number of concurrent kernels. N parallel biology/ml subagents each holding a dataset copy still sum to an OOM — this was the subagent-multiplication mechanism diagnosed for bug #102. The semaphore serializes them.
  • Template guard: prompt advice ("avoid n_jobs=-1") relies on the model complying; code enforcement doesn't — which matters for exactly the weak models that OOM.

Testing

  • bun test — full suite green (1138 pass / 1 skip / 0 fail).
  • New: semaphore concurrency bound + FIFO wake + abort-while-queued (proves no slot leak / no lost slot).

Notes

  • Not verified end-to-end against a captured OOM dataset; the changes are conservative (guards + reaping, no happy-path change), safe to land as hardening.
  • killTreeSync/group-kill are best-effort; the group signal is guarded so it can't hit the wrong group.

Follow-up to the merged #102 thread-cap (#178) — #102 is already closed; this hardens the axes that fix didn't cover. Not intended to re-open the issue.

… regress_out (#102)

Complements the thread-cap already in main (pythonThreadCapEnv) with the axes it
doesn't cover:

- Reap orphaned workers: python/R kernels now spawn detached (own process group)
  and every kill path (timeout, abort, idle-reap, shutdown, process-exit) routes
  through Shell.killTree/killTreeSync, so joblib/BLAS worker children die with the
  kernel instead of lingering and thrashing swap. Group kill only fires when the
  child leads its own group (verified via /proc on Linux).
- Bound concurrent compute subagents (biology/ml/physics) with an abort-aware
  counting semaphore (OPENSCIENCE_MAX_COMPUTE_SUBAGENTS, default 2): N parallel
  subagent kernels each holding a dataset copy no longer sum to an OOM. A cancelled
  subagent still queued unblocks immediately and leaks no slot.
- Scanpy template: size regress_out's n_jobs to the worker copies that fit in RAM
  and skip (with guidance) only when even one won't fit; warn on a dense adata.X.
  regress_out stays enabled by default.

Full suite green (1138 pass).
@vercel

vercel Bot commented Jul 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
openscience Ready Ready Preview, Comment Jul 11, 2026 10:22am

Request Review

@KB-syntheticsciences KB (KB-syntheticsciences) changed the title fix(compute): reap kernel workers + cap compute subagents (#102) fix(compute): reap kernel workers + cap compute subagents (follow-up to #102 thread-cap) Jul 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant