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

Spawning PHP sub-processes in Web Workers #1069

Merged
merged 2 commits into from
Feb 28, 2024
Merged

Spawning PHP sub-processes in Web Workers #1069

merged 2 commits into from
Feb 28, 2024

Conversation

adamziel
Copy link
Collaborator

@adamziel adamziel commented Feb 28, 2024

Related to #1026, #1027, and #1051. Supersedes #1031

Adds support for spawning PHP subprocesses via <?php proc_open(['php', 'activate_theme.php']);. The spawned subprocess affects the filesystem used by the parent process.

Implementation details

This PR updates the default spawnHandler in worker-thread.ts that creates another WebPHP instance and mounts the parent filesystem using Emscripten's PROXYFS.

A shared filesystem didn't pan out. Synchronizing is the second best option.

This code snippet illustrates the idea – note the actual implementation is more nuanced:

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

Future work

  • Stream stdout and stderr from childPHP to processApi instead of buffering the output and passing everything at once

Example of how it works

/wordpress/spawn.php

<?php
echo "<plaintext>";
echo "Spawning /wordpress/child.php\n";
$handle = proc_open('php /wordpress/child.php', [
	0 => ['pipe', 'r'],
	1 => ['pipe', 'w'],
	2 => ['pipe', 'w'],
], $pipes);

echo "stdout: " . stream_get_contents($pipes[1]) . "\n";
echo "stderr: " . stream_get_contents($pipes[2]) . "\n";
echo "Finished\n";
echo "Contents of the created file: " . file_get_contents("/wordpress/new.txt") . "\n";

/wordpress/child.php

<?php
echo "<plaintext>";
echo "Spawned, running";
error_log("Here's a message logged to stderr! " . rand());
file_put_contents("/wordpress/new.txt", "Hello, world!" . rand() . "\n");

Testing instructions

  1. Update worker-thread.ts to create the two files listed above
  2. In Playground, navigate to /spawn.php
  3. Confirm the output is the same as on the screenshot above

cc @dmsnell @bgrgicak

@adamziel adamziel changed the title Spawn php proxyfs Spawning PHP sub-processes in Web Workers Feb 28, 2024
@adamziel adamziel merged commit 24f2c61 into trunk Feb 28, 2024
5 checks passed
@adamziel adamziel deleted the spawn-php-proxyfs branch February 28, 2024 14:32
adamziel added a commit that referenced this pull request Feb 28, 2024
## What is this PR doing?

Supersedes #1051

Adds a PHP Blueprints demo page where the use of
[blueprints.phar](WordPress/blueprints#28) PHP
library in Playground may be further explored. The showcase is
intentionally not added to
http://localhost:5400/website-server/demos/index.html as PHP Blueprints
may become a part of Playground core soon enough.

For more context see:

* #1025
* https://github.com/WordPress/blueprints

## How does it work?

* The built Blueprints library is included with this PR via the
`blueprints.phar` file
* A number of PHP.wasm improvements have been merged to support it:
   * #1064
   * #1065
   * #1069
* This PR ships a `fetch` subprocess handler to enable streaming network
data in the Blueprints library – it uses [a special network transport
called
`fetch`](https://github.com/WordPress/blueprints/blob/efa8deef56095bd5bcb94868787e29f7b54350f3/src/WordPress/DataSource/PlaygroundFetchSource.php)
that requests network data via `proc_open()` when running in Playground.
Why subprocesses? They provide everything a custom network handler
needs: pipes, asynchronous execution, exit codes, internal PHP plumbing.

## Follow-up work

* Support a real-time progress bar

## Testing instructions

Go to http://localhost:5400/website-server/demos/php-blueprints.html and
confirm it looks like on the screenshot below:

![CleanShot 2024-02-28 at 15 46
14@2x](https://github.com/WordPress/wordpress-playground/assets/205419/47a91d99-07f3-40a5-a046-b58f8cda952e)
Comment on lines +339 to +350
if (!childPHP.fileExists(path)) {
childPHP.mkdir(path);
}
childPHP[__private__dont__use].FS.mount(
childPHP[__private__dont__use].PROXYFS,
{
root: path,
fs: php[__private__dont__use].FS,
},
path
);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice! Glad you found a way without copying the files.

define('STDIN', fopen('php://stdin', 'rb'));
define('STDOUT', fopen('php://stdout', 'wb'));
define('STDERR', fopen('/tmp/stderr', 'wb'));

Copy link
Collaborator

Choose a reason for hiding this comment

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

Should this be /internal/stderr?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oh! Yeah if that remains a file path then yes. I wonder why this isn't php://stderr actually, I think I copied it over from #161 and, at that time, there was a bug preventing the use of php://stderr.

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 created an issue to track it here: #1071

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

Successfully merging this pull request may close these issues.

2 participants