Skip to content

feat(core): add memory-aware worker spawn gate#1246

Merged
fi3ework merged 2 commits into
mainfrom
tree/dev1
May 14, 2026
Merged

feat(core): add memory-aware worker spawn gate#1246
fi3ework merged 2 commits into
mainfrom
tree/dev1

Conversation

@fi3ework

Copy link
Copy Markdown
Member

Background

@rstest/core's worker pool scaled purely by CPU count, so low-RAM hosts (CI containers, smaller dev machines) could OOM the device when worker RSS piled up, or trip a V8 heap-limit crash in the host when many in-flight IPC payloads landed at once. Closes #1160.

Implementation

  • New MemoryGate consulted in Pool.acquireRunner before each fresh spawn; defers when system memory or main-process heap headroom can't safely host another worker.
  • Hot path stays free: cached os.freemem() fast lane (>= P90(rss) × 3) short-circuits; heap-delta tracking is lazily enabled only after the slow lane first fires.
  • Cold-start window is intentionally undefended — first workers spawn at full speed, their RSS reports back-feed the gate. Emergency kill switch via RSTEST_MEMORY_AWARE=0.
  • Gate owns its own poll timer + worker-message subscription so PoolRunner is untouched.
acquireRunner ─► maxWorkers cap ─► MemoryGate.canSpawnNewWorker ─► spawn
                                          │
                                          ├─ deadlock guard (activeCount == 0)
                                          ├─ cold-start bypass (no samples)
                                          ├─ fast lane: freemem ≥ P90(rss) × 3
                                          └─ slow lane:
                                                ├─ MemAvailable ≥ P90(rss)
                                                └─ heap_headroom ≥ P90(heapΔ) × 1.5

User Impact

Tests now defer worker spawns under memory pressure instead of OOM-ing. No public API or default-behavior changes when memory is plentiful.

Related Links

Closes #1160

Worker pool now consults a MemoryGate before spawning a fresh runner.
Defends against device OOM (system memory exhaustion across workers)
and main-process heap OOM (V8 heap overrun from in-flight IPC payloads).

Hot path stays unchanged when memory is plentiful: a cached freemem
fast lane short-circuits the gate, and per-dispatch heap tracking is
lazily enabled only after the slow lane first triggers. Cold-start
sample window is intentionally undefended — first batch of workers
spawns at full speed, samples back-feed the gate for subsequent
spawns.

PoolRunner is untouched: MemoryGate subscribes to the PoolWorker
message stream directly. Pool only owns a small `tryWakeGateWaiter`
callback; the poll timer lives in MemoryGate. Emergency kill switch
via `RSTEST_MEMORY_AWARE=0` env var.

Closes #1160

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b0f09c8886

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/core/src/pool/memoryGate.ts Outdated
@github-actions

github-actions Bot commented May 12, 2026

Copy link
Copy Markdown

Rsdoctor Bundle Diff Analysis

Found 12 projects in monorepo, 1 project with changes.

📊 Quick Summary
Project Total Size Change
adapter-rsbuild 3.6 KB 0
adapter-rslib 24.6 KB 0
adapter-rspack 7.7 KB 0
browser 2.0 MB 0
browser-react 3.7 KB 0
browser-ui 803.5 KB 0
coverage-istanbul 9.6 KB 0
core/browser 970.0 KB 0
core/loaders 869.0 B 0
core/main 1.6 MB +6.3 KB (0.4%)
vscode/extension 26.9 MB 0
vscode/worker 14.4 KB 0
📋 Detailed Reports (Click to expand)

📁 core/main

Path: packages/core/.rsdoctor/main/rsdoctor-data.json

📌 Baseline Commit: 58277b1eb3 | PR: #1235

Metric Current Baseline Change
📊 Total Size 1.6 MB 1.6 MB +6.3 KB (0.4%)
📄 JavaScript 1.6 MB 1.5 MB +6.3 KB (0.4%)
🎨 CSS 0 B 0 B 0
🌐 HTML 0 B 0 B 0
📁 Other Assets 79.2 KB 79.2 KB 0

📦 Download Diff Report: core/main Bundle Diff

Generated by Rsdoctor GitHub Action

MemoryGate stored heapBeforeDispatch as a single instance field, so
concurrent dispatches clobbered each other's baselines. Resolves either
computed the wrong delta or skipped sampling entirely once one resolve
cleared the field, systematically underestimating heapDeltaSamples in
the high-concurrency scenarios the slow lane is meant to defend.

Return the baseline from recordDispatch and thread it through
recordResolve so each dispatch carries its own token.
@fi3ework fi3ework requested a review from 9aoy May 14, 2026 06:01
@fi3ework fi3ework merged commit 185f0fd into main May 14, 2026
12 checks passed
@fi3ework fi3ework deleted the tree/dev1 branch May 14, 2026 06:12
@9aoy 9aoy mentioned this pull request May 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Memory-aware worker creation to prevent OOM

2 participants