Re: [RFC] True Async RFC 1.7
| From: | Larry Garfield | Date: | Sun, 25 Jan 2026 21:20:16 +0000 |
| Subject: | Re: [RFC] True Async RFC 1.7 | ||
| References: | 1 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-129924@lists.php.net to get a copy of this message | ||
On Thu, Dec 25, 2025, at 1:57 AM, Edmond Dantes wrote:
> Hello everyone!
>
> RFC https://wiki.php.net/rfc/true_async
>
> Here is the seventh version of the RFC, revised and expanded.
Notes, collected as i go:
- Most RFCs have an example in the introduction of what the proposed syntax looks like in a small
but meaningful example. Please include such, as it helps "set the stage" and provide
context for the rest of the RFC.
- Please break up the API Overview code block into several pieces by type to make scrolling easier.
- As I've said before, I'd rather have keywords than functions for the main API.
- Please please do NOT have finally() be a function on its own. current_coroutine()->finally()
is sufficient. Having it on its own is going to be too confusing with the finally keyword for
exceptions. Can we find a better name for it that isn't confusing with exceptions?
onCompletion()?
What is the difference then between $coroutine->finally() and register_shutdown_function(), when
the current coroutine happens to be main?
- getSpawnFileAndLine() and getSpawnLocation() - It's non-obvious when they're first
mentioned what the difference is. I don't know if they're described better further down,
but they should have a docblock here to avoid confusion.
- getAwaitingInfo(): array - Hell no, no struct arrays. Make it a class.
- Remember, we now have PFA, so
spawn('sleep', 2) can now be spelled
spawn(sleep(2, ...)), to make it available for IDE analysis. :-) (I'm not sure
what the implications of that are/should be for all the other "callable and arg array"
cases.) But at very least, please use func_name(...), not a string, in all examples.
Function name strings should basically never be used again. (Another argument in favor of
keywords.)
- "The non-blocking version of the file_get_contents function is not part of this RFC." -
Wait, I'm confused. I though the entire point of this RFC was that we don't need a
user-exposed alternate version of file_get_contents(). It just blocks or doesn't as needed.
Now you're saying that's not the case? We need to use the silly spawn() syntax or it will
block our coroutine? That's a no-go for any existing library that happens to have a
file_get_contents() in it, which would then block if it gets called inside a coroutine it
doesn't expect. If that's not the case, then this comment is highly misleading. Either
way, it's scary.
- "This means that multiple calls to select <Awaitable> may produce different results.
" - I don't believe a select keyword or function has been discussed to this point, so this
line is confusing.
- Completable interface section needs an example. I don't grok what it means in practice. I
also don't understand what Awaitable is for other than being a parent of Completable. At least
not yet by this point in the RFC.
- The examples seem very inconsistent. Some are using foo(...), some are using strings, and the
"await with cancellation" example is using a long-closure syntax for reasons I do not
fathom. The inconsistency is making it difficult to get a sense for what typical usage will be
like.
- "As soon as the $cancellation is triggered, execution is interrupted with an exception "
- What does triggered mean? When it starts? Ends? Something else? What's a use case here
other than sleep? This is all very unclear.
- In Fibers, in the basic example, "start() blocks until the fiber completes" - I assume
that means it blocks the calling coroutine? Or does it block the whole system until it completes?
- I have no idea what it means for coroutines to be "symmetric". This is stated as a
thing, but I have no clue what that means. That makes much of the Fibers section incomprehensible.
- I will have to defer to the existing async gurus (especially Aaron P of AmPHP, who wrote the
Fibers RFC) to determine if the FIber integration is logical and reasonable. For me, it feels very
complicated but that may be necessary. I am not sure.
- "Active coroutines (running or suspended) are protected from collection while they have
pending operations" - If I'm reading that correctly, it means
span(very_long_function(...)) will start a coroutine, but not assign it to a variable.
So I cannot track it or refer to it, but it will still run in the background until it completes, at
which point it will get GCed. Is that correct?
- The FrankenPHP example linked to... doesn't seem to do anything to start a coroutine. I
don't know what it's supposed to be demonstrating other than FrankenPHP itself.
- OK, now all the way at the bottom we get to an explanation of getSpawnFileAndLine() vs
getSpawnLocation(). I am not convinced we need both, and the naming is highly confusing. More
discussion to have here.
- Non-Async code can use Cancellation, but... what does that even mean or do? Why would someone use
it? This is sneaking in a 3rd error type without much guidance. catch(Throwable) is surprisingly
common (rightly or wrongly), so this feels like a major pothole potential.
- Oh crap, Scopes are now a SEPARATE RFC, of similar length? See previous "this thing is
huge" comment... I do not have the bandwidth right now to review that, but I feel like I have
to in order to speak to this one intelligently. I also haven't gotten to read any of the
linked references, as I have run out of time.
More general comments:
This seems mostly logical as a low-level API. As I have stated before, though, I don't want a
low-level API. I want a *safe* API. I am not convinced those are compatible. And I certainly do
not want to see the low-level API in one release and then have to wait for the next release for the
safer API. Having pipes and PFA in separate releases is bad enough. :-)
That this allows for zombie coroutines or "lost" background coroutines to exist is, to me,
a major problem. I realize changing that means bringing scopes back in, which makes it bigger,
but... I don't want to hand footguns to a million PHP developers with no async experience. I
don't trust us with that kind of power to not do something grossly stupid.
As I've said before, I don't like await, spawn, etc. being free-standing functions rather
than keywords. That is very limiting on the syntax they can use. It also means, as the examples
show, we get this pattern very often:
await(spawn($fn));
That's very clumsy. It introduces two layers of wrapped functions, which means a lot of extra
parentheses.
I... have no idea what would happen if one used pipes here, but that's also an open question.
I think it would be:
$result = $fn |> spawn(...) |> await(...);
I am not sure if I like that or not. But regardless, the "start coroutine and block until
it's done" operation is common enough that it needs a cleaner way of writing it than two
nested function calls. We can debate what exactly that is, but we need it.
I would much rather have $cor->onComplete() than finally(). And absolutely no global function by
that name, which is way too easy to confuse with the existing keyword.
All the use of inline function callbacks also, once again, demonstrates why our current long-closure
syntax is a problem. We need to fix that, for simple ergonomics if nothing else. It makes this
sort of code a pain in the ass.
--Larry Garfield