Skip to content

Conversation

@adamziel
Copy link
Collaborator

@adamziel adamziel commented Aug 26, 2025

Motivation for the change, related issues

Moves the rotatePHPRuntime() logic inside the PHP class and properly await them to prevent async rotation failures:

{
  type: 'request.error',
  error: Error: PHP.run() failed with exit code 255.
      at runExecutionFunction.then.dispatchEvent.type (/Users/cloudnik/www/Automattic/core/plugins/wordpress-playground/packages/php-wasm/universal/src/lib/php.ts:1052:14),
  source: 'php-wasm'
}
Trace: hotSwapPHPRuntime called on a disposed PHP instance
    at PHP.hotSwapPHPRuntime (/Users/cloudnik/www/Automattic/core/plugins/wordpress-playground/packages/php-wasm/universal/src/lib/php.ts:1272:13)
    at rotateRuntime (/Users/cloudnik/www/Automattic/core/plugins/wordpress-playground/packages/php-wasm/universal/src/lib/rotate-php-runtime.ts:45:14)
    at async rotateRuntimeForPhpWasmError (/Users/cloudnik/www/Automattic/core/plugins/wordpress-playground/packages/php-wasm/universal/src/lib/rotate-php-runtime.ts:64:4)

Implementation

The rotatePHPRuntime() function kills the "worn out" PHP Emscripten runtimes and replaces them with new ones. php-fpm does the same thing. Rotating the runtime happens in response to a few events emitted by the PHP class. However, rotation is an async operation and event listeners are expected to be synchronous. As a result, a PHP instance may get killed and the rotate handler will attempt to rotate it asynchronously, causing errors such as the one cited above.

This PR inlines that logic in the PHP class via a new php.enableRuntimeRotation() method. The PHP instance now keeps track if the rotation condition internally and optionally rotates the runtime before every php.run(), php.runStream(), and php.cli() call. This approach also has other benefits:

  • Ability to call php.cli() and php.run() multiple times without trashing the instance – the rotation is handled internally.
  • It creates a way to get rid of the primary and secondary PHP instance concept in the PHPProcessManager class. We no longer need to worry about accidentally trashing the primary instance by calling .cli() on it.
  • The rotation is now lazy, not eager, and only happens on the next PHP code execution.

Potential follow-up work

  • Expose a static PHP.create( runtimeFactory ) method to simplify the instance constructions. Right now you need to do const php = new PHP( runtimeId ); PHP.enableRuntimeRotation( runtimeFactory );.

Testing Instructions (or ideally a Blueprint)

CI

brandonpayton and others added 30 commits August 15, 2025 19:23
- Fixed conflict in symlink test - preserved both HEAD and trunk functionality
- Fixed conflict in auto-mount tests - merged both versions properly
- Fixed TypeScript errors by changing autoMount from boolean to string
- All tests now work with both Blueprint v1 and v2 versions
@adamziel adamziel changed the title [PHP] Unbind rotatePHPRuntime() handler when PHP instance is destroyed [PHP] Move rotatePHPRuntime() logic into the PHP class Aug 27, 2025
delete php[__private__dont__use].journal;
}
php.addEventListener('runtime.beforedestroy', unbindFromOldRuntime);
php.addEventListener('runtime.beforeExit', unbindFromOldRuntime);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Technically the PHP instance can still be reinitialized with another PHP runtime so it's not destroyed

if (!this.#webSapiInitialized) {
await this.#initWebRuntime();
this.#webSapiInitialized = true;
if (!this.#phpWasmInitCalled) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I inlined this call to reduce the mental load – there were too many names for me to follow

Add error handling to ensure original error is rethrown.
@adamziel
Copy link
Collaborator Author

@brandonpayton can you see any problems with this implementation?

@adamziel adamziel requested a review from brandonpayton August 28, 2025 11:30
@adamziel adamziel changed the title [PHP] Move rotatePHPRuntime() logic into the PHP class [PHP] Inline rotatePHPRuntime() into the PHP class Aug 28, 2025
@adamziel
Copy link
Collaborator Author

adamziel commented Sep 3, 2025

This is blocking other work so I'll go ahead and merge. Feel free to still leave feedback or even rollback if this ended up having any unintended consequences.

@adamziel adamziel requested a review from a team September 3, 2025 10:57
@adamziel adamziel merged commit f0e4956 into trunk Sep 3, 2025
26 checks passed
@adamziel adamziel deleted the unbind-hotswap branch September 3, 2025 10:57
adamziel added a commit that referenced this pull request Sep 3, 2025
This PR ensures setting the spawn handler once is effective for the
entire lifetime of a given PHP instance. Before this PR, the spawn
handler would not be re-bound to the new PHP runtime in the
hotSwapPHPRuntime() call.

Follows up on #2559. The spawn handler was not preserved even before
more prominent.

 ## Testing instructions

CI – there's a new test to ensure the spawn handler is preserved after
the runtime rotation / swapping
adamziel added a commit that referenced this pull request Sep 3, 2025
This PR ensures setting the spawn handler once is effective for the
entire lifetime of a given PHP instance. Before this PR, the spawn
handler would not be re-bound to the new PHP runtime in the
hotSwapPHPRuntime() call.

Follows up on #2559. The spawn handler was not preserved even before
more prominent.

 ## Testing instructions

CI – there's a new test to ensure the spawn handler is preserved after
the runtime rotation / swapping
adamziel added a commit that referenced this pull request Sep 3, 2025
This PR ships the web wasm binaries with the `run_cli` C function that
enables calling php.cli(). Intertwining the CLI SAPI usage with the web
SAPI usage was impractical before #2559, but now we can easily call
`await php.cli(); await php.run()` on the same PHP instance.

Note this PR does not expose the `.cli()` method to the public yet. It
only ships the updated WASM binaries to unlock that.

 ## Testing instructions

 CI
adamziel added a commit that referenced this pull request Sep 3, 2025
This PR ships the web wasm binaries with the `run_cli` C function that
enables calling php.cli(). Intertwining the CLI SAPI usage with the web
SAPI usage was impractical before #2559, but now we can easily call
`await php.cli(); await php.run()` on the same PHP instance.

Note this PR does not expose the `.cli()` method to the public yet. It
only ships the updated WASM binaries to unlock that.

 ## Testing instructions

 CI
adamziel added a commit that referenced this pull request Sep 3, 2025
## Motivation for the change, related issues

This PR:

1. Exposes the `php.cli()` method for in-browser PHP.wasm consumers
2. Starts using it in the Playground website by swapping the custom
spawn handler for the universal `sandboxedSpawnHandlerFactory()` that
calls `php.cli()` and is consistent across all Playground runtimes.

For context on what makes this possible, see #2589 and #2559.

## Testing Instructions (or ideally a Blueprint)

CI
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.

3 participants