Re: Re: PHP True Async RFC Stage 5
| From: | Rob Landers | Date: | Sat, 15 Nov 2025 14:09:51 +0000 |
| Subject: | Re: Re: PHP True Async RFC Stage 5 | ||
| References: | 1 2 3 4 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-129231@lists.php.net to get a copy of this message | ||
On Sat, Nov 15, 2025, at 13:16, Edmond Dantes wrote:
> Hello all.
>
> Some of these questions sound familiar. Let’s try to sort them out.
>
> > If I have a tight loop processing data in memory (no I/O), will it monopolise the
> > coroutine scheduler? Do I need to manually insert suspend() calls? How do I know when and where?
>
> A coroutine must yield control on its own. If it keeps it through an
> endless loop, then it will be the only one running.
>
> > The RFC suggests that existing PHP functions won’t automatically be non-blocking. So
> > which will? Is there a way to identify suspension points at the language/API level?
>
> The RFC does not say that. PHP I/O functions automatically become
> non-blocking relative to the whole process. In other words, an I/O
> function calls suspend() on its own when needed. The programmer writes
> code exactly as before, under the illusion that operations are
> executed one after another.
>
> > Performance implications: Without knowing where suspensions occur, how do developers avoid
> > either:
>
> In most cases, this is not the developer’s concern. Situations where
> performance is critical should be handled with dedicated tools.
> A PHP developer should not have to drop down to the C level. Properly
> designed abstractions must provide the required performance.
>
> I can already anticipate the question: but a developer could write
> something like “for i < 10000 suspend” or something similar.
> The answer is this: a developer must know how to use abstractions. As
> always. Everywhere. In any area of programming.
> It’s just as important as respecting proper layering in code.
>
> Provided that PHP does not try to play the role of a C-level language
> (there have already been such attempts, and they keep resurfacing),
> and does not try to play a web server or a database system.
> For most web scenarios, the current approach is more than sufficient.
> This has been proven by Swoole, which has been on the market for many
> years. Therefore, performance questions are outside the scope of this
> RFC.
>
> As for the concurrency model, let me remind you that Go has true
> multitasking. Goroutines in Go, although they have a “synthetic”
> stack. But you already know all this well.
> This RFC and its implementation describe coroutines in a single
> thread. That is very far from what Go provides.
>
> There is no preemptive multitasking in this RFC because it is
> completely unrelated.
> This RFC and its implementation provide cooperative concurrency in a
> single thread, where coroutine code yields control on its own.
> Why was this model chosen? The simple answer is: because it is the
> only one that can realistically be implemented within a finite
> timeframe.
>
> Any more questions?
>
> -- Ed
Hey Ed,
I feel like we’re talking past each other a bit. I’m not questioning the choice of a cooperative
scheduler, that’s totally fine, I’m trying to understand what the actual suspension points are.
Every other language/runtime with cooperative concurrency spells this out, because without it
developers can’t reason about performance or why a deadlock is happening.
Based on the conversation so far, I’d imagine the list to look something like:
- network i/o (streams/sockets/dns/curl/etc)
- sleeps
- subprocess waits?
proc_open and friends?
- extensions with a reactor implementation
- awaiting FutureLike
- suspend()
If that’s the intended model, it’d help to have that spelled out directly; it makes it
immediately clear which functions can or will suspend and prevents surprises.
I also think the RFC needs at least minimal wording about scheduler guarantees, even if the details
are implementation-specific. For example, is the scheduler run-to-suspend? FIFO or round-robin
wakeup? And non-preemptive behaviour only appears here in the thread. It isn’t mentioned in the
RFC itself. That’s important for people writing long, CPU-bound loops, since nothing will
interrupt them unless they explicitly yield.
Lastly, cancellation during a syscall is still unclear. If a coroutine is cancelled while something
like fwrite() or a DB write is in progress, what should happen? Does
fwrite() still return the number of bytes written? Does it throw? For write-operations
in particular, this affects whether applications can maintain a consistent state.
Clarifying these points would really help people understand how to reason about concurrency with
this API.
— Rob