Skip to content

Spawning PHP sub-processes in Web Workers - #1069

Merged
adamziel merged 2 commits into
trunkfrom
spawn-php-proxyfs
Feb 28, 2024
Merged

Spawning PHP sub-processes in Web Workers#1069
adamziel merged 2 commits into
trunkfrom
spawn-php-proxyfs

Conversation

@adamziel

@adamziel adamziel commented Feb 28, 2024

Copy link
Copy Markdown
Collaborator

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 Feb 28, 2024
@adamziel
adamziel merged commit 24f2c61 into trunk Feb 28, 2024
@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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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

2 participants