perf(build): cap Rolldown threads on large hosts; build pairing web client in the background (#33828)

Rolldown defaults to 1.5x physical cores Tokio workers plus a Rayon pool per
logical CPU (24+24 here). Capping both at 8 on >8-core hosts cuts electron-vite
build CPU 24% with byte-identical output. pnpm dev no longer blocks on a stale
pairing web build; it stages into out/.web-next and swaps on success.

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
OrcaWin
2026-10-26 00:51:28 -07:00
committed by GitHub
co-authored by OrcaWin
parent 25d331af76
commit b252aef0cf
6 changed files with 94 additions and 13 deletions
+16
View File
@@ -0,0 +1,16 @@
import os from 'node:os'
// Measured on 24 threads: Rolldown's default 24+24 workers cost ~25% more CPU than 8 for no wall-time gain.
const maxRolldownThreads = '8'
/** Caps Rolldown's Tokio and Rayon pools on large hosts unless the caller already chose sizes. */
export function withRolldownThreadLimit(env, availableParallelism = os.availableParallelism()) {
if (availableParallelism <= Number(maxRolldownThreads)) {
return env
}
return {
...env,
ROLLDOWN_WORKER_THREADS: env.ROLLDOWN_WORKER_THREADS ?? maxRolldownThreads,
RAYON_NUM_THREADS: env.RAYON_NUM_THREADS ?? maxRolldownThreads
}
}
@@ -0,0 +1,17 @@
import { describe, expect, it } from 'vitest'
import { withRolldownThreadLimit } from './rolldown-thread-limit.mjs'
describe('withRolldownThreadLimit', () => {
it('caps both pools on large hosts', () => {
expect(withRolldownThreadLimit({ A: '1' }, 24)).toEqual({
A: '1',
ROLLDOWN_WORKER_THREADS: '8',
RAYON_NUM_THREADS: '8'
})
})
it('keeps Rolldown defaults on small hosts and explicit sizes everywhere', () => {
expect(withRolldownThreadLimit({}, 8)).toEqual({})
expect(withRolldownThreadLimit({ RAYON_NUM_THREADS: '2' }, 24).RAYON_NUM_THREADS).toBe('2')
})
})
+2 -1
View File
@@ -3,6 +3,7 @@ import fs from 'node:fs'
import { createRequire } from 'node:module'
import path from 'node:path'
import { appendBuildOldSpaceOption } from './node-old-space-limit.mjs'
import { withRolldownThreadLimit } from './rolldown-thread-limit.mjs'
import { RENDERER_BUILD_DIR, verifyRendererBootGraph } from './renderer-boot-graph.mjs'
const require = createRequire(import.meta.url)
@@ -15,7 +16,7 @@ const nodeOptions = appendBuildOldSpaceOption(process.env.NODE_OPTIONS)
const child = spawn(process.execPath, [electronViteCli, 'build', ...process.argv.slice(2)], {
stdio: 'inherit',
env: {
...process.env,
...withRolldownThreadLimit(process.env),
NODE_OPTIONS: nodeOptions
}
})
+55 -11
View File
@@ -7,12 +7,14 @@ import {
readdirSync,
readFileSync,
readlinkSync,
renameSync,
rmSync,
statSync,
symlinkSync,
writeFileSync
} from 'node:fs'
import { createRequire } from 'node:module'
import os from 'node:os'
import path from 'node:path'
import { prepareDevCliTerminalWrappers } from './dev-cli-terminal-wrapper.mjs'
@@ -36,6 +38,7 @@ import {
isDevWebClientFresh,
recordDevWebClientInputs
} from './dev-web-client-freshness.mjs'
import { withRolldownThreadLimit } from './rolldown-thread-limit.mjs'
import { copyPrivateTree } from './space-sharing-copy.mjs'
import {
DEV_BUNDLE_ID,
@@ -513,17 +516,53 @@ function prepareDevWebClient() {
return
}
const inputs = captureDevWebClientInputs(repoRoot)
console.error('[orca-dev] Building web client for pairing...')
execFileSync(
console.error('[orca-dev] Building web client for pairing in the background...')
// Pairing reads out/web per request, so a stale bundle must not delay Electron startup.
// Stage then swap, so an interrupted build never leaves out/web empty.
const stagingDir = path.join(repoRoot, 'out', '.web-next')
const build = spawn(
process.execPath,
[viteCli, 'build', '--config', path.join(repoRoot, 'vite.web.config.ts')],
{
cwd: repoRoot,
stdio: 'inherit',
env: process.env
}
[
viteCli,
'build',
'--config',
path.join(repoRoot, 'vite.web.config.ts'),
'--outDir',
stagingDir,
'--logLevel',
'warn'
],
{ cwd: repoRoot, stdio: 'inherit', env: withRolldownThreadLimit(process.env) }
)
recordDevWebClientInputs(repoRoot, inputs)
webClientBuild = build
try {
os.setPriority(build.pid, os.constants.priority.PRIORITY_BELOW_NORMAL)
} catch {
// Priority is best effort; the build still runs at normal priority.
}
build.once('error', (error) => {
console.error(`[orca-dev] Web client build failed to start: ${error.message}`)
})
build.once('exit', (code, signal) => {
webClientBuild = null
if (code !== 0) {
if (!isShuttingDown) {
console.error(
`[orca-dev] Web client build failed (${signal ?? code}); pairing serves the previous bundle`
)
}
return
}
try {
const outDir = path.dirname(getDevWebClientIndexPath())
rmSync(outDir, { recursive: true, force: true })
renameSync(stagingDir, outDir)
recordDevWebClientInputs(repoRoot, inputs)
console.error('[orca-dev] Web client for pairing is ready')
} catch (error) {
console.error(`[orca-dev] Could not install the web client build: ${error.message}`)
}
})
}
if (!isHelpOrVersion && process.env.ORCA_DEV_INSTANCE_LABEL) {
@@ -542,7 +581,6 @@ if (!isHelpOrVersion) {
forwardedExtras = ports.forwardedExtras
announceDevInstance({ repoRoot, profile: devProfile, ...ports })
}
prepareDevWebClient()
const forwardedArgs = ['dev', ...forwardedRaw, ...forwardedExtras]
const child = spawn(process.execPath, [electronViteCli, ...forwardedArgs], {
stdio: 'inherit',
@@ -551,13 +589,16 @@ const child = spawn(process.execPath, [electronViteCli, ...forwardedArgs], {
detached: process.platform !== 'win32'
})
let webClientBuild = null
let isShuttingDown = false
child.once('spawn', () => {
if (!isHelpOrVersion) {
startDevStorageReclaim({ repoRoot })
}
prepareDevWebClient()
})
let isShuttingDown = false
let forcedKillTimer = null
function signalExitCode(signal) {
@@ -600,6 +641,7 @@ function beginShutdown(signal) {
}
isShuttingDown = true
webClientBuild?.kill()
terminateChild(signal)
forcedKillTimer = setTimeout(() => {
terminateChild('SIGKILL')
@@ -626,6 +668,8 @@ child.on('exit', (code, signal) => {
if (forcedKillTimer) {
clearTimeout(forcedKillTimer)
}
// A half-written out/web must not outlive the session that started it.
webClientBuild?.kill()
if (isShuttingDown) {
process.exit(signalExitCode(signal ?? 'SIGINT'))
+2 -1
View File
@@ -2,6 +2,7 @@ import { spawn } from 'node:child_process'
import { createRequire } from 'node:module'
import path from 'node:path'
import { appendBuildOldSpaceOption } from './node-old-space-limit.mjs'
import { withRolldownThreadLimit } from './rolldown-thread-limit.mjs'
const require = createRequire(import.meta.url)
const vitePackageJson = require.resolve('vite/package.json')
@@ -16,7 +17,7 @@ const child = spawn(
{
stdio: 'inherit',
env: {
...process.env,
...withRolldownThreadLimit(process.env),
NODE_OPTIONS: nodeOptions
}
}
@@ -230,6 +230,8 @@ describe('run-electron-vite-dev web client prepare', () => {
}
})
// The pairing build runs beside electron-vite instead of before it.
await waitFor(() => existsSync(viteFile))
expect(readFileSync(viteFile, 'utf8')).toContain('build')
const trackedPids = trackPidFile(pidFile)