- Notifications
You must be signed in to change notification settings - Fork 349
PHP Process Manager #1301
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
PHP Process Manager #1301
Conversation
Adds an optional `timeout` option to the Semaphore class that throws an error when the Semaphore cannot be acquired for `timeout` milliseconds. Related to #1287 ## Testing instructions Confirm the unit tests pass
Adds a PHP Process manager, a class that can spawn maintain a number of PHP instances at the same time. The idea is to have: * A single "primary" PHP instance that's never killed – it contains the reference filesystem used by all other PHP instances. * A pool of disposable PHP instances that are spawned to handle a single request and reaped immediately after. Spawning new PHP instances is reasonably fast and can happen on demand, there's no need to keep a pool of "warm" instances that occupies the memory all the time. Example usage: ```ts const mgr = new PHPProcessManager({ phpFactory: async () => NodePHP.load(RecommendedPHPVersion), maxPhpInstances: 4, }); const instance = await mgr.getInstance(); await instance.php.run({ code }); instance.reap(); ``` Related to #1287 ## Testing instructions Confirm the unit tests pass. This PR only adds new code, it does not plug in the PHPProcessManager class into any request dispatching code yet.
Memoizes fetch() responses related to `php.wasm` files to avoid slow network calls every time a new PHP instance is spawned by [PHPProcessManager](#1301). This is related to [Loopback request support](#1287) where we have to spawn additional PHP instances on demand. ## Testing instructions Confirm CI checks passed.
Memoizes fetch() responses related to `php.wasm` files to avoid slow network calls every time a new PHP instance is spawned by [PHPProcessManager](#1301). This is related to [Loopback request support](#1287) where we have to spawn additional PHP instances on demand. ## Testing instructions Confirm CI checks passed.
cc @dmsnell @brandonpayton @bgrgicak for reviews – this is an important component and I'd love to discuss it before moving further. |
Memoizes fetch() responses related to `php.wasm` files to avoid slow network calls every time a new PHP instance is spawned by [PHPProcessManager](#1301). This is related to [Loopback request support](#1287) where we have to spawn additional PHP instances on demand. ## Testing instructions Confirm CI checks passed.
The approach makes sense to me. Do you have any specific concerns? |
Nothing specific, I just wanted to make sure I get some really smart folks to take a look :-) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@adamziel, here are my thoughts and questions so far. I may keep reading and thinking about this for a bit but want to get some feedback out there.
In addition to the inline feedback, some other questions are:
- Does there need to be a primary PHP instance?
- What happens if a consumer disposes of the primary PHP instance? Does it matter?
- Would it make sense to track parent/child relationships? Maybe that is something best left to the consumer of the PHP Process Manager.
…ow for any others. Otherwise, an error other than AcquireTimeoutError will be hidden and masquerade as a max-instances condition.
All good notes @brandonpayton, thank you!
They don't get access to a
I was thinking about that and here's what I realized:
|
The unit test failure is an intermittent timeout. I've addressed the feedback and I'll go ahead and merge this one. Thank you so much for the discussion! |
Adds support for PHP spawning more PHP instances: ```ts const handler = new PHPRequestHandler({ documentRoot: '/', phpFactory: async ({ isPrimary }) => { const php = await createPhp(requestHandler); if (!isPrimary) { proxyFileSystem( await requestHandler.getPrimaryPhp(), php, requestHandler.documentRoot ); } return php; }, maxPhpInstances: 2, }); const php = await handler.getPrimaryPhp(); php.writeFile('/second-script.php', `<?php echo 'Hi from another PHP instance!';`); php.writeFile( '/main.php', `<?php require "/wordpress/load.php"; // Can send HTTP requests to self echo file_get_contents( get_site_url() . '/second-script.php' ); // Can execute PHP scripts echo exec( 'php /second-script.php ); ` }) await startLocalServer( handler ); await handler.request({ url: '/main.php', }); ``` Without this PR, we only have a single PHP instance running. If that instance needs to run another PHP script while it's running, it will get error 502 as there's no other PHP instance available to handle that. Closes #1182 Closes #1177 ## Technical implementation This PR draws a clear line between BaesPHP and PHPRequestHandler. Before this PR, BasePHP could be given a `serverOptions` argument and it would create a `new PHPRequestHandler` as a convenience. After this PR, BasePHP longer instantiates `PHPRequestHandler`. Why? Two reasons: * We may have multiple PHP instances but only ever want a single `PHPRequestHandler` to orchestrate them. * `PHPRequestHandler` must know how to start new PHP instances, but BasePHP shouldn't be concerned with that at all. separates PHP leverages two major building blocks: Furthermore, `PHPRequestHandler` now uses [PHPProcessManager](#1301) to spawn new PHP instances on demand. To create a new request handler, you now need to pass a `processManager` instance or, alternatively, a `phpFactory` function. Ideally, `BasePHP` would have no more references to `PHPRequestHandler` after this PR, but unfortunately we cannot fully decouple the two just yet. `BasePHP` exposes an `absoluteUrl`, `documentRoot`, and a `request` method that are used by Blueprints, and Blueprint steps only get a `BasePHP` instance without any extra contextual information. This will be easier to resolve once we switch to [PHP Blueprints](https://github.com/WordPress/blueprints-library/) where we'll never call `php.request()` and we'll also be able to access these contextual information via `$_SERVER` and such. ## Follow up work * #1233 * Create a separate, minimal Emscripten module as a source of the Filesystem. Remove the concept of a "primary" PHP instance. * Decouple BasePHP from the PHPRequestHandler. The dependencies are super intertwined now – BasePHP needs a PHPRequestHandler instance, but PHPRequestHandler acts on BasePHP. PHPProcessManager needs both, but also configuring a spawn handler requires a PHPProcessManager. * `UniversalPHP` and such are interchangeable with both `BasePHP` and `WebPHPEndpoint`. This isn't intuitive, especially now that `WebPHPEndpoint` acts on many PHP instances. Let's make `BasePHP` distinct from `WebPHPEndpoint`. They will share some methods and props, but will also have disjoint ones.
…xhausted (#1324) Fixes a bug in the [PHPProcessManager](#1301) where `acquirePHPInstance()` would continue returning `this.nextInstance` even after the semaphore concurrency limit was exhausted. The solution is to clear `this.nextInstance` for the next `acquirePHPInstance()` call. ## Testing instructions Confirm the CI checks pass
Adds a PHP Process manager, a class that can spawn maintain a number of PHP instances at the same time.
The idea is to have:
Spawning new PHP instances is reasonably fast and can happen on demand, there's no need to keep a pool of "warm" instances that occupies the memory all the time.
Example usage:
Related to #1287
Remaining work
getInstance()
to enable setting the correct SAPI Name, e.g.CLI
orFPM
depending on the purpose the PHP instance is created for. This would solve issues like PHP content type headers ignored? #1223. Alternatively, the consuming program could callsetSapiName()
on the PHP instance returned bygetInstance()
– but that assumes the factory did not initialize the web runtime.Testing instructions
Confirm the unit tests pass. This PR only adds new code, it does not plug in the PHPProcessManager class into any request dispatching code yet.