Skip to content

Commit 85b7124

Browse files
authored
Merge pull request #2088 from Automattic/fix/2087-playground-worker-count
Configure Playground runtime request workers
2 parents 8cb506f + f2be573 commit 85b7124

11 files changed

Lines changed: 90 additions & 5 deletions

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@
227227
"test:php-patch-approval-filter": "php scripts/php-patch-approval-filter-smoke.php",
228228
"test:schema-parity": "tsx tests/schema-parity.test.ts",
229229
"test:recipe-validation-descriptors": "tsx tests/recipe-validation-descriptors.test.ts",
230+
"test:runtime-workers": "tsx tests/runtime-workers.test.ts",
230231
"test:recipe-runtime-backend-normalization": "tsx tests/recipe-runtime-backend-normalization.test.ts",
231232
"test:recipe-runtime-setup-staged-materialization": "tsx tests/recipe-runtime-setup-staged-materialization.test.ts",
232233
"test:recipe-run-provenance": "tsx tests/recipe-run-provenance.test.ts",

packages/cli/src/commands/recipe-build.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { readFile, writeFile } from "node:fs/promises"
2-
import { buildGenericAbilityRuntimeRunRecipe, buildRuntimePackageRunRecipe, buildWordPressBenchRecipe, buildWordPressPhpunitRecipe, compileRecipeTemplate, type GenericAbilityRuntimeRunOptions, type RecipeTemplateInput, type RuntimePackageRunRecipeOptions, type RuntimeWordPressInstallMode, type WorkspaceRecipe, type WorkspaceRecipeExtraPlugin, type WorkspaceRecipeMount, type WorkspaceRecipePHPWasmExtensionManifest, type WorkspaceRecipeRuntimeBackendPackage, type WorkspaceRecipeRuntimeService, type WorkspaceRecipeStep } from "@automattic/wp-codebox-core"
2+
import { buildGenericAbilityRuntimeRunRecipe, buildRuntimePackageRunRecipe, buildWordPressBenchRecipe, buildWordPressPhpunitRecipe, compileRecipeTemplate, type GenericAbilityRuntimeRunOptions, type RecipeTemplateInput, type RuntimePackageRunRecipeOptions, type RuntimeWorkerCount, type RuntimeWordPressInstallMode, type WorkspaceRecipe, type WorkspaceRecipeExtraPlugin, type WorkspaceRecipeMount, type WorkspaceRecipePHPWasmExtensionManifest, type WorkspaceRecipeRuntimeBackendPackage, type WorkspaceRecipeRuntimeService, type WorkspaceRecipeStep } from "@automattic/wp-codebox-core"
33

44
interface RecipeBuildOptions {
55
recipeType: "phpunit" | "bench" | "template" | "generic-ability-runtime-run" | "runtime-package-run"
@@ -11,6 +11,7 @@ interface WordPressPhpunitBuilderOptions {
1111
blueprint?: unknown
1212
wordpressVersion?: string
1313
phpVersion?: string
14+
workers?: RuntimeWorkerCount
1415
databaseType?: "sqlite" | "mysql"
1516
wordpressInstallMode?: RuntimeWordPressInstallMode
1617
extensions?: WorkspaceRecipePHPWasmExtensionManifest[]
@@ -82,6 +83,7 @@ function buildRecipe(recipeType: RecipeBuildOptions["recipeType"], options: Word
8283
blueprint: phpunitOptions.blueprint,
8384
wordpressVersion: stringOrUndefined(phpunitOptions.wordpressVersion),
8485
phpVersion: stringOrUndefined(phpunitOptions.phpVersion),
86+
workers: workerCountOrUndefined(phpunitOptions.workers),
8587
databaseType: phpunitOptions.databaseType,
8688
wordpressInstallMode: phpunitOptions.wordpressInstallMode,
8789
extensions: Array.isArray(phpunitOptions.extensions) ? phpunitOptions.extensions : [],
@@ -182,6 +184,12 @@ function requiredString(value: unknown, name: string): string {
182184
return value
183185
}
184186

187+
function workerCountOrUndefined(value: unknown): RuntimeWorkerCount | undefined {
188+
if (value === undefined || value === "auto") return value
189+
if (typeof value === "number" && Number.isSafeInteger(value) && value >= 1 && value <= 64) return value
190+
throw new Error("workers must be an integer from 1 to 64 or auto.")
191+
}
192+
185193
function stringOrUndefined(value: unknown): string | undefined {
186194
return typeof value === "string" && value !== "" ? value : undefined
187195
}

packages/cli/src/commands/recipe-run.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ export async function runRecipe(options: RecipeRunOptions, interruption?: Recipe
217217
name: plan.runtime.name,
218218
version: plan.runtime.wp,
219219
phpVersion: plan.runtime.phpVersion,
220+
workers: plan.runtime.workers,
220221
wordpressInstallMode: plan.runtime.wordpressInstallMode,
221222
databaseSetup: recipe.inputs?.services?.some(({ kind }) => kind === "mysql") ? "external" as const : undefined,
222223
blueprint: plan.runtime.blueprint,

packages/cli/src/recipe-dry-run.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export interface RecipePlan {
5454
name: string
5555
wp: string
5656
phpVersion?: string
57+
workers?: number | "auto"
5758
wordpressInstallMode?: RuntimeWordPressInstallMode
5859
assets?: RuntimeAssetSpec
5960
blueprint: unknown
@@ -442,6 +443,7 @@ export async function planWorkspaceRecipe(recipe: WorkspaceRecipe, recipeDirecto
442443
name: recipe.runtime?.name ?? "wp-codebox-recipe",
443444
wp: recipe.runtime?.wp ?? context.defaultWordPressVersion,
444445
...(recipe.runtime?.phpVersion ? { phpVersion: recipe.runtime.phpVersion } : {}),
446+
...(recipe.runtime?.workers !== undefined ? { workers: recipe.runtime.workers } : {}),
445447
...(recipe.runtime?.wordpressInstallMode ? { wordpressInstallMode: recipe.runtime.wordpressInstallMode } : {}),
446448
...(recipe.runtime?.assets ? { assets: recipe.runtime.assets } : {}),
447449
...(recipe.runtime?.extensions ? { extensions: recipe.runtime.extensions } : {}),

packages/runtime-core/src/recipe-builders.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { RuntimePreviewSpec, RuntimeWordPressInstallMode, WorkspaceRecipe, WorkspaceRecipeExtraPlugin, WorkspaceRecipeMount, WorkspaceRecipePHPWasmExtensionManifest, WorkspaceRecipeRuntimeBackendPackage, WorkspaceRecipeRuntimeService, WorkspaceRecipeStep } from "./runtime-contracts.js"
1+
import type { RuntimePreviewSpec, RuntimeWorkerCount, RuntimeWordPressInstallMode, WorkspaceRecipe, WorkspaceRecipeExtraPlugin, WorkspaceRecipeMount, WorkspaceRecipePHPWasmExtensionManifest, WorkspaceRecipeRuntimeBackendPackage, WorkspaceRecipeRuntimeService, WorkspaceRecipeStep } from "./runtime-contracts.js"
22
import { commandArg, commandJsonArg, commandStringListArg } from "./command-codecs.js"
33
export { buildRuntimePackageRunRecipe, CODEBOX_RUN_RUNTIME_PACKAGE_ABILITY, RUNTIME_PACKAGE_ARTIFACT_DECLARATION_SCHEMA, RUNTIME_PACKAGE_EXECUTION_INPUT_SCHEMA, RUNTIME_PACKAGE_EXECUTION_RESULT_SCHEMA, RUNTIME_PACKAGE_OUTPUT_PROJECTION_SCHEMA, runtimePackageExecutionInput, type RuntimePackageArtifactDeclaration, type RuntimePackageExecutionInput, type RuntimePackageOutputProjection, type RuntimePackageRunRecipeOptions } from "./runtime-package-execution.js"
44
export { RUNTIME_PACKAGE_DIAGNOSTIC_SCHEMA, RUNTIME_PACKAGE_RESULT_SCHEMA, RUNTIME_PACKAGE_TASK_SCHEMA, normalizeRuntimePackageResult, normalizeRuntimePackageTask, validateRuntimePackageTask, type RuntimePackageDiagnostic, type RuntimePackageResult, type RuntimePackageTask } from "./runtime-package-contracts.js"
@@ -14,6 +14,7 @@ export interface NormalizeRecipeMountsOptions {
1414
export interface WordPressPhpunitRecipeOptions {
1515
wordpressVersion?: string
1616
phpVersion?: string
17+
workers?: RuntimeWorkerCount
1718
databaseType?: "sqlite" | "mysql"
1819
wordpressInstallMode?: RuntimeWordPressInstallMode
1920
blueprint?: unknown
@@ -81,6 +82,7 @@ export function buildWordPressPhpunitRecipe(options: WordPressPhpunitRecipeOptio
8182
runtime: {
8283
wp: options.wordpressVersion ?? DEFAULT_WORDPRESS_VERSION,
8384
...(options.phpVersion ? { phpVersion: options.phpVersion } : {}),
85+
...(options.workers !== undefined ? { workers: options.workers } : {}),
8486
...(options.wordpressInstallMode ? { wordpressInstallMode: options.wordpressInstallMode } : {}),
8587
blueprint: blueprintWithMultisite(options.blueprint ?? { steps: [] }, (options.multisite ?? false) && options.databaseType !== "mysql"),
8688
...(options.preview || options.multisite ? { preview: multisitePreview(options.preview, options.multisite ?? false) } : {}),

packages/runtime-core/src/recipe-schema.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,13 @@ export function createWorkspaceRecipeJsonSchema(options: WorkspaceRecipeJsonSche
119119
pattern: "^[0-9]+\\.[0-9]+$",
120120
description: "PHP runtime version passed to WordPress Playground, for example 8.3 or 8.4.",
121121
},
122+
workers: {
123+
anyOf: [
124+
{ type: "integer", minimum: 1, maximum: 64 },
125+
{ const: "auto" },
126+
],
127+
description: "Runtime request worker count. Omit to use the backend default.",
128+
},
122129
wordpressInstallMode: {
123130
enum: enumValues(options.runtimeWordPressInstallModes, defaultRuntimeWordPressInstallModes),
124131
description: "Controls how Playground prepares a mounted WordPress directory. Use do-not-attempt-installing for custom distributions that own their own boot/readiness probes.",

packages/runtime-core/src/runtime-contracts.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { RuntimePolicy } from "./runtime-policy.js"
33
import { SANDBOX_WORKSPACE_ROOT } from "./runtime-action-adapter.js"
44
import type { ArtifactFileDigest, ArtifactManifestFile, ArtifactSpec, ArtifactViewerMetadata } from "./artifact-manifest.js"
55
import type { HostToolDefinition, HostToolRegistry } from "./host-tool-registry.js"
6-
import type { BackendNeutralRuntimeProvenance, RuntimePHPWasmExtensionManifest, RuntimeWordPressAssetSpec, RuntimeWordPressDatabaseSetupContract, RuntimeWordPressEnvironmentSpec, RuntimeWordPressInstallModeContract, RuntimeWordPressProvenance } from "./runtime-neutral-contracts.js"
6+
import type { BackendNeutralRuntimeProvenance, RuntimePHPWasmExtensionManifest, RuntimeWorkerCount, RuntimeWordPressAssetSpec, RuntimeWordPressDatabaseSetupContract, RuntimeWordPressEnvironmentSpec, RuntimeWordPressInstallModeContract, RuntimeWordPressProvenance } from "./runtime-neutral-contracts.js"
77
import type {
88
RUNTIME_EPISODE_ACTION_SCHEMA,
99
RUNTIME_EPISODE_OBSERVATION_SCHEMA,
@@ -20,6 +20,7 @@ export interface EnvironmentSpec extends RuntimeWordPressEnvironmentSpec {}
2020

2121
export type RuntimeWordPressInstallMode = RuntimeWordPressInstallModeContract
2222
export type RuntimeWordPressDatabaseSetup = RuntimeWordPressDatabaseSetupContract
23+
export type { RuntimeWorkerCount }
2324

2425
export interface RuntimeAssetSpec extends RuntimeWordPressAssetSpec {}
2526

@@ -647,6 +648,7 @@ export interface WorkspaceRecipe {
647648
name?: string
648649
wp?: string
649650
phpVersion?: string
651+
workers?: RuntimeWorkerCount
650652
wordpressInstallMode?: RuntimeWordPressInstallMode
651653
blueprint?: unknown
652654
preview?: RuntimePreviewSpec

packages/runtime-core/src/runtime-neutral-contracts.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export interface BackendNeutralEnvironmentSpec {
4747

4848
export type RuntimeWordPressInstallModeContract = "install-from-existing-files" | "install-from-existing-files-if-needed" | "do-not-attempt-installing"
4949
export type RuntimeWordPressDatabaseSetupContract = "runtime-managed" | "external"
50+
export type RuntimeWorkerCount = number | "auto"
5051

5152
export interface RuntimeWordPressAssetSpec extends BackendNeutralRuntimeAssetSpec {
5253
wordpressDirectory?: string
@@ -61,6 +62,7 @@ export interface RuntimePHPWasmExtensionManifest {
6162
export interface RuntimeWordPressEnvironmentSpec extends BackendNeutralEnvironmentSpec {
6263
blueprint?: unknown
6364
phpVersion?: string
65+
workers?: RuntimeWorkerCount
6466
assets?: RuntimeWordPressAssetSpec
6567
wordpressInstallMode?: RuntimeWordPressInstallModeContract
6668
databaseSetup?: RuntimeWordPressDatabaseSetupContract
@@ -115,13 +117,23 @@ export function normalizeRuntimeWordPressEnvironmentSpec(input: unknown): Runtim
115117
...neutral,
116118
blueprint: value.blueprint,
117119
phpVersion: optionalString(value.phpVersion, "environment.phpVersion"),
120+
workers: normalizeRuntimeWorkerCount(value.workers),
118121
assets: normalizeRuntimeWordPressAssetSpec(value.assets),
119122
wordpressInstallMode: optionalString(value.wordpressInstallMode, "environment.wordpressInstallMode") as RuntimeWordPressInstallModeContract | undefined,
120123
databaseSetup: optionalString(value.databaseSetup, "environment.databaseSetup") as RuntimeWordPressDatabaseSetupContract | undefined,
121124
extensions: normalizeRuntimePHPWasmExtensionManifests(value.extensions),
122125
})
123126
}
124127

128+
function normalizeRuntimeWorkerCount(value: unknown): RuntimeWorkerCount | undefined {
129+
if (value === undefined) return undefined
130+
if (value === "auto") return value
131+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1 || value > 64) {
132+
throw new Error("environment.workers must be an integer from 1 to 64 or auto.")
133+
}
134+
return value
135+
}
136+
125137
function normalizeRuntimePHPWasmExtensionManifests(input: unknown): RuntimePHPWasmExtensionManifest[] | undefined {
126138
if (input === undefined) return undefined
127139
if (!Array.isArray(input)) throw new Error("environment.extensions must be an array.")

packages/runtime-playground/src/playground-cli-runner.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ export async function startPlaygroundCliServer(spec: RuntimeCreateSpec, mounts:
150150
quiet: true,
151151
verbosity: "quiet",
152152
skipBrowser: true,
153-
workers: 6,
153+
workers: spec.environment.workers ?? 6,
154154
mount: [
155155
...postinstallMounts.map((mount) => ({
156156
hostPath: mount.source,

tests/playground-cli-runner-bootstrap-ini.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ try {
3333
environment: {
3434
version: "mounted-wordpress-source",
3535
phpVersion: "8.4",
36+
workers: 1,
3637
wordpressInstallMode: "do-not-attempt-installing",
3738
databaseSetup: "external",
3839
assets: { wordpressDirectory: wordpressDevelopDirectory },
@@ -84,7 +85,7 @@ try {
8485
assert.equal(calls[0]["mount-before-install"]?.[4]?.vfsPath, "/wordpress/wp-config.php")
8586
assert.deepEqual(calls[0]["mount-before-install"]?.[5], { hostPath: wordpressDevelopDirectory, vfsPath: "/wordpress" })
8687
assert.deepEqual(calls[0].mount, [])
87-
assert.equal(calls[0].workers, 6)
88+
assert.equal(calls[0].workers, 1)
8889
assert.equal(calls[0].wordpressInstallMode, "do-not-attempt-installing")
8990
assert.equal(calls[0].skipSqliteSetup, true)
9091
assert.equal(calls[0].phpEnv?.DB_PASSWORD, "secret")
@@ -135,6 +136,7 @@ try {
135136
await defaultRuntimeIniServer[Symbol.asyncDispose]()
136137

137138
assert.equal(calls.length, 1)
139+
assert.equal(calls[0].workers, 1)
138140
assert.deepEqual(calls[0].phpIniEntries, { memory_limit: "512M" })
139141
assert.equal(calls[0].skipSqliteSetup, false)
140142
assert.equal(shouldUseProgrammaticPlaygroundRunner(defaultRuntimeIniSpec), true)
@@ -146,6 +148,7 @@ try {
146148
...defaultRuntimeIniSpec.environment,
147149
version: "latest",
148150
wordpressInstallMode: undefined,
151+
workers: undefined,
149152
assets: undefined,
150153
},
151154
}
@@ -158,6 +161,7 @@ try {
158161
assert.equal(calls[0]["mount-before-install"]?.[0]?.vfsPath, "/internal/wp-codebox")
159162
assert.match(calls[0]["mount-before-install"]?.[1]?.vfsPath ?? "", /^\/wordpress\/wp-codebox-execute-[a-f0-9]{24}\.php$/)
160163
assert.equal(calls[0].wordpressInstallMode, undefined)
164+
assert.equal(calls[0].workers, 6)
161165
assert.equal(shouldUseProgrammaticPlaygroundRunner(downloadedWordPressSpec), false)
162166

163167
calls.length = 0

0 commit comments

Comments
 (0)