Skip to content

Commit 6011949

Browse files
authored
[CLI] Add --workers=<n|auto> flag to configure worker thread count (#3504)
## Motivation for the change, related issues The Playground CLI hardcodes the number of request-handling worker threads to 6 (chosen to match HTTP/1.1's per-origin browser connection limit). That's fine for a single browser, but it caps multi-client workloads — parallel Playwright contexts, load tests, fanning e2e suites — at six in-flight requests, which is too few on modern hosts. The old `--experimental-multi-worker` flag that let users override this was deprecated and its value is now silently ignored, and the pre-hardcoding default of `cpus - 1` was dropped along with it. There's no way to turn the dial back up. ## Implementation details Adds a new `--workers=<n|auto>` flag: - A positive integer sets an explicit worker count. - `auto` uses `max(1, os.cpus().length - 1)` — restores the old pre-hardcoding default. - When the flag is absent, the default is `min(6, max(1, cpus - 1))`: preserves today's 6-worker behavior on machines with ≥7 cores, shrinks on small hosts so we don't spawn more workers than cores. The `Math.max(1, ...)` guard covers single-core hosts and restricted environments where `os.cpus()` returns an empty array. - Validation happens in yargs `coerce`: rejects 0, negatives, non-integers, and any non-`auto` string with a clear error message, routed through the existing `.fail()` handler. The resolver is a small exported pure function (`resolveWorkerCount`) so the min/max/cpu logic is unit-testable without spinning up a server. `--experimental-multi-worker` stays parsed-but-ignored — existing invocations don't break — but now emits a one-line `logger.warn` pointing users at `--workers`. Docs updated in both `packages/playground/cli/README.md` and the Docusaurus CLI reference page. ### Breaking changes None. The default worker count is unchanged on typical dev machines (≥7 cores → still 6). On hosts with 1–6 cores the default drops to `cpus - 1`, which is a behavior change on small hosts but strictly safer (we were previously spawning more workers than cores there). ## Testing Instructions (or ideally a Blueprint) **Unit tests** (fast, no server spawn): ```bash npx nx run playground-cli:test-playground-cli --testNamePattern="resolveWorkerCount|worker count" ``` All 14 new tests should pass: default, explicit number, `--workers=1` (single-worker bootstrap), `--workers=auto`, invalid `0`, invalid `abc`, deprecation warning for `--experimental-multi-worker`, plus 7 unit tests stubbing `os.cpus()` to cover the min/max/empty-array edges. **Manual smoke**: ```bash # Explicit count — server prints "(12 workers)" npx nx dev playground-cli server --workers=12 # auto — uses cpus-1 npx nx dev playground-cli server --workers=auto # default — min(6, cpus-1) npx nx dev playground-cli server # Deprecated flag still works, now warns npx nx dev playground-cli server --experimental-multi-worker=4 # Validation npx nx dev playground-cli server --workers=0 # exits 1 with clear error npx nx dev playground-cli server --workers=abc # exits 1 with clear error ``` **Multi-client verification** (the motivating use case): ```bash # Start with more than 6 workers npx nx dev playground-cli server --workers=16 & # Fire >6 concurrent requests; throughput should scale past the old 6-worker ceiling seq 1 32 | xargs -P32 -I{} curl -s -o /dev/null -w "%{time_total}\n" http://127.0.0.1:9400/ ```
1 parent a86fac8 commit 6011949

4 files changed

Lines changed: 318 additions & 14 deletions

File tree

‎packages/docs/site/docs/developers/05-local-development/04-wp-playground-cli.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,8 @@ The `server` command supports the following optional arguments:
262262
- `--xdebug`: Enable Xdebug. Defaults to false.
263263
- `--experimental-devtools`: Enable experimental browser development tools. Defaults to false.
264264
- `--experimental-unsafe-ide-integration=<ide>`: Set up the Xdebug integration on VS Code (`vscode`) and PhpStorm (`phpstorm`).
265-
- `--experimental-multi-worker=<number>`: Enable experimental multi-worker support which requires a `/wordpress` directory backed by a real filesystem. Pass a positive number to specify the number of workers to use. Otherwise, defaults to the number of CPUs minus 1.
265+
- `--workers=<n|auto>`: Number of request-handling worker threads. Pass a positive integer, or `auto` to use one worker per CPU core (minus one). Defaults to `min(6, cpus-1)`. Useful for multi-client workloads (e.g. parallel e2e suites) that need more than 6 in-flight requests.
266+
- `--experimental-multi-worker=<number>`: Deprecated. Use `--workers=<n|auto>` instead. The value of this flag is ignored.
266267

267268
:::caution
268269
With the flag `--follow-symlinks`, the following symlinks will expose files outside mounted directories to Playground and could be a security risk.

‎packages/playground/cli/README.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ The `server` command supports the following optional arguments:
9797

9898
- `--debug`: Print the PHP error log if an error occurs during boot.
9999
- `--follow-symlinks`: Allow Playground to follow symlinks by automatically mounting symlinked directories and files encountered in mounted directories. ⚠️ Warning: Following symlinks will expose files outside mounted directories to Playground and could be a security risk.
100-
- `--experimental-multi-worker`: Enables experimental multi-worker support. It needs JSPI and a /wordpress directory on a real filesystem. You can pass a positive number to use a specific number of workers, otherwise, it defaults to the number of CPUs minus one.
100+
- `--workers=<n|auto>`: Number of request-handling worker threads. Pass a positive integer, or `auto` to use one worker per CPU core (minus one). Defaults to `min(6, cpus-1)`. Useful for multi-client workloads (e.g. parallel e2e suites) that need more than 6 in-flight requests.
101+
- `--experimental-multi-worker`: Deprecated. Use `--workers=<n|auto>` instead. The value of this flag is ignored.
101102
- `--phpmyadmin[=<path>]`: Install phpMyAdmin for database management. The phpMyAdmin URL will be printed after boot. Optionally specify a custom URL path (default: `/phpmyadmin`).
102103
- `--internal-cookie-store`: Enables Playground's internal cookie handling. When active, Playground uses an HttpCookieStore to manage and persist cookies across requests. If disabled, cookies are handled externally, like by a browser in Node.js.
103104

‎packages/playground/cli/src/run-cli.ts‎

Lines changed: 83 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -343,14 +343,35 @@ export async function parseOptionsAndRunCLI(argsToParse: string[]) {
343343
'Port to listen on when serving. Defaults to 9400 when available.',
344344
type: 'number',
345345
},
346+
workers: {
347+
describe:
348+
'Number of request-handling worker threads. Accepts a ' +
349+
'positive integer, or "auto" to use one per CPU core ' +
350+
'(minus one). Defaults to min(6, cpus-1).',
351+
type: 'string',
352+
coerce: (value?: string) => {
353+
if (value === undefined) {
354+
return undefined;
355+
}
356+
if (value === 'auto') {
357+
return 'auto' as const;
358+
}
359+
const n = Number(value);
360+
if (!Number.isInteger(n) || n < 1) {
361+
throw new Error(
362+
`Invalid --workers value "${value}": ` +
363+
'expected a positive integer or "auto".'
364+
);
365+
}
366+
return n;
367+
},
368+
},
346369
'experimental-multi-worker': {
347370
deprecated:
348-
'This option is not needed. Multiple workers are always used.',
371+
'Use --workers=<n|auto> instead. The value of this flag is ignored.',
349372
describe:
350-
'Enable experimental multi-worker support which requires ' +
351-
'a /wordpress directory backed by a real filesystem. ' +
352-
'Pass a positive number to specify the number of workers to use. ' +
353-
'Otherwise, default to the number of CPUs minus 1.',
373+
'Deprecated. Use --workers=<n|auto> to control the ' +
374+
'number of request-handling worker threads.',
354375
type: 'number',
355376
},
356377
'experimental-devtools': {
@@ -813,6 +834,24 @@ function getMountForVfsPath(
813834
);
814835
}
815836

837+
/**
838+
* Resolve the --workers flag into a concrete worker count.
839+
*
840+
* The Math.max(1, ...) guard covers single-core hosts and restricted
841+
* environments where `os.cpus()` can return an empty array — without it
842+
* the default would drop to 0 and no workers would be spawned.
843+
*/
844+
export function resolveWorkerCount(value: number | 'auto' | undefined): number {
845+
const cpusMinusOne = Math.max(1, os.cpus().length - 1);
846+
if (value === undefined) {
847+
return Math.min(6, cpusMinusOne);
848+
}
849+
if (value === 'auto') {
850+
return cpusMinusOne;
851+
}
852+
return value;
853+
}
854+
816855
export interface RunCLIArgs {
817856
/**
818857
* `_` holds positional tokens in the order they appeared.
@@ -854,6 +893,8 @@ export interface RunCLIArgs {
854893
experimentalUnsafeIdeIntegration?: string[];
855894
experimentalDevtools?: boolean;
856895
'experimental-blueprints-v2-runner'?: boolean;
896+
workers?: number | 'auto';
897+
'experimental-multi-worker'?: number;
857898
wordpressInstallMode?: WordPressInstallMode;
858899
/**
859900
* PHP string constants defined via --define flag.
@@ -1082,22 +1123,52 @@ export async function runCLI(args: RunCLIArgs): Promise<RunCLIServer | void> {
10821123
const serverUrl = `http://${host}:${port}`;
10831124
const siteUrl = args['site-url'] || serverUrl;
10841125

1126+
if (args['experimental-multi-worker'] !== undefined) {
1127+
logger.warn(
1128+
'--experimental-multi-worker is deprecated and its value is ignored. ' +
1129+
'Use --workers=<n|auto> instead.'
1130+
);
1131+
}
1132+
10851133
/**
10861134
* With HTTP 1.1, browsers typically support 6 parallel connections per domain.
10871135
* > browsers open several connections to each domain,
10881136
* > sending parallel requests. Default was once 2 to 3 connections,
10891137
* > but this has now increased to a more common use of 6 parallel connections.
10901138
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Connection_management_in_HTTP_1.x#domain_sharding
10911139
*
1092-
* While our HTTP server only supports HTTP 1.1 and while we are trying to limit the
1093-
* memory requirements of multiple workers, let's hard-code the number of request-handling
1094-
* workers to 6.
1140+
* 6 is therefore a sensible default; --workers=<n|auto> lets
1141+
* users override this for multi-client workloads (e2e tests,
1142+
* load testing) that need more in-flight requests.
10951143
*
1096-
* Going higher than browsers' max concurrent requests seems pointless,
1097-
* and going lower may increase the likelihood of deadlock due to workers
1098-
* blocking and waiting for file locks.
1144+
* Note: going lower may increase the likelihood of deadlock
1145+
* due to workers blocking and waiting for file locks.
10991146
*/
1100-
const targetWorkerCount = 6;
1147+
const targetWorkerCount = resolveWorkerCount(args.workers);
1148+
1149+
if (targetWorkerCount < 6) {
1150+
const deadlockNote =
1151+
'Running fewer than 6 workers may increase the ' +
1152+
'likelihood of deadlock due to workers blocking on ' +
1153+
'file locks.';
1154+
if (args.workers === undefined) {
1155+
/*
1156+
* Default path landed below 6 because the machine has
1157+
* fewer than 7 CPUs. Distinct message so users see this
1158+
* as a hardware ceiling, not a config mistake.
1159+
*/
1160+
logger.warn(
1161+
`The default worker count has been reduced to ${targetWorkerCount} ` +
1162+
`because this machine has only ${os.cpus().length} CPU(s). ` +
1163+
deadlockNote
1164+
);
1165+
} else {
1166+
logger.warn(
1167+
`Worker count (${targetWorkerCount}) is below the recommended threshold (6). ` +
1168+
deadlockNote
1169+
);
1170+
}
1171+
}
11011172

11021173
/*
11031174
* Use a real temp dir as a target for the following Playground paths

0 commit comments

Comments
 (0)