Re: PHP True Async RFC - Stage 2

From: Date: Wed, 19 Mar 2025 00:28:32 +0000
Subject: Re: PHP True Async RFC - Stage 2
References: 1  Groups: php.internals 
Request: Send a blank email to [email protected] to get a copy of this message


On Sun, Mar 16, 2025, at 10:24, Edmond Dantes wrote:
> Good day, everyone. I hope you're doing well.
> 
> https://wiki.php.net/rfc/true_async
> 
> Here is a new version of the RFC dedicated to asynchrony.
> 
> Key differences from the previous version:
> 
> * The RFC is not based on Fiber; it introduces a separate class representation for the
> asynchronous context.
> * All low-level elements, including the Scheduler and Reactor, have been removed from the RFC.
> * The RFC does not include Future, Channel, or any other primitives, except those directly
> related to the implementation of structured concurrency.
> 
> The new RFC proposes more significant changes than the previous one; however, all of them are
> feasible for implementation.
> 
> I have also added PHP code examples to illustrate how it could look within the API of this RFC.
> 
> I would like to make a few comments right away. In the end, the Kotlin model lost, and the RFC
> includes an analysis of why this happened. The model that won is based on the Actor approach,
> although, in reality, there are no Actors, nor is there an assumption of implementing encapsulated
> processes.
> 
> On an emotional level, the chosen model prevailed because it forces developers to constantly
> think about how long coroutines will run and what they should be synchronized with. This somewhat
> reminded me of Rust’s approach to lifetime management.
> 
> Another advantage I liked is that there is no need for complex syntax like in Kotlin, nor do we
> have to create separate entities like Supervisors and so on. Everything is achieved through a simple
> API that is quite intuitive.
> 
> Of course, there are also downsides — how could there not be? But considering that PHP is a
> language for web server applications, these trade-offs are acceptable.
> 
> I would like to once again thank everyone who participated in the previous discussion. It was
> great!  

Hey Edmond,

Here are my notes:

> The *Scheduler* and *Reactor* components should be described in a separate *RFC*, which should
> focus on the low-level implementation in *C* and define *API* contracts for PHP extensions.

Generally, RFCs are for changes in the language itself, not for API contracts in C. That can
generally be handled in PRs, if I understand correctly.

> The suspend function has no parameters and does not return any values, unlike the
> yield operator.

If it can throw, then it does return values? I can foresee people abusing this for flow control and
passing out (serialized) values of suspended coroutines. Especially if it is broadcast to all other
coroutines awaiting it. It is probably simpler to simply allow passing a value out via suspend.

> The suspend function can be used in any function and in any place including from
> the main execution flow:

Does this mean it is an expression? So you can basically do:

return suspend();

$x = [suspend(), suspend(), suspend()];

foreach ($x as $_) {}

or other weird shenanigans? I think it would be better as a statement.

> The await function/operator is used to wait for the completion of another
> coroutine:

What happens if it throws? Why does it return NULL; why not void or the result of the
awaited spawn?

> The register_shutdown_function handler operates in synchronous mode, after
> asynchronous handlers have already been destroyed. Therefore, the
> register_shutdown_function code should not use the concurrency API. The
> suspend() function will have no effect, and the spawn operation will not
> be executed at all.

Wouldn't it be better to throw an exception instead of silently failing?

From this section, I really don't like the dual-syntax of spawn, it is
function-like, except not. In other words, this won't behave like you would expect it to:

spawn ($is_callable ? $callable : $default_callable)($value);

I'm not sure what will actually happen here.

---

When comparing the three different models, it would be ideal to keep to the same example for all
three and describe how their execution differs between the example. Having to parse through the
examples of each description is a pain.

> Child coroutines inherit the parent's Scope:

Hmm. Do you mean this literally? So if I call a random function via spawn, it will have access to my
current scope?

function foo() {
  $x = 'bar';
}

$x = 'baz';
$scope->spawn(foo(...));

echo $x; // baz or bar??

That seems like a massive footgun. I think you mean to say that it would behave like normal. If you
spawn a function, it behaves like a function, if you spawn a closure, it closes over variables just
like normal. Though I think it is worth defining "when" it closes over the variables --
when it executes the closure, or when it hits the spawn.

Does "spawn" not provide a \Scope?

---

I still don't understand the need for a special context thing. One of the most subtle footguns
with go contexts is to propagate the context when it shouldn't be propagated. For example, if
you are sending a value to a queue, you probably don't want to send the request context. If you
did and the request was cancelled (or even just completed!), it would also cancel putting the value
on the queue -- which is almost certainly what you do *not* want. Since the context is handled for
you, you also have to worry about a context disappearing while you are using it, from the looks of
things.

This can be easily built in userland, so I'm not sure why we are defining it as part of the
language.

> To ensure data encapsulation between different components, *Coroutine Scope Slots* provide the
> ability to associate data using *key objects*. An object instance is unique across the entire
> application, so code that does not have access to the object cannot read the data associated with
> it.

heh, reminds me of records.

---

I know I have been critical in this email, but I actually like it; for the most part. I think there
are still some rough edges to sand down and polish, but it is on the right track!

— Rob


Thread (59 messages)

« previous php.internals (#126833) next »