Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support spawning PHP sub-processes #1026

Closed
adamziel opened this issue Feb 11, 2024 · 1 comment
Closed

Support spawning PHP sub-processes #1026

adamziel opened this issue Feb 11, 2024 · 1 comment
Labels

Comments

@adamziel
Copy link
Collaborator

adamziel commented Feb 11, 2024

Implementing Blueprints as a PHP library requires spawning PHP processes from PHP processes as follows:

<?php proc_open(['php', 'activate_theme.php']);

Rationale

Complex tasks, such as activating WordPress Plugins, involve loading WordPress via require "wp-load.php"; to run WordPress functions, such as activate_plugin(). However, loading WordPress in the same PHP process that executes a Blueprint has significant downsides:

  • WordPress left in a broken state can't be recovered
  • A fatal error in WordPress would instantly kill the Blueprint executor
  • WordPress defines global constants that subsequent steps may change (e.g. WP_DEBUG or MULTISITE)
  • Once WordPress is updated, it can't be reloaded without running into "function already defined" errors

Multiprocessing model

Goal: Playground can spawn a PHP.wasm subprocesses in the Browser, Node.js, and VS Code.

fork() isn't an option

In native PHP, proc_open calls fork() in the main process and substitutes the child process with the requested command. Emscripten, however, doesn't support fork(). Playground's PHP.wasm polyfills proc_open with a custom Asyncify-based implementation.

Reusing the current PHP instance is not an option

This *would crash:

php.setSpawnHandler(
	createSpawnHandler(async function (args, processApi) {
		const result = await php.run({
			scriptPath: args[1]
		});
		processApi.exit(result.exitCode);
	})
);

Internally, PHP sets a bunch of global variables. Calling run() before the previous run() is finished would either fail or leave us in an undefined state.

We need to start a fresh PHP instance

This would not crash:

php1.setSpawnHandler(
	createSpawnHandler(async function (args, processApi) {
		const php2 = await WebPHP.load();
		const result = await php2.run({
			scriptPath: args[1]
		});
		processApi.exit(result.exitCode);
	})
);

php2 could be spawned either:

  • In the same web worker as php1. Upsides: Easy resource sharing much easier. Downsides: ?
  • In a new web worker. Upsides: ?. Downsides: Sharing resources would involve postMessage(), transferrable objects, SharedArrayBuffers, and other advanced APIs.

Filesystem sharing

Just spawning php2 wouldn't quite solve the problem as php2 would act on a separate in-memory filesystem (MEMFS) without affecting the WordPress residing in php1.

Let's discuss this in a dedicated issue:

@adamziel
Copy link
Collaborator Author

Done in #1069

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant