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