On 23/11/2025 19:09, Edmond Dantes wrote:
@Ed Unless something calls `spawn` all I/O is going to be blocking &
non-concurrent, correct?
Yes.
If no one calls spawn, this is equivalent to the code running inside a
single coroutine.
At the moment, TrueAsync has an internal flag that allows it to be
enabled or disabled. If Async is disabled, an exception will be
thrown.
I think there's a lot of confusion in this thread because different
people are talking about different scenarios. Perhaps it would be useful
to introduce some User Stories...
Async Alice is working on a brand new application written in PHP 9, and
is designing it from the ground up to make use of async capabilities
wherever possible. She wants third-party libraries to use async I/O so
that she can use them in her design.
Beginner Bob has a recently built application, and thinks there's an
opportunity to improve it with async I/O, but doesn't know anything
about it. He wants a simple-to-use API that lets him get the benefits,
and clear instructions on what pitfalls to look out for.
Legacy Les is maintaining a 20-year-old business back office system,
which makes extensive use of global state and does not have good
automated testing. He wants to run it under PHP 9, and to use up-to-date
third-party libraries for new functionality, without an expensive and
risky rewrite of existing code.
Finally, SDK Susie is publishing the official PHP library for a popular
cloud API. She wants to serve the best version she can for Alice, Bob,
and Les, but doesn't want to maintain separate "sync" and "async"
branches of the library or its methods.
Feel free to create more personas if you want to talk about additional
scenarios.
The first thing I want to clarify is that SDK Susie doesn't necessarily
need to change the public methods of her library; she can still use
async I/O internally. For instance, if a method already returns an
Iterator to silently fetch a page of results at a time, that can be
changed to store Promises internally, and await them when the data is
needed.
However, she might want to mark it as a breaking change anyway, so
that Async Alice and Beginner Bob know they are opting into it.
Legacy Les won't get it until he opts in, but at some point he will need
a new version of the library for other reasons (e.g. because the cloud
API becomes incompatible with the old library version); so he still
needs a way to run it safely.
If he runs PHP in a mode where any attempt to use async I/O *throws an
error*, he still can't use the new version of the library, so this
doesn't help him.
However, if Legacy Les can run PHP in a mode where any attempt to use
async I/O is *automatically run synchronously*, then he will be happy:
he can run his legacy application under PHP 9, and use the updated
library, without worrying about async code.
Beginner Bob doesn't want to run his whole application in "sync only"
mode, but might want to switch *parts of it*, so that he doesn't have to
think about them yet. So a scoped, rather than global, switch might be
useful for him.
This is how I picture that mode working: when SDK Susie's library code
calls "spawn", a Coroutine is created as normal. However, when it
suspends, the Scheduler immediately resumes it, rather than switching to
a different Coroutine. The library code will see the Coroutine object it
expects, but passing it to "await" will immediately produce its result.
However, I might well be misunderstanding something, and this is either
impossible or difficult to implement. If so, I think some other solution
to Legacy Les's requirements is needed.
I hope this description is useful.
--
Rowan Tommins
[IMSoP]