Hi Edmond!

On Thu, Oct 30, 2025 at 5:22 AM Edmond Dantes <[email protected]> wrote:

> Hi
>
> 1.5 RFC:
> https://wiki.php.net/rfc/true_async
>
> Here’s the fifth version of the RFC with the updates made after the
> 1.4 discussion.
>
> Starting from 2025-11-03, there will be a two-week discussion period.
>
> **Changelog:**
>
> * Added FutureLike interface methods: cancel(), isCompleted(),
> isCancelled()
> * Renamed Coroutine::isFinished() to Coroutine::isCompleted()
> * Clarified exit/die behavior: always triggers Graceful Shutdown mode
> regardless of where called
> * Added rationale for “Cancellable by design” policy: explains why
> default cancellability reduces code complexity for read-heavy PHP
> workloads
> * RFC structure improvements: reorganized Cancellation section with
> proper subsections hierarchy
> * Moved “Coroutine lifetime” as subsection under Coroutine section
> * Extended glossary with Awaitable, Suspension, Graceful Shutdown, and
> Deadlock terms
> * Introduced FutureLike interface with single-assignment semantics and
> changed await() signature to accept FutureLike instead of Awaitable
> for type safety
> * Split RFC: Moved Scope and structured concurrency functionality to
> separate Scope RFC. Base RFC now focuses on core async primitives
> (coroutines, await, cancellation)
>
> I decided not to wait until Monday and made the changes today. If
> anyone has read version 1.4 and has comments on it, they’re still
> relevant.
>
> The Scope API has been moved to a separate RFC:
> https://wiki.php.net/rfc/true_async_scope
>
> ----
> Best Regards, Ed
>

Thanks for the RFC update! I've been trying to read and understand this RFC
since its earlier versions and I can definitely feel it getting easier to
digest, which I can only assume it's a good thing for RFC voters - it's
easier to vote No because something is too complex / too hard to understand.

One minor question: is this section
https://wiki.php.net/rfc/true_async#awaiting_a_result_with_cancellation
named wrongly? I'm not sure how this snippet of code relates to
cancellation.

Onto more important things. In regards to the change of Awaitable vs
FutureLike, my understanding of the discussion is that on the
implementation side it was asked about whether an Awaitable object should
be awaited in a loop (consumed until completion) or if it should be awaited
only once, which also raised the question about idempotency and whether an
object is awaitable more than once. While the change makes the type-system
somewhat more explicit in regards to how await() is meant for a single-shot
Awaitable object (named FutureLike), it does mean the implementation of
things like awaitAll(Awaitable[] $awaitables) is no longer a simple loop to
await every item in the array.

My questions are:
- What's the difference between Multishot Awaitables and Generators?
- If await() is hardened to FutureLike only, doesn't this mean that
multishot awaitables are not really capable of being awaited anymore?
Doesn't this mean that the Awaitable interface becomes out-of-sync with the
await() function and it stops making sense?
- Shouldn't FutureLike be Awaitable and what has been described as
Multishot awaitables should actually be generators / array of / list of
Awaitables?

In regards to Cancellable by Design. In the current state of PHP, we can
assume that if a function used to throw an Exception and a future version
stops throwing said exception it is not considered a BC Break. Of course,
throwing a *different exception* not part of the hierarchy is a BC break.
But when an exception signals "I cannot handle this" and a future version
becomes capable of handling it, that in essence is a feature/enhancement
and not treated as a BC break. With "Cancellation by Design" we are
expected that every coroutine be cancellable and that we must write try /
catch to design for cancellation. This seems to open up a different
development flow where catch blocks can be fundamentally part of the BC
promise. One possible alternative would be e.g. await(Awaitable $awaitable,
Closure $cancellation) where the cancellation of a coroutine would trigger
the specified Closure. Now, I don't want to dive too much into the
trade-offs of these options, what I want is to spark the idea that there
may be multiple ways to design cancellable coroutines. When I consider
that, plus the added fact that the RFC is very dense, extensive and hard to
digest, wouldn't it be best to postpone coroutine cancellations altogether?
The RFC itself states:

>  [...] read operations (database queries, API calls, file reads) are
typically as frequent as—or even more frequent than—write operations. Since
read operations generally don't modify state, they're inherently safe to
cancel without risking data corruption.

which not only I agree with, but also want us to focus on this very fact.
What if the first version of Async PHP provides userland with the ability
to trigger coroutines for reading purposes only? We will not forbid/prevent
anybody from shooting themselves if they want to, but we can still clearly
state the design principle that Async PHP is meant to spawn coroutines
cancellable by design. There will not be any coroutine markers in the
future nor there will be assumptions that coroutines written for the 1st
version of Async PHP must not be cancellable. The assumption is that the
first version of Async PHP should be treated as a way to perform read
operations only and a future RFC / enhancement will bring cancellation
capabilities (be it Closure, Try / Catch, what have you). My reasoning is
that this would further reduce the scope of the RFC while still introducing
real life useful async components to PHP, even if at a limited capacity. It
gives voters less concepts to understand, digest, agree on and approve and
it extends the opportunity to focus on specific deep aspects of Async PHP
in chunks and throughout different stages.

-- 
Marco Deleu

Reply via email to