[PHP-DEV] PHP True Async RFC

wrap it in a specific form (async\run or async\async, in this RFC), which ironically colors the function.

It doesn’t color the function.
The function is unchanged.

Any existing function in userland do not have to be changed in any way. It’s calls do not have to be rewritten into await, and all that stuff.

This same statement comes as well to all built-in functions like file_get_contents, that already return needed data, rather than an promise object whereof we could possibly fetch the data from.

for($i = 0; $i < 10; $i++) $results = async\async(fn($f) => file_get_contents($f),
$file[$i]);
// convert $results into futures somehow – though actually doesn’t look like it is
possible.
$results = async\awaitAll($results);

Future can be obtained via getFuture(), according to the current RFC.

async\async(fn($f) => file_get_contents($f), $file[$i])->getFuture();

And this semantics can be simplified to:
async file_get_contents($file[$i]);
or
spawn file_get_contents($file[$i]);

From this perspective, I like that any function can be called with spawn/async without worrying about its internals or modifying its code. The pros and cons of this approach are well known.

On Sat, Mar 8, 2025, at 09:24, Edmond Dantes wrote:

for($i = 0; $i < 10; $i++) $results = async\async(fn($f) => file_get_contents($f),

$file[$i]);

// convert $results into futures somehow – though actually doesn’t look like it is

possible.

$results = async\awaitAll($results);

Future can be obtained via getFuture(), according to the current RFC.


async\async(fn($f) => file_get_contents($f), $file[$i])->getFuture();

And this semantics can be simplified to:

async file_get_contents($file[$i]);

or

spawn file_get_contents($file[$i]);

From this perspective, I like that any function can be called with spawn/async without worrying about its internals or modifying its code. The pros and cons of this approach are well known.

Yes, that is much much nicer! It feel familiar to go:

go file_get_contents($file[$i])

And yes, I realize that would be a fun error in go, but you get the gist.

— Rob

Yes, that is much much nicer! It feel familiar to go:

go file_get_contents($file[$i])

And yes, I realize that would be a fun error in go, but you get the gist.

— Rob

Yes, that’s the point that we don’t bother client code with any of the async stuff :slightly_smiling_face:

If we want to create “async space” for functions to have switching on IO, only then do we call them like this, and the await’em all with awaitAll :slightly_smiling_face:.

In my opinion, colored functions is the worst thing that could happen to PHP.

What Color is Your Function? – journal.stuffwithstuff.com
Describes quite expressively what’s wrong about this approach.

This is going to be a ton of changes, when currently sync (blue function) will have to become async (red one).

The way amphp goes - it’s the right way. They have had this problem of red-blue functions a long ago until Fibers came into place.

This is just annoying, and IMO should not be considered.

+++++ on this, the discussion on this RFC is veering in a very annoying direction for absolutely no good reason.

Golang has shown and proven that we do not need colored functions to make use of (extremely simple to use) concurrency.

Please do not cripple the adoption of async php by making it colored and by adding absolutely useless async blocks, forcing everyone to rewrite their codebases for absolutely no good reason, when with the current colorless, fiber approach the only thing that’s needed to adapt current codebases for async is the usage of channels and a few appropriately placed synchronization primitives (I know this from experience, migrating a large and complex codebase to be fully async).

The only thing that’s truly needed in this RFC is a set of synchronization primitives like in golang, and a way to parent/unparent fibers in order to inherit cancellations (as previously mentioned in this list), not contexts, async blocks and colored functions.

Any issue around $_GET/etc superglobals (i.e. to handle each incoming request in a separate fiber) should be solved at the SAPI level with a separate RFC, not by introducing contexts and async blocks and making concurrency harder to use.

I like and use immutability, but it has it limits, it should not be used everywhere, and it should not be forced upon everyone just because someone is a strong proponent of it.

Regards,
Daniil Gentili.

In my opinion, colored functions is the worst thing that could happen to PHP.

https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function
Describes quite expressively what’s wrong about this approach.

This is going to be a ton of changes, when currently sync (blue function) will have to become async (red one).

The way amphp goes - it’s the right way. They have had this problem of red-blue functions a long ago until Fibers came into place.

This is just annoying, and IMO should not be considered.

+++++ on this, the discussion on this RFC is veering in a very annoying direction for absolutely no good reason.

Golang has shown and proven that we do not need colored functions to make use of (extremely simple to use) concurrency.

Please do not cripple the adoption of async php by making it colored and by adding absolutely useless async blocks, forcing everyone to rewrite their codebases for absolutely no good reason, when with the current colorless, fiber approach the only thing that’s needed to adapt current codebases for async is the usage of channels and a few appropriately placed synchronization primitives (I know this from experience, migrating a large and complex codebase to be fully async).

The only thing that’s truly needed in this RFC is a set of synchronization primitives like in golang, and a way to parent/unparent fibers in order to inherit cancellations (as previously mentioned in this list), not contexts, async blocks and colored functions.

Any issue around $_GET/etc superglobals (i.e. to handle each incoming request in a separate fiber) should be solved at the SAPI level with a separate RFC, not by introducing contexts and async blocks and making concurrency harder to use.

I like and use immutability, but it has it limits, it should not be used everywhere, and it should not be forced upon everyone just because someone is a strong proponent of it.

Regards,
Daniil Gentili.

(Resent again as the other email has deliverability issues on the list).

you //never// get to decide unless you wrap it in a specific form (async\run or async\async, in this RFC), which ironically colors the function.

You’re wrong, it does not color the function: spawning a new fiber does not make the code running it async: it is always async, regardless of whether you use async() or not, but when not using async(), it’s simply the only async execution flow running.

can’t think of any way around it. My biggest issue with this RFC is that it results in multiple colors: FiberHandle, Future, and Resume.

You misunderstand again, colors are not objects or classes, colors are special and annoying keywords (red, blue, await) that must always be added to call functions of the same color (red, blue, await).

The FiberHandle is literally just a handle associated with the execution flow spawned with async(), it is not in any way associated with the spawned function, nor it is required in any way to invoke the spawned function.

Adding colors to functions makes async unnecessarily complex to use, for no good reason at all (no, forcing developers to explicitly know if a function is async or not is not a good enough reason, when I write code I want to get things done, when I want to parallelize execution of something I use go/async(), when I don’t, I couldn’t care less about what the function does inside, and I especially do not want to do a lot of hoop jumping to use it or use other common patterns like functional composition).

Again, take a look at how nicely golang handles concurrency with colorless functions: php fibers weren’t the first to do it.

Regards,
Daniil Gentili.

Mar 8, 2025 9:29:15 AM Edmond Dantes edmond.ht@gmail.com:

for($i = 0; $i < 10; $i++) $results = async\async(fn($f) => file_get_contents($f),
$file[$i]);
// convert $results into futures somehow – though actually doesn’t look like it is
possible.
$results = async\awaitAll($results);

And this semantics can be simplified to:
async file_get_contents($file[$i]);
or
spawn file_get_contents($file[$i]);

From this perspective, I like that any function can be called with spawn/async without worrying about its internals or modifying its code. The pros and cons of this approach are well known.

Loving this.

One might even consider to use the go keyaord along with async/spawn, to more easily associate the operation with go’s (gc)oroutines…

Regards,
Daniil Gentili.

Again, take a look at how nicely golang handles concurrency with colorless functions: php fibers weren’t the first to do it.

Also, a colored functions approach for php would make a future thread-based concurrency approach completely non-viable, because it would require marking ALL functions (not just IO-bound functions, CPU-bound ones as well) as async and forcing the use of await for ALL function calls, just to be able to sometimes use some functions in parallel (in separate threads).

Colored functions completely preclude a possible future thread-based implementation of concurrency.

Regards,
Daniil Gentili.

Colored functions completely preclude a possible future thread-based implementation of concurrency.

I can assure you that colored functions are neither part of this RFC nor any future ones from me. And it’s not because it’s my decision it’s rather the language itself and the existing codebase that dictate the implementation. Moreover, we already have extensive experience with Swoole, where developers’ reactions are well known. And Swoole is essentially PHP + coroutines.

I can recall the maintainer’s words from memory: it turned out that developers don’t want to use special functions instead of standard ones.

I can confirm this from my own experience. For example, adapting a library for RabbitMQ required changing only 10-20 lines, and overall, it worked quickly without tests.

The true strength of any language is not in its syntax. The main strength is its infrastructure.
Even if a language is poor in terms of development quality, if it has a massive infrastructure, people will use it.


Ed.

I will just put this picture here

···

Iliya Miroslavov Iliev
i.miroslavov@gmail.com

(Attachment just-image.jpg is missing)

On 8 March 2025 10:44:35 GMT, Daniil Gentili <daniil.gentili@gmail.com> wrote:

The only thing that's truly needed in this RFC is a set of synchronization primitives like in golang, and a way to parent/unparent fibers in order to inherit cancellations (as previously mentioned in this list), not contexts, async blocks and colored functions.

The async block as I'm picturing it has nothing to do with function colouring, it's about the outermost function in an async stack being able to say "make sure the scheduler is started" and "block here until all child fibers are either concluded, detached, or cancelled".

It's roughly equivalent to calling the RFC's Async\launchScheduler() more than once, but I imagine the later calls would not actually start a new scheduler, just track a group of fibers.

If we're building this into the language, we're not limited to expressing things with functions and objects, and a block syntax makes it trivial for the compiler to detect a mismatched start and end.

Regards,
Rowan Tommins
[IMSoP]

I don’t want to get involved, I’m giving just an opinion. If my wife wants me to get a present for someone and she wants me to get it wrapped up in either “green with orange ribbon” or “blue with red ribbon” (strictly) but I have to buy them separately from a different shops. If I buy orange ribbon from the shop that sells ribbons because it was open early and the other one was closed yet and the shop that sells the wrapping paper tells me they sell only blue, what should I do… kill myslef or go back and try to change it?

···

Iliya Miroslavov Iliev
i.miroslavov@gmail.com

The async block as I’m picturing it has nothing to do with function colouring, it’s about the outermost function in an async stack being able to say “make sure the scheduler is started” and “block here until all child fibers are either concluded, detached, or cancelled”.

There’s no need for such a construct, as the awaitAll function does precisely what you describe, without the need to introduce the concept of a child fiber and the excessive limitation of an async block that severely limits concurrency.

There is absolutely nothing wrong with the concept of a fiber without a parent, or a fiber that throws an exception (or a cancellation exception) out of the event loop.

A panic in a golang fiber surfaces out of the event loop (unless it is catched with a recover), just like an uncatched exception in a fiber surfaces out of the event loop: it makes no sense to severely limit concurrency with an async block just to handle the edge case of an uncaught exception (which can be handled anyway with an event loop exception handler).

In general, I really don’t like the concept of an async block the way it is presented here, because it implies that concurrency is something bad that must be limited and controlled, or else bad stuff will happen, when in reality, a fiber throwing an exception (without anyone await()ing on the fiber handle, thus throwing out of the event loop) is not the end of the world, and can be handled by other means, without limiting concurrency.

Regards,
Daniil Gentili.

As far as I can tell, the entire reason we are talking about this is because adding the event loop changes the behavior of existing code. So we cannot “just turn it on”.

I haven’t seen an explanation of why this is the case, but that’s how we got to this point. We need some way to “opt in” to turning on the event loop.

This also seems like a very bad idea: there is no reason for the language hide concurrency behind an INI or even worse a compilation flag.

Existing code may not all be free from races, but the choice should be up to the user, not the hoster or whoever provides the php distribution.

Enabling concurrency by default will allow gradual addition of fiber/threadsafety of codebases, as developers will know that concurrency is a (very easy to use) option, and will hopefully want to prepare their codebases for it, and after that happens, it will be even easier for users to use it.
(And actually, this is already the case, as fibers were added in 8.1, limiting the userland scheduler makes no sense now that (thankfully!!) the cat is out of the ba).

Regards,
Daniil Gentili.

One person observes 3 persons and he is curious what they are doing because it looks strange.
The first person digs a hole.
The second person buries the hole.
The third person waters the buried hole.
He asks them: Why are you doing this?
They say: There is a fourth person that plants the seeds but he is sick right now.

···

Iliya Miroslavov Iliev
i.miroslavov@gmail.com

As far as I can tell, the entire reason we are talking about this is because adding the event loop changes the behavior of existing code. So we cannot “just turn it on”.

I haven’t seen an explanation of why this is the case, but that’s how we got to this point. We need some way to “opt in” to turning on the event loop.

This also seems like a very bad idea: there is no reason for the language hide concurrency behind an INI or even worse a compilation flag.

Existing code may not all be free from races, but the choice should be up to the user, not the hoster or whoever provides the php distribution.

Enabling concurrency by default will allow gradual addition of fiber/threadsafety of codebases, as developers will know that concurrency is a (very easy to use) option, and will hopefully want to prepare their codebases for it, and after that happens, it will be even easier for users to use it.

In other words, no one’s forcing anyone to use async PHP: just because the language will provide a spawn/go keyword to spawn a new fiber, no one’s forcing you to use it, you can keep using everything in a single thread, single fiber, no spawning.

Crippling async PHP with async blocks just because some libraries aren’t ready for concurrency now, means crippling the future of async php.

Regards,
Daniil Gentili.

On Sat, Mar 8, 2025, at 8:00 AM, Daniil Gentili wrote:

As far as I can tell, the entire reason we are talking about this is because adding the event loop changes the behavior of existing code. So we cannot "just turn it on".

I haven't seen an explanation of why this is the case, but that's how we got to this point. We need some way to "opt in" to turning on the event loop.

This also seems like a very bad idea: there is no reason for the
language hide concurrency behind an INI or even worse a compilation
flag.

This is beyond a strawman to the point of being a straw-pile. Literally no one has suggested "hiding concurrency behind an ini flag or compilation flag." Please confine your comments to those that have some basis is reality and in this thread.

--Larry Garfield

This also seems like a very bad idea: there is no reason for the language hide concurrency behind an INI or even worse a compilation flag.

This is not because someone wants it that way. This situation is solely due to the fact that the Scheduler contradicts of Fiber.

  • The Scheduler expects to switch contexts as it sees fit.
  • Fiber expects context switching to occur only between the Fiber-parent and its child.

Of course, the switching mechanism can be modified, and the logic of the main context can also be changed. The problem is that, at a logical level, these two approaches are mutually exclusive.

For example, the Swow project introduced a separate coroutine library (libcat) and abandoned Fiber. But we cannot do the same.


Ed.

Crippling async PHP with async blocks just because some libraries aren’t ready for concurrency now, means crippling the future of async php.

How can calling a single function have such a destructive impact on the future of PHP?

Yes, you have to write 10-20 more characters than usual one time.
Yes, it’s a hack. Every programming language with history has hacks.
Hacks evoke negative emotions. But life does too :slight_smile:

All that’s needed is to change a few lines of code in index.php to initialize the application in an asynchronous context. It’s the same as launching Swoole: you need a few lines of code to initialize the web server.


Ed