Skip to content

perf(wsl): pooled wsl.exe command loops and orcad compile cache on the WSL wake path (C4 follow-up) - #27923

Merged
nwparker merged 2 commits into
mainfrom
nwparker/wsl-c4b-wake-path-perf
Oct 13, 2026
Merged

nwparker merged 2 commits into
mainfrom
nwparker/wsl-c4b-wake-path-perf

Conversation

@nwparker

@nwparker nwparker commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 6 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​343 0 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​343
Prod 11 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​505 $\color{#cf222e}{\Huge{\mathbf{−}}}$​26 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​479

Stacked on #27819 (C4). C5 (#27834) now stacks on #27941 above this. Do not merge.

ELI5

Restarting Orca's server inside a WSL distro took about 12 seconds. Most of that time went to starting wsl.exe around 37 times in a row, once for each small check. Each start costs about 180 ms. Now a few wsl.exe processes stay open and run those checks one after another, at about 10 ms each. The server itself also starts faster, because Node now caches its compiled code. A warm restart takes about 5.7 seconds instead of about 12.5.

What Changed

  • Pooled WSL command loops (src/main/wsl/wsl-command-loop.ts, wsl-command-loop-pool.ts).
    • The WSL transport has a new execWithClosedStdin. It sends a command to an idle wsl.exe --exec sh -c <loop> built with buildWslExecArgs and spawned through process-host.
    • Each command runs as setsid sh -c "$cmd" </dev/null (plain sh -c if the guest has no setsid), so it gets its own session as before. Its stdout, stderr and exit status come back base64-encoded between per-loop random markers.
    • At most 4 loops run at once. A 5th concurrent command gets its own wsl.exe, as before. Idle loops end after 5 s, or right away through dispose(), which deployAndLaunchWslOrcad calls when it finishes.
    • If a guest fails the loop's startup self-test (mktemp/base64), that transport goes back to one wsl.exe per command.
  • Routing in execCommand. execCommand uses execWithClosedStdin only when the caller passes no stdin and the transport defines the method. In that case nothing would ever write the channel's stdin anyway. Commands that do take stdin (uploads, writeFile, the stdio bridge) still get their own wsl.exe. SSH transports do not define the method, so the SSH command text and options are byte-for-byte unchanged. A new test asserts this.
  • orcad compile cache (src/main/orcad/orcad-compile-cache.ts).
    • The launcher turns on module.enableCompileCache(<slot>/.orcad-compile-cache), but only for a client-managed slot (one with ORCA_ORCAD_MANAGED_ACTIVATION_ROOT set). It never runs in a packaged app directory, where a new file would break the bundle's signature.
    • The profile-preflight child compiles the same 10 MB bundle, so it gets NODE_COMPILE_CACHE in its own env only. The daemon and terminals never inherit it.
    • Slot GC deletes the cache along with the slot.
    • The env constant moved into a dependency-free module, so the launcher bundle does not pull in zod.

Why

The orcad lifecycle code (fence, journal, recovery, census, GC) is shared with SSH, and every one of its steps matters. Batching those steps into combined scripts would have meant rewriting shared command sequences that SSH also runs. Making each round trip cheap at the transport level speeds up all of them without changing what any of them do. A full stdin multiplexer would need a helper inside the guest, so loops serve only commands that never use stdin.

The compile cache is a standard Node feature that TypeScript's own CLI also uses. It changes no command and works the same on SSH and WSL hosts.

Linked Issue

Internal WSL-via-orcad tracker, step C4 follow-up.

Visual Proof

N/A: no UI change.

Testing

  • I manually tested these changes locally
  • Automated tests added/updated, or explained why not below

win-lowspec (Windows 11, 8 logical CPUs). Throwaway Ubuntu 24.04 distro orca-c4b-probe with systemd=false. I ran deployAndLaunchWslOrcad under Orca.exe with ELECTRON_RUN_AS_NODE, wrapping every exec with timing. For each warm run I killed orcad, then relaunched. Before = C4 transport plus C4 orcad. After = this branch, for both the transport and orcad.

warm relaunch (5 runs) before after
total call 12.2 / 12.4 / 13.2 / 13.6 / 14.2 s 5.5 / 5.6 / 5.7 / 6.0 / 6.5 s
orcad start → ready 3.31 / 3.33 / 3.48 / 3.50 / 3.76 s 2.59 / 2.72 / 2.85 / 2.86 / 3.67 s
execs 37 × ~190 ms 37 × ~8–20 ms (one loop)
cold first deploy 26.6 s 17.4 s
  • I checked that orcad survives after the loop's wsl.exe exits: same PID, its own session.
  • No loop shells or /tmp/orca-wsl-exec.* directories were left behind.
  • The cache directory is 3.5 MB and 0700.
  • The distro, temp directory and terminal were all removed afterwards.

Automated.

  • The loop is tested on a real local sh: framing, binary output, stderr, exit codes, stdin EOF, its own session, the concurrency cap, abort, a loop dying mid-command, the startup fallback, and a backgrounded writer.
  • The transport is tested with a stand-in sh for wsl.exe, plus the execCommand routing and the SSH byte-identity test.
  • The compile-cache gating is tested.
  • The launcher-isolation list is updated.
  • src/main/{ssh,wsl,orcad} pass. A few real-shell timing tests fail only under full parallel load and pass alone. orcad-state-snapshot-shell's killed-group case also fails on macOS without this change.
  • pnpm tc, check:code-quality:changed, oxlint, anti-slop, process-host imports, the max-lines, runtime-electron and runtime-launcher ratchets, the node pin check and smoke:orcad-terminal all pass.

AI Disclosure

Review

Agent skill upstream boundary

  • Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

  • SSH: no SSH transport defines execWithClosedStdin, so its commands and options are unchanged. The compile cache applies to managed SSH slots too. It changes no command, and if it fails, orcad compiles as before.
  • Semantics: a looped command gets EOF on stdin instead of an open pipe that nothing writes to. A command that does not exit while its own background child holds stdout now finishes, where before it would hang. A command still running when its loop dies is reported the way a killed wsl.exe was.
  • Remaining hotspots (after):
    • orcad startup is about 2.8 s. The profile preflight child takes about 1.2 s, including its own bundle load and the writer worker, which the cache does not reach. Profile-state startup in the server takes about 0.7 s. The deliberate preflight gate makes these unsafe to defer here.
    • materializeOrcadArtifact takes about 1.3 s locally. It hashes the template and cache three times per call.
    • The holder and registry read take about 0.5 s in this harness. That figure includes a PowerShell process-table fallback that the app avoids with its native binding.
    • The guest's pinned-Node runtime check takes about 0.3 s.
  • The 3 s warm target is not met. It needs the materializer and orcad preflight work above.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

@nwparker
nwparker force-pushed the nwparker/wsl-c4-deploy-launch branch from ca22e1a to 21a0166 Compare October 12, 2026 10:38
@nwparker
nwparker force-pushed the nwparker/wsl-c4b-wake-path-perf branch from fa0b90e to a17d5b1 Compare October 12, 2026 10:38
@nwparker
nwparker force-pushed the nwparker/wsl-c4b-wake-path-perf branch from a17d5b1 to fbc7021 Compare October 12, 2026 12:00
@nwparker
nwparker force-pushed the nwparker/wsl-c4-deploy-launch branch from 21a0166 to ae08e94 Compare October 12, 2026 12:00
@nwparker
nwparker force-pushed the nwparker/wsl-c4b-wake-path-perf branch from fbc7021 to 850b587 Compare October 12, 2026 16:25
@nwparker
nwparker force-pushed the nwparker/wsl-c4-deploy-launch branch from b7ce5f1 to deaf720 Compare October 13, 2026 07:00
@nwparker
nwparker changed the base branch from nwparker/wsl-c4-deploy-launch to main October 13, 2026 08:31
@nwparker
nwparker marked this pull request as ready for review October 13, 2026 08:31
@nwparker
nwparker force-pushed the nwparker/wsl-c4b-wake-path-perf branch from 850b587 to 342e50a Compare October 13, 2026 08:31
@coderabbitai

coderabbitai Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ee1f2c03-5850-486d-9aa3-afff0e80e26e
📥 Commits

Reviewing files that changed from the base of the PR and between b852950cca55e46254468e1ed5748de1f8178275 and 8eddaf4.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

The launcher now enables V8 compile caching for managed Orcad slots, and the startup preflight receives the cache directory when available. Remote command execution can select closed-stdin transport execution when callers provide no stdin. The WSL transport adds reusable shell command loops with pooling, readiness and failure handling, and fallback to individual processes. WSL deployment disposes of transports that expose a disposal method.

Priority: ➖ Normal

Merge Risk: 🔵 Low · up to 61d2f

The change is mergeable with awareness that unexpectedly large command output could increase memory use; the current deployment commands are not expected to produce it.

Pre-merge checks · Passed 4 · Failed 1

Failed Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 27.78% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 18 functions across 17 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks (4 passed)
Check name Status Explanation
Title check Passed The title clearly identifies both primary changes: pooled WSL command loops and the Orcad compile cache on the WSL wake path.
Description check Passed The description is detailed and covers the required change summary, rationale, testing, compatibility considerations, visual proof, and checklist. The Linked Issue section does not contain an actual i…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@nwparker
nwparker force-pushed the nwparker/wsl-c4b-wake-path-perf branch from 342e50a to 61d2f1e Compare October 13, 2026 09:26

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/main/wsl/wsl-command-loop.ts (1)

88-94: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

Pooled output can exceed the memory bound that execCommand enforces.

The loop keeps every base64 line of a command in command.stdout and command.errors. It decodes all of them at once in settle. execCommand keeps only the last MAX_EXEC_OUTPUT_CHARS of output, but that limit applies after the data reaches it. A direct wsl.exe child streams its output, so it never holds the full output in memory.

The guest script also writes each command's full output to $d/o before it frames anything. If a command such as cat on a large file produces a lot of output, memory grows with the full output size, at about 1.33× because of base64.

The probes in the current callers are small, so the practical risk is low. To fix it, push each decoded chunk to the channel as it arrives. Decode in multiples of 4 base64 characters.


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 0f5675d6-2bcd-41de-8d5c-8b429220b3aa
📥 Commits

Reviewing files that changed from the base of the PR and between 4857afd and 61d2f1e66b73448ce5bc178f08de2cc138a91f73.

📒 Files selected for processing (17)
  • src/main/orcad/launcher.ts
  • src/main/orcad/orcad-compile-cache.test.ts
  • src/main/orcad/orcad-compile-cache.ts
  • src/main/orcad/orcad-launcher-isolation.integration.test.ts
  • src/main/orcad/orcad-profile-preflight.ts
  • src/main/ssh/remote-install-transport.ts
  • src/main/ssh/ssh-relay-exec-closed-stdin.test.ts
  • src/main/ssh/ssh-relay-exec-command.ts
  • src/main/wsl/wsl-command-loop-pool.ts
  • src/main/wsl/wsl-command-loop.test.ts
  • src/main/wsl/wsl-command-loop.ts
  • src/main/wsl/wsl-orcad-deploy.ts
  • src/main/wsl/wsl-orcad-host-transport-loops.test.ts
  • src/main/wsl/wsl-orcad-host-transport.test.ts
  • src/main/wsl/wsl-orcad-host-transport.ts
  • src/shared/orcad-idle-exit.ts
  • src/shared/orcad-managed-launch-env.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 1 remain after this review.

@nwparker
nwparker force-pushed the nwparker/wsl-c4b-wake-path-perf branch 2 times, most recently from 2ca6b4d to b852950 Compare October 13, 2026 10:50
…ache orcad's compiled bundle

The WSL wake path made ~37 sequential wsl.exe starts at ~180 ms each. Stdin-free
execCommand calls now go through a few long-lived wsl.exe sh loops (one command at a
time per loop, own session, stdin at EOF, base64-framed output). SSH has no such
method, so its commands and options are unchanged.

orcad enables Node's compile cache inside a client-managed slot, and hands it to the
profile preflight child, which compiles the same bundle.
… managed slot

An unmanaged orcad started with its own NODE_COMPILE_CACHE kept that
location for its preflight before; never redirect it into the install dir.
@nwparker
nwparker force-pushed the nwparker/wsl-c4b-wake-path-perf branch from b852950 to 8eddaf4 Compare October 13, 2026 10:54
@nwparker
nwparker merged commit e09d732 into main Oct 13, 2026
58 checks passed
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