[PHP-DEV] [VOTE] True Async RFC 1.6

ini settings are very frowned upon, but for the sake of a conversation, we can think of it like that. if I do async.enabled = 0, then nothing changes for me. Having this opt-in/opt-out control would mean that a broken website caused by spawn() only happens if they decide to enable async and they can start learning and developing their mental habit about how to work with async code.

Like I said in my previous email, having a feature flag like this
would be a reasonable compromise between adoption and backwards
compatibility. Shared state is definitely a problem but there's also
less obvious things that can go wrong.

For example: database transactions are done on a per connection basis.
Sharing a single database connection for purposes other than queries
that read things is dangerous. Imagine starting a transaction in one
coroutine, doing a couple of inserts and then switching do a different
coroutine before committing the transaction. As long as the
transaction has not been committed or rolled back, the second
coroutine will execute queries on the same connection!

As for the feature flag: I would prefer something that can be enabled
on a per-project basis. I'm guessing an ini-setting could work? You
can enable it in both the configuration and in code (index.php).

As an addition, a flag in composer.json that indicates async behavior
would be a nice to have. It could warn users when they are requiring a
library that does not support async.

Best Regards,
Bart Vanhoutte

Op vr 21 nov 2025 om 15:18 schreef Deleu <deleugyn@gmail.com>:

On Fri, Nov 21, 2025 at 10:45 AM Edmond Dantes <edmond.ht@gmail.com> wrote:

I have a mental habit: when I write asynchronous code, I always assume
that any object shared between coroutines can change at any moment.
Similarly, if you hand off memory ownership to another class or share
memory, you have to keep in mind that there is a chance some developer
might accidentally do something wrong.

The only difference between synchronous and asynchronous code is that
you don’t have a precise moment in time when the change happens. And
yes, that makes debugging harder. It makes finding bugs harder. But
it’s not something fundamentally new.

I don't know how else to describe it, but I decided to give a last attempt here since you mentioned your mental habit: having a mental habit about async code makes perfect sense. What I'm trying to say is that most PHP developers don't have that mental habit and that's not a problem because Async code in PHP doesn't come into your project out of nowhere. With `spawn()` being native, anytime you do "composer update" your project may start running async code without your consent and without warning you that you should go through a mental habit that you don't even know exist because you never had to worry about async code before in your life.

Having spawn() be an extremely easy way to start running async code is a great feature (not a bug). But the very first thing that seems to be missing is: I'm a dumb PHP developer that don't know and don't care about async php and when I upgrade to PHP X.Y, how do I keep my project always-sync without risking one of my composer packages suddenly calling spawn() and causing bugs I have no idea how to even begin to understand?

ini settings are very frowned upon, but for the sake of a conversation, we can think of it like that. if I do async.enabled = 0, then nothing changes for me. Having this opt-in/opt-out control would mean that a broken website caused by spawn() only happens if they decide to enable async and they can start learning and developing their mental habit about how to work with async code.

--
Marco Deleu

1 Like

As long as the
transaction has not been committed or rolled back, the second
coroutine will execute queries on the same connection!

*transaction, the second coroutine will execute queries on the same transaction.

Op vr 21 nov 2025 om 15:38 schreef Bart Vanhoutte <bart+php@croquemonsieur.be>:

ini settings are very frowned upon, but for the sake of a conversation, we can think of it like that. if I do async.enabled = 0, then nothing changes for me. Having this opt-in/opt-out control would mean that a broken website caused by spawn() only happens if they decide to enable async and they can start learning and developing their mental habit about how to work with async code.

Like I said in my previous email, having a feature flag like this
would be a reasonable compromise between adoption and backwards
compatibility. Shared state is definitely a problem but there’s also
less obvious things that can go wrong.

For example: database transactions are done on a per connection basis.
Sharing a single database connection for purposes other than queries
that read things is dangerous. Imagine starting a transaction in one
coroutine, doing a couple of inserts and then switching do a different
coroutine before committing the transaction. As long as the
transaction has not been committed or rolled back, the second
coroutine will execute queries on the same connection!

As for the feature flag: I would prefer something that can be enabled
on a per-project basis. I’m guessing an ini-setting could work? You
can enable it in both the configuration and in code (index.php).

As an addition, a flag in composer.json that indicates async behavior
would be a nice to have. It could warn users when they are requiring a
library that does not support async.

Best Regards,
Bart Vanhoutte

Op vr 21 nov 2025 om 15:18 schreef Deleu <deleugyn@gmail.com>:

On Fri, Nov 21, 2025 at 10:45 AM Edmond Dantes <edmond.ht@gmail.com> wrote:

I have a mental habit: when I write asynchronous code, I always assume
that any object shared between coroutines can change at any moment.
Similarly, if you hand off memory ownership to another class or share
memory, you have to keep in mind that there is a chance some developer
might accidentally do something wrong.

The only difference between synchronous and asynchronous code is that
you don’t have a precise moment in time when the change happens. And
yes, that makes debugging harder. It makes finding bugs harder. But
it’s not something fundamentally new.

I don’t know how else to describe it, but I decided to give a last attempt here since you mentioned your mental habit: having a mental habit about async code makes perfect sense. What I’m trying to say is that most PHP developers don’t have that mental habit and that’s not a problem because Async code in PHP doesn’t come into your project out of nowhere. With spawn() being native, anytime you do “composer update” your project may start running async code without your consent and without warning you that you should go through a mental habit that you don’t even know exist because you never had to worry about async code before in your life.

Having spawn() be an extremely easy way to start running async code is a great feature (not a bug). But the very first thing that seems to be missing is: I’m a dumb PHP developer that don’t know and don’t care about async php and when I upgrade to PHP X.Y, how do I keep my project always-sync without risking one of my composer packages suddenly calling spawn() and causing bugs I have no idea how to even begin to understand?

ini settings are very frowned upon, but for the sake of a conversation, we can think of it like that. if I do async.enabled = 0, then nothing changes for me. Having this opt-in/opt-out control would mean that a broken website caused by spawn() only happens if they decide to enable async and they can start learning and developing their mental habit about how to work with async code.

–
Marco Deleu

Hello

Without well-defined suspension points, literally all code is unpredictable. This is what people have been trying to tell you for the last several weeks. How can I know when the code will yield to the scheduler?
How do I reason about it? The foundation is there in the RFC and the code, but it needs more definition and guarantees.

What guarantee does suspend() give? You can still break the code with
it. I understand what you’re talking about, but I don’t see a big
difference between situations like these:

// ---------------------------
// 1. File get contents suspend inside PHP CORE
// ---------------------------

$data = ['value' => 1];
$content = file_get_contents('http://example.com');
$data['value'] = 2;

// ---------------------------
// 2. Asynchronous version special API
// ---------------------------

$data = ['value' => 1];
// Yes we know about this here.
$content = file_get_contents_async('http://example.com');
$data['value'] = 2; // write happens after resume, timing is different

I understand that you are trying to say that if `file_get_contents` is
blocking, then there is *slightly more chance* that the code won’t
break.
And if `file_get_contents_async` is explicitly non-blocking, then the
programmer will have to rewrite the code.

But which is cheaper:

1. Rewrite **all** the code?
2. Or rewrite **only the code that uses shared memory**?

And at the same time, using `file_get_contents_async` does **not**
save the programmer from mistakes.
I can already imagine a situation where a programmer, trying to adapt
the code for async, simply renames the functions hoping it will “just
work” :slight_smile:
No. It's not!

This approach:

* Increases the amount of refactoring
* Does not protect you from errors

The foundation is there in the RFC and the code, but it needs more definition and guarantees.

There is another approach.
You can use a special `async` attribute for functions that support it.
This would allow not only highlighting the code in the editor, but
also validating it with static analysis.

In transparent asynchrony there is a rule when writing code.
Only code that makes no function calls at all is considered guaranteed safe.
Any call is potentially treated as a hypothetical yield point. I use
the same mindset when writing highly reliable code.
I imagine that it can crash literally at any line. The principle here
is similar.

Asynchronous code in this sense is always divided into two major parts:

1. code that accesses shared memory
2. code that does **not** access shared memory

You only need to be careful in the first case. Everything else
requires no special attention.

This leads to other principles:

1. Shared state should only be passed when it is truly necessary.
2. If an object can be made immutable, it *should* be immutable.
3. If an object must be mutable, then it deserves double attention.

Above these principles there is one more, the most important:

* if you can avoid writing asynchronous code, you should avoid it

Global state is a core part of PHP, like it or not.

I love the global state. I’m not one of those people who shout that
some approach is “bad.”
Global state is great. The only question is how and when to use it.

So... summary:
Coroutines require:

1. A framework that can work correctly in an asynchronous environment.
2. A different mindset for framework and library developers.
3. More knowledge from regular developers — a bit of a minus… and a
plus at the same time.
4. Strict memory discipline for everyone. Hooray! This is the future
of programming. You can’t run away from it.

I’m confident that PHP can be adapted for more convenient memory
handling. There are possibilities for that.... at the syntax level and
at the helper-object level. This still needs discussion.

P.S.
If someone ever writes documentation, these would be excellent phrases for it :slight_smile:

On 21 November 2025 13:06:30 GMT, Deleu <deleugyn@gmail.com> wrote:

Here is a very simple hypothetical:
Suppose I run a website and I have very little understanding of PHP, but I
know the basics. Every Monday I run `composer update`, do a little testing
and deploy the website. Suppose next Monday when I run `composer update`
one of my dependencies (Package A) starts to pull in AMPHP as its nested
dependency. When I try to open my website locally it will either have
everything broken or it will just work regularly and nothing will be
running async. There is no other option.
Now suppose the same example but with PHP 9 + TrueAsync. I do `composer
update` one day and test my home page and it works. But deeply nested
somewhere there is a feature that will use Package A which then spawns a
coroutine and leads to my global state code behaving weirdly. It's not a
fatal error. It's not very clear at all to me that something changed, but
now my array indexes are triggering DB updates out of order.

This is basically the exact scenario I had in mind.

For me, having a cast-iron way to avoid this problem is a hard requirement for releasing a version of PHP with True Async included. Whether that's an ini setting, an annotation that protects particular parts of the code, etc, is detail we can work out later, but it must be a design requirement that protecting legacy code in some way is possible.

Debugging tools would be a useful addition, but wouldn't replace the off switch. I know for a fact that there are applications I will want to run on PHP 9 which will never be worth the investment to make async-ready. Other applications, I might well run in sync mode initially, then test and enable async.

And this again highlights why we need a project structure, where we can agree selected details of the design, with known outstanding issues. I want to be able to say "I approve of the design so far, with the understanding that X, Y and Z will be addressed before final release".

Regards,

Rowan Tommins
[IMSoP]

On Fri, Nov 21, 2025 at 3:22 PM Edmond Dantes <edmond.ht@gmail.com> wrote:

Hello

how do I keep my project always-sync without risking one of my composer packages suddenly calling spawn() and causing bugs I have no idea how to even begin to understand?

This idea was mentioned a bit earlier in the discussion. You can use a
special setting to disable asynchrony. There are many different
possibilities here. For example, you can tell Composer not to use
async packages. And so on. You can come up with your own protection
mechanisms. There is no technical problem here in terms of
implementation.
Why not enable asynchronous mode in PHP explicitly through php.ini?

I’m not sure if INI is going to be successful. I would bet that it is going to get rejected as PHP has been moving against introducing INI for language / extension behavior changes.

It reminds to some extend the scalar type hints with strict / non strict discussion that ended up using declare keyword. So maybe async could use it too and it could be enabled per file in a similar way as strict types. I haven’t thought if it would always make sense for async but for projects that want to be fully async, it would need to be declared in each file.

Cheers

Jakub

Hello, from a Brazil that is (as always) very, very, very hot.

I believe that using a php.ini flag would be the approach that makes the most sense, especially in an experimental stage. Since that isn’t possible, I think having a function inside the Async namespace is the best alternative, something like Async\enable(), without a corresponding Async\disable(), of course.

Sincerely,
LuĂ­s VinĂ­cius.

Em sex., 21 de nov. de 2025, 16:45, Jakub Zelenka <bukka@php.net> escreveu:

On Fri, Nov 21, 2025 at 3:22 PM Edmond Dantes <edmond.ht@gmail.com> wrote:

Hello

how do I keep my project always-sync without risking one of my composer packages suddenly calling spawn() and causing bugs I have no idea how to even begin to understand?

This idea was mentioned a bit earlier in the discussion. You can use a
special setting to disable asynchrony. There are many different
possibilities here. For example, you can tell Composer not to use
async packages. And so on. You can come up with your own protection
mechanisms. There is no technical problem here in terms of
implementation.
Why not enable asynchronous mode in PHP explicitly through php.ini?

I’m not sure if INI is going to be successful. I would bet that it is going to get rejected as PHP has been moving against introducing INI for language / extension behavior changes.

It reminds to some extend the scalar type hints with strict / non strict discussion that ended up using declare keyword. So maybe async could use it too and it could be enabled per file in a similar way as strict types. I haven’t thought if it would always make sense for async but for projects that want to be fully async, it would need to be declared in each file.

Cheers

Jakub

On 21 November 2025 19:42:40 GMT, Jakub Zelenka <bukka@php.net> wrote:

I'm not sure if INI is going to be successful. I would bet that it is going
to get rejected as PHP has been moving against introducing INI for language
/ extension behavior changes.

I think Edmond is right that exactly how the "off switch" should look is a ticket for later in the project. A ticket that blocks the final release, but doesn't have a deadline other than that.

The important thing for now is to have a general model of how code behaves when the switch in the "off" position, and whether or not sync and async code can run in the same thread, because that has an impact on how other parts of the design progress.

Rowan Tommins
[IMSoP]

Rowan Tommins [IMSoP] <imsop.php@rwec.co.uk> hat am 21.11.2025 21:58 CET geschrieben:

On 21 November 2025 19:42:40 GMT, Jakub Zelenka <bukka@php.net> wrote:
>I'm not sure if INI is going to be successful. I would bet that it is going
>to get rejected as PHP has been moving against introducing INI for language
>/ extension behavior changes.

I think Edmond is right that exactly how the "off switch" should look is a ticket for later in the project. A ticket that blocks the final release, but doesn't have a deadline other than that.

The important thing for now is to have a general model of how code behaves when the switch in the "off" position, and whether or not sync and async code can run in the same thread, because that has an impact on how other parts of the design progress.

Rowan Tommins
[IMSoP]

Hello,

from userland perspective I would prefer explicit declaration on each usage of async over declare(), hooks and INI. This would make reviews, static code analysis, etc. easier. RFCs can be written in small parts for each new function, e.g.

$content = \file_get_contents(); // sync
$promise = \file_get_contents_async(); // async
$promise = \Async\file_get_contents(); // async

$contentOrFalse = curl_exec($ch); // sync
$promise = \curl_exec_async($ch); // async
$promise = \Async\curl_exec($ch); // async

$pdoStatement = $pdo->prepare(); // sync
$pdoStatementAsync = $pdo->prepare_async(); // async
$promise = $pdoStatementAsync->execute()

Best Regards
Thomas

Hello

from userland perspective I would prefer explicit declaration on each usage of async over declare(), hooks and INI. This would make reviews, static code analysis, etc. easier.
RFCs can be written in small parts for each new function, e.g.

This solution has the following consequences.

We have a REST API project that is 10 years old. Recently we learned
that PHP 9 has been released, and we can improve performance. To do
this, we need to:
1. Migrate to another web server.
2. Wrap the request-handling code in a coroutine.

However, it turns out this doesn’t work. If the code inside the
coroutine doesn’t yield control, then there is no improvement at all.
And we’re like… this technology is useless.

And then one of the developers says: “Guys, we just need to rename all
functions to _async and it will work.”
(And he’s this young optimistic perfectionist programmer who says:
*“Then the code will be beautiful, amazing, and blaaaaaazing!”*)

Everyone happily writes a script to rename 30-50,000 lines of a
many-years-old project. New bugs appear. But we fix them, hoping for a
better future.
And then it turns out that after renaming the functions nothing works,
because shared memory has to be removed.

In other words, the team spent time on refactoring, and in the end
they still had to do the refactoring that was unavoidable.

What benefits did the developers gain?
1. Did the code become clearer? Yes, the code became a bit clearer.
It’s visible that the functions will yield control.
2. Did the number of bugs decrease?
3. Did the amount of code changes become smaller?
4. Did API segmentation appear?

People won’t rewrite code unless the business is at risk. And even
then, very often no one does anything. Therefore, the **more**
complicated the transition to asynchrony is, the **less likely** it is
to ever happen.
There is a chance that asynchrony could give the language a push
toward greater memory safety and make it more functional in style.
Because features in programming evolve according to the law of
accessibility. The more accessible a feature is, the more likely it is
to be used, and the more widespread it becomes. Go made parallel
programming more common because it made it simpler.

---
Ed

Hey Jakub,

¡¡¡

On 21.11.2025 12:29:16, Jakub Zelenka wrote:

Hi,

I think you seriously underestimate impact of this in the current PHP code bases where many applications depend on global state. Especially the legacy ones but even the most popular ones. Just look into WordPress which use global state extensively. Now imagine that some popular plugin decides to use async which change some of its globals (like $post or $wp_query) during the suspension of the main code. I would assume this could horribly break things. Don’t forget that other code don’t have control over the plugin and it might not even know that async is used there. So I’m not sure if this design is compatible with WordPress and similar applications where global state is used extensively. If that’s the case, it’s of course a problem because those applications (well WordPress on its own in fact) compose the majority of PHP users so introducing something that would have potential to break its code would limit usability of the whole feature and could even lead to loosing more users that we could gain from introducing this feature.

So I think it will need to find some solution that will prevent this from happening. I guess there might be few options

  1. Disallow suspension of the main sync code which is effectively some sort of colouring.
  2. Preventing access to globals from coroutine which I’m not sure is even fully doable from the engine PoV - it would mean some sort of different execution mode that could not use globals (e.g. global keyword and calling some functions that change global state). It would need channels for communications between coroutines. The advantage of such model would be possibility to combine it with threads in the future but I could imagine it could still lead to some subtle issue for years as there is internal global state as well that can lead to some surprises. But maybe that would be worth it.

I think you seriously misunderstand this.

Async does not allow you to introduce some effects from the outside. You have to actively opt-in to running stuff in parallel.

The problematic part, is, when you call code inside of coroutines, which is not safe to be run in parallel with other code.

I.e. if you start writing code like await([new Coroutine(fn() => not_safe_to_run_twice()), new Coroutine(fn() => not_safe_to_run_twice())]) (pseudo code). Where not_safe_to_run_twice() shares some context.

So, when you write async code, you have to know that the called code is allowed to run in parallel with any other code which you explicitly chose to run in parallel with it. E.g. if you’d run two wordpress functions which don’t have side-effects (not even to internal state), then you’re perfectly fine doing that. If you run wordpress functions in parallel which may jump back to the scheduler during its operation and leave the internal state in some intermittently invalid form, then yes, then you have a problem.

But if your goal is to just do a localized parallelized operation which you know is safe to run in parallel - e.g. some image processing and storing some stuff in the database, within your wordpress plugin, that’s absolutely fine. It won’t break anything.

Essentially: Just don’t call stuff in parallel which you don’t know is safe to be called in parallel and you’re golden.

Bob

Edmond Dantes <edmond.ht@gmail.com> hat am 22.11.2025 05:17 CET geschrieben:

Hello

> from userland perspective I would prefer explicit declaration on each usage of async over declare(), hooks and INI. This would make reviews, static code analysis, etc. easier.
> RFCs can be written in small parts for each new function, e.g.

This solution has the following consequences.

We have a REST API project that is 10 years old. Recently we learned
that PHP 9 has been released, and we can improve performance. To do
this, we need to:
1. Migrate to another web server.
2. Wrap the request-handling code in a coroutine.

However, it turns out this doesn’t work. If the code inside the
coroutine doesn’t yield control, then there is no improvement at all.
And we’re like… this technology is useless.

And then one of the developers says: “Guys, we just need to rename all
functions to _async and it will work.”
(And he’s this young optimistic perfectionist programmer who says:
*“Then the code will be beautiful, amazing, and blaaaaaazing!”*)

Everyone happily writes a script to rename 30-50,000 lines of a
many-years-old project. New bugs appear. But we fix them, hoping for a
better future.
And then it turns out that after renaming the functions nothing works,
because shared memory has to be removed.

In other words, the team spent time on refactoring, and in the end
they still had to do the refactoring that was unavoidable.

What benefits did the developers gain?
1. Did the code become clearer? Yes, the code became a bit clearer.
It’s visible that the functions will yield control.
2. Did the number of bugs decrease?
3. Did the amount of code changes become smaller?
4. Did API segmentation appear?

People won’t rewrite code unless the business is at risk. And even
then, very often no one does anything. Therefore, the **more**
complicated the transition to asynchrony is, the **less likely** it is
to ever happen.
There is a chance that asynchrony could give the language a push
toward greater memory safety and make it more functional in style.
Because features in programming evolve according to the law of
accessibility. The more accessible a feature is, the more likely it is
to be used, and the more widespread it becomes. Go made parallel
programming more common because it made it simpler.

---
Ed

Maybe misunderstanding, I'm not proposing coroutines, I'm proposing to add new functions for async io. e.g.

// currently we have
$content = \file_get_contents(); // old function remains: sync, returns string|false

// new function file_get_contents_async
$promise = \file_get_contents_async(); // new function: async, returns a promise object

(similar to node.js using fs.readFile and fs.readFileSync)

Regarding 10 year old projects: I would only add new functions and don't change existing functions for async io. So new async functions can be used, but don't need to be used.

I expect that's already a good improvement for many use cases to read multiple files in parallel, run multiple queries in parallel, etc.

Best Regards
Thomas

Hi Bob,

On Sat, Nov 22, 2025 at 5:50 AM Bob Weinand <bobwei9@hotmail.com> wrote:

Hey Jakub,

On 21.11.2025 12:29:16, Jakub Zelenka wrote:

Hi,

I think you seriously underestimate impact of this in the current PHP code bases where many applications depend on global state. Especially the legacy ones but even the most popular ones. Just look into WordPress which use global state extensively. Now imagine that some popular plugin decides to use async which change some of its globals (like $post or $wp_query) during the suspension of the main code. I would assume this could horribly break things. Don’t forget that other code don’t have control over the plugin and it might not even know that async is used there. So I’m not sure if this design is compatible with WordPress and similar applications where global state is used extensively. If that’s the case, it’s of course a problem because those applications (well WordPress on its own in fact) compose the majority of PHP users so introducing something that would have potential to break its code would limit usability of the whole feature and could even lead to loosing more users that we could gain from introducing this feature.

So I think it will need to find some solution that will prevent this from happening. I guess there might be few options

  1. Disallow suspension of the main sync code which is effectively some sort of colouring.
  2. Preventing access to globals from coroutine which I’m not sure is even fully doable from the engine PoV - it would mean some sort of different execution mode that could not use globals (e.g. global keyword and calling some functions that change global state). It would need channels for communications between coroutines. The advantage of such model would be possibility to combine it with threads in the future but I could imagine it could still lead to some subtle issue for years as there is internal global state as well that can lead to some surprises. But maybe that would be worth it.

I think you seriously misunderstand this.

Async does not allow you to introduce some effects from the outside. You have to actively opt-in to running stuff in parallel.

But you don’t have to opt in into it in this current form. If some external plugin calls spawn (without await), then it can suspend other plugins (that didn’t opt in to it) in places where it wasn’t expected.

The problematic part, is, when you call code inside of coroutines, which is not safe to be run in parallel with other code.

I.e. if you start writing code like await([new Coroutine(fn() => not_safe_to_run_twice()), new Coroutine(fn() => not_safe_to_run_twice())]) (pseudo code). Where not_safe_to_run_twice() shares some context.

Well if you call await, you should be safe but that’s not requirement here. You can just spawn coroutine and main functions continues the flow to other plugins that didn’t opt in to async.

So, when you write async code, you have to know that the called code is allowed to run in parallel with any other code which you explicitly chose to run in parallel with it. E.g. if you’d run two wordpress functions which don’t have side-effects (not even to internal state), then you’re perfectly fine doing that. If you run wordpress functions in parallel which may jump back to the scheduler during its operation and leave the internal state in some intermittently invalid form, then yes, then you have a problem.

I’m not saying that is not possible to write safe code. This would be perfectly possible and recommended, of course. The problem is that it doesn’t prohibit writing unsafe code in any way.

But if your goal is to just do a localized parallelized operation which you know is safe to run in parallel - e.g. some image processing and storing some stuff in the database, within your wordpress plugin, that’s absolutely fine. It won’t break anything.

Not sure if you worked with WordPress but if you want to query something, it is often done through $wp_query global so it might actually not be always safe if there is some IO done in between building the query.

Essentially: Just don’t call stuff in parallel which you don’t know is safe to be called in parallel and you’re golden.

That’s, of course, correct advise but I’m not sure you realise that if those rules are not somehow enforced, it will be most likely get ignored and lead to problems that many people are worried about. Also it might not be immediately obvious that something is safe because the change of state might happen indirectly and be deeply nested.

Kind regards,

Jakub

Hello

I expect that's already a good improvement for many use cases to read multiple files in parallel, run multiple queries in parallel, etc.

I see. You want to get several promises to wait for?
Then there is a good way to do it without additional functions:

$promise1 = spawn(file_get_content(...), "file1.txt");
$promise2 = spawn(file_get_content(...), "file2.txt");
$promise3 = spawn(file_get_content(...), "file3.txt");

This is equivalent because a coroutine is a Future. In the same way,
you can turn any other function into a Promise without creating a
separate API.

There is a nuance regarding resources. And in certain scenarios,
creating an array of Promises in another way can save memory. For
example, a scenario where you need to handle a large array of sockets.
Such cases require a special API.

---
Ed

Edmond Dantes <edmond.ht@gmail.com> hat am 22.11.2025 11:37 CET geschrieben:

Hello

> I expect that's already a good improvement for many use cases to read multiple files in parallel, run multiple queries in parallel, etc.

I see. You want to get several promises to wait for?
Then there is a good way to do it without additional functions:

$promise1 = spawn(file_get_content(...), "file1.txt");
$promise2 = spawn(file_get_content(...), "file2.txt");
$promise3 = spawn(file_get_content(...), "file3.txt");

This is equivalent because a coroutine is a Future. In the same way,
you can turn any other function into a Promise without creating a
separate API.

There is a nuance regarding resources. And in certain scenarios,
creating an array of Promises in another way can save memory. For
example, a scenario where you need to handle a large array of sockets.
Such cases require a special API.

---
Ed

Hello,

function return types should not depend on the outside context (spawn, hook, ini, etc.) because when the code gets more complex, it's very hard to find the outside context.

So file_get_contents() should always return string|false, file_get_contents_async() should always return a promise object.

From the example I would expect it like this:

$promise1 = file_get_content_async("file1.txt");
$promise2 = file_get_content_async("file2.txt");
$promise3 = file_get_content_async("file3.txt");

// do sth else

$content1 = $promise->await();
$content2 = $promise->await();
$content3 = $promise->await();

PHP got very successful by making things easier than in C, this should be the path to continue.

Best Regards
Thomas

Thomas Bley <mails@thomasbley.de> hat am 22.11.2025 12:42 CET geschrieben:

> Edmond Dantes <edmond.ht@gmail.com> hat am 22.11.2025 11:37 CET geschrieben:
>
>
> Hello
>
> > I expect that's already a good improvement for many use cases to read multiple files in parallel, run multiple queries in parallel, etc.
>
> I see. You want to get several promises to wait for?
> Then there is a good way to do it without additional functions:
>
> ```php
> $promise1 = spawn(file_get_content(...), "file1.txt");
> $promise2 = spawn(file_get_content(...), "file2.txt");
> $promise3 = spawn(file_get_content(...), "file3.txt");
> ```
>
> This is equivalent because a coroutine is a Future. In the same way,
> you can turn any other function into a Promise without creating a
> separate API.
>
> There is a nuance regarding resources. And in certain scenarios,
> creating an array of Promises in another way can save memory. For
> example, a scenario where you need to handle a large array of sockets.
> Such cases require a special API.
>
> ---
> Ed

Hello,

function return types should not depend on the outside context (spawn, hook, ini, etc.) because when the code gets more complex, it's very hard to find the outside context.

So file_get_contents() should always return string|false, file_get_contents_async() should always return a promise object.

From the example I would expect it like this:

$promise1 = file_get_content_async("file1.txt");
$promise2 = file_get_content_async("file2.txt");
$promise3 = file_get_content_async("file3.txt");

// do sth else

$content1 = $promise->await();
$content2 = $promise->await();
$content3 = $promise->await();

PHP got very successful by making things easier than in C, this should be the path to continue.

Best Regards
Thomas

I'm sorry, the correct example should be:

$promise1 = file_get_content_async("file1.txt");
$promise2 = file_get_content_async("file2.txt");
$promise3 = file_get_content_async("file3.txt");

// do sth else

$content1 = $promise1->await();
$content2 = $promise2->await();
$content3 = $promise3->await();

Best Regards
Thomas

Hello

function return types should not depend on the outside context (spawn, hook, ini, etc.) because when the code gets more complex, it's very hard to find the outside context.

What does “outside context” mean?

I just want to understand the practical use of functions with Promise.
The code above makes sense only if there is awaitAll.

$promise1 = file_get_content_async("file1.txt");
$promise2 = file_get_content_async("file2.txt");
$promise3 = file_get_content_async("file3.txt");

awaitAll($promise1, ....);

But you can achieve exactly the same effect without special functions.
The only difference is that the _async function inside might be
optimized in some way.
Or is there something else?

---
Ed

Edmond Dantes <edmond.ht@gmail.com> hat am 22.11.2025 13:01 CET geschrieben:

Hello

> function return types should not depend on the outside context (spawn, hook, ini, etc.) because when the code gets more complex, it's very hard to find the outside context.

What does “outside context” mean?

I just want to understand the practical use of functions with Promise.
The code above makes sense only if there is awaitAll.

$promise1 = file_get_content_async("file1.txt");
$promise2 = file_get_content_async("file2.txt");
$promise3 = file_get_content_async("file3.txt");

awaitAll($promise1, ....);

But you can achieve exactly the same effect without special functions.
The only difference is that the _async function inside might be
optimized in some way.
Or is there something else?

---
Ed

Hello,

basically in $result = foo(spawn(bar(baz(file_get_contents())))); file_get_contents() receives outside context from spawn() to turn into async mode. Also foo(), bar(), baz() can be in different namespaces, different classes, so by looking at the code calling file_get_contents(), it's not clear if the result is sync or async.

Another example:

$foo = file_get_contents('foo.txt');
$result = foo($foo);

should be equal to:

$result = foo(file_get_contents('foo.txt'));

but having:

$foo = file_get_contents('foo.txt'); // sync
$result = spawn($foo); // error because $foo is string

would not be eqaul to:

$result = spawn(file_get_contents('foo.txt')); // async

Best Regards
Thomas

basically in $result = foo(spawn(bar(baz(file_get_contents())))); file_get_contents() receives outside context from spawn() to turn into async mode.
Also foo(), bar(), baz() can be in different namespaces, different classes, so by looking at the code calling file_get_contents(), it's not clear if the result is sync or async.

Ok, then let’s look in detail at what is happening.

foo(spawn(bar(baz(file_get_contents()))))

i.e.

$result = foo(spawn(bar(...), fn () => baz(file_get_contents())));

Did I understand the code correctly?
(Assume there was also some parameter there, like a file name.)

1. We call `bar` in a separate coroutine, which
2. First calls `file_get_contents`
3. Then passes the result to the function `baz()`

Is that correct?

Also foo(), bar(), baz() can be in different namespaces, different classes, so by looking at the code calling file_get_contents(), it's not clear if the result is sync or async.

If we are discussing the code above, then it returns a Promise not of
the file-reading result.
This is a completely different logic, and here the programmer clearly
intended to do something else.
What if the function baz replaces every a character with baz? It's ok.

$foo = file_get_contents('foo.txt'); // sync
$result = spawn($foo); // error because $foo is string

Here I don’t understand why someone would intentionally write incorrect code.

Code:

$result = foo(file_get_contents('foo.txt'));

// equivalent to
// the code below has no practical purpose

$result = foo(await(spawn(file_get_contents(...), 'foo.txt')));

Is that correct?

Edmond Dantes <edmond.ht@gmail.com> hat am 22.11.2025 13:37 CET geschrieben:

> basically in $result = foo(spawn(bar(baz(file_get_contents())))); file_get_contents() receives outside context from spawn() to turn into async mode.
> Also foo(), bar(), baz() can be in different namespaces, different classes, so by looking at the code calling file_get_contents(), it's not clear if the result is sync or async.

Ok, then let’s look in detail at what is happening.

> foo(spawn(bar(baz(file_get_contents()))))

i.e.

$result = foo(spawn(bar(...), fn () => baz(file_get_contents())));

Did I understand the code correctly?
(Assume there was also some parameter there, like a file name.)

1. We call `bar` in a separate coroutine, which
2. First calls `file_get_contents`
3. Then passes the result to the function `baz()`

Is that correct?

> Also foo(), bar(), baz() can be in different namespaces, different classes, so by looking at the code calling file_get_contents(), it's not clear if the result is sync or async.
If we are discussing the code above, then it returns a Promise not of
the file-reading result.
This is a completely different logic, and here the programmer clearly
intended to do something else.
What if the function baz replaces every a character with baz? It's ok.

$foo = file_get_contents('foo.txt'); // sync
$result = spawn($foo); // error because $foo is string

Here I don’t understand why someone would intentionally write incorrect code.

Code:

$result = foo(file_get_contents('foo.txt'));

// equivalent to
// the code below has no practical purpose

$result = foo(await(spawn(file_get_contents(...), 'foo.txt')));

Is that correct?

So I guess you want to use spawn() in a similar way as call_user_func() works.
This changes the behavior of file_get_contents() from the outside, so it gives file_get_contents() async context from the outside to behave differently.
Assuming every io operation inside spawn() runs async, I guess it's not possible to mix sync and async io inside of one function.

As mentioned before I'm not suggesting to use coroutines for async io. My recommendation would be implementing new functions for async io and a promise object. Having everything that's executed async explicitly named "_async" helps a lot when reading and understanding userland code.

Regarding Coroutines in Go from userland perspective: the idea is nice but often fails with deadlocks, hanging processes and having to run a "Data Race Detector" all the time. Sure it's always the developers fault, but it happens too often.

Best Regards
Thomas

So I guess you want to use spawn() in a similar way as call_user_func() works.

yes

This changes the behavior of file_get_contents() from the outside

No.

function file_get_contents(string $filename): string
{
    $fh = fopen();

    // It creates an EPOLL event so it can wake us when the data
becomes available.
     $event = ReactorAPI.create_event_from($fh);
    $waker = Scheduler.getCurrentWaker();
    // Event Driven logic inside.
    $waker.add_event($event, function() use($waker) {
          // Wakeup this coroutine
          $waker.wake();
     });

    // suspend current coroutine
    // zz..... z.....
    Scheduler.suspend();

    // Continue here after the IO event

    // Now we have date, return
    return fread($fh, ....);
}

This is pseudocode. You can assume it always works.
If you call `file_get_contents` directly, it behaves the same way.
So it does not matter where `file_get_contents` is called.
Since all PHP code together with TrueAsync runs inside coroutines,
`file_get_contents` will suspend the coroutine in which it was invoked.

When you call `spawn`, you simply run the function in another
coroutine, not in your own. But `spawn` has no effect on
`file_get_contents`.

We’re not at risk of DataRace yet :slight_smile: We don’t have multithreading.
And most likely it won’t appear anytime soon.