What refactoring the TSRM gets us. I think? #1980
Replies: 6 comments 13 replies
-
|
I mean I'd love it, it would essentially level the playing field with swoole. But it would also condemn us to maintain the same i/o extension hooks and we'd need very strict testing to ensure we're still 1:1 with php-fpm in classic mode (in behaviour, outside of php's thread bugs). |
Beta Was this translation helpful? Give feedback.
-
|
Nice 👍 good to see that you've already made some progress. Scenario 1: PHP runs on C threads (blocking)This is the current scenario. We need a worker thread for each concurrent request. Worker threads are spawned dynamically if needed. Each thread has its own dedicated connection to e.g a database. Scenario 2: PHP runs on go threads (blocking)If PHP runs on go threads instead, but IO is still blocking, we'd basically have to lock the go OS thread when serving requests. That would save having to cross the following thread boundaries:
That would definitely save some friction. I suspect though that having to spawn additional Go OS threads (basically 1 for every concurrent requests) would outweigh that friction. Scenario 3: PHP runs on go threads (non-blocking)I think it's more interesting if PHP actually runs on go threads while PHP IO is non-blocking. Now we only need the standard amount of go threads and save on having to cross the boundaries between go and C. This might potentially also unlock some other optimizations, but we have to deal with all the downsides that come from having 2 runtimes on the same thread. And each 'worker instance' will only be able to handle one request at a time, not sharing its memory with other instances. Scenario 4: PHP runs on C threads (async non-blocking)Async runtimes have the advantage that you can re-use memory across concurrent requests. This allows for many potential optimizations like connection pools. The downside is of course that you can re-use memory across concurrent requests 😅 , introducing a new potential source for bugs. PHP being async is actually the Swoole model. In our case workers run in threads instead of processes. Scenario 5: PHP runs on go threads (async, non-blocking)Additionally to the Swoole async model, PHP can also run directly on go threads. Go would in that case be the async backend for PHP and we don't have to cross thread boundaries. Each go thread has access to multiple async PHP workers, seamlessly switching between them. We would only need Scenario 6: PHP runs on go threads (async, non-blocking, single instance)Even if PHP runs asynchronously, it would not fully reflect the go execution model. Every global go variable is global per-process, not per-instance or per-thread. It might be possible to just start a single PHP worker instance and have its globals be fully unsafe across the whole process. PHP would then handle requests asynchronously and in-parallel, exactly like go. That would be the 'true go mode', but it would also require devs to lock globals and PHP's memory management is very far from being safe in such a scenario. |
Beta Was this translation helpful? Give feedback.
-
Actually, swoole has a thread model with ZTS builds! Very similar to what we'd be doing (if we keep php execution in C land). Php running directly in Goroutines sounds great, but we'd need to pin them to OS threads as far as I can imagine. And how to keep both runtimes from colliding, no idea. My understanding of the Go runtime is limited to "it doesn't do things the same way that other native runtimes work". |
Beta Was this translation helpful? Give feedback.
-
This description fits the existing VM interrupt mechanism, and how signals are delivered by pcntl currently :) |
Beta Was this translation helpful? Give feedback.
-
|
Hello everyone. So, we've detached ZTS from the thread, and now the PHP VM can hop between threads like a butterfly. This is great. And if we want to bind a specific PHP coroutine to a specific Go coroutine, that likely won’t be possible, because PHP code won’t be able to jump from one VM to another by crossing virtual-thread boundaries. On the other hand, we can implement this scheme: I don’t see any conflicts here. |
Beta Was this translation helpful? Give feedback.
-
|
Combining 'virtual threads' with coroutines also has its merits. This is how a 'maximum backwards compatibility' approach could look like:
Won't solve all the additional complexity from async, but would be compatible with current PHP code. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
I’m working on a major refactoring of PHP’s Thread Safe Resource Manager (TSRM) that’ll eventually enable FrankenPHP to run PHP requests as native Go goroutines. Here’s where I’m at, along with some pie-in-the-sky.
I’m nearly 80–90% through with refactoring TSRM and about to start playing with it on FrankenPHP. One thing to note is that thread-pools can go away, eventually. Instead of a specific thread, you pass PHP a context handle and it’ll do the thing. That being said, this is only the beginning of a larger plan.
TSRM changes
For the most part, TSRM remains almost the same. I’ve got a working compatibility layer so that for existing extensions/SAPIs, nothing has to change. For FrankenPHP, however, we’ll use the new stuff. This basically amounts to:
The key difference here is that the context (globals/CG/EG/etc) can actually migrate between threads. No more allocating special worker pools, or dealing with num workers. Instead, you have a pool of threads that you allocate contexts to as needed. You can have more contexts than threads (so, 15 workerA contexts, 10 workerB contexts, but only 15 threads) and then only need to spin up new threads when required to handle the load. This is waaay cheaper than what we currently have (I hope).
All that being said, little will change unless we have ...
Go-based signal handling
Once we have a TSRM that can migrate between threads, then we need a way to deliver signals so they can actually migrate between them as well. This means we’ll have to have some way to dispatch these signals from Go to PHP. This will also have to introduce an API into PHP where we can "interrupt" PHP contexts on-demand.
I’ve identified several safe points where this can be done:
This also conveniently gives us preemption in PHP... and this doesn’t need the TSRM bit at all. This is a bit tricky, though, because other extensions might use signals. So, we’ll need to figure out some sort of API for extensions or something. (Yes, I know the "or something" is carrying a lot of weight here)
We can start connecting up a preemptive scheduler that works with the go scheduler to run php preemptively, essentially making thread pools a thing of the past and having a single Go thread handling hundreds (if not thousands) of requests fairly, and concurrently; all across a much smaller number of threads. PHP requests basically could become just another goroutine.
When
This will take awhile. Maybe PHP 9.x? This is mostly pie-in-the-sky thinking right now, and glosses over the technical (and political) details and challenges (there are a ton). But, I wanted to share my vision and see what you think.
Beta Was this translation helpful? Give feedback.
All reactions