On Sat, Mar 1, 2025, at 3:11 AM, Edmond Dantes wrote: > Good day, everyone. I hope you're doing well. > > I’d like to introduce a draft version of the RFC for the True Async component. > > https://wiki.php.net/rfc/true_async > > I believe this version is not perfect and requires analysis. And I > strongly believe that things like this shouldn't be developed in > isolation. So, if you think any important (or even minor) aspects have > been overlooked, please bring them to attention. > > The draft status also highlights the fact that it includes doubts about > the implementation and criticism. The main global issue I see is the > lack of "future experience" regarding how this API will be used—another > reason to bring it up for public discussion. > > Wishing you all a great day, and thank you for your feedback!
I finally managed to read through enough of the RFC to say something intelligent. :-) First off, as others have said, thank you for a thorough and detailed proposal. It's clear you've thought through a lot of details. I also especially like that it's transparent for most IO operations, which is mandatory for adoption. It's clear to me that async in PHP will never be more than niche until there is a built-in dev-facing API that is easy to use on its own without any 3rd party libraries. Unfortunately, at this point I cannot support this proposal, because I disagree with the fundamental design primitives. Let's look at the core design primitives: * A series of free-standing functions. * That only work if the scheduler is active. * The scheduler being active is a run-once global flag. * So code that uses those functions is only useful based on a global state not present in that function. * And a host of other seemingly low-level objects that have a myriad of methods on them that do, um, stuff. * Oh, and a lot of static methods, too, instead of free-standing functions. The number of ways for this to go wrong and confuse the heck out of a developer is disturbingly high. In the Low-Level API section, the RFC notes: > I came to the conclusion that, in the long run, sacrificing flexibility in > favor of code safety is a reasonable trade-off. I completely agree with this statement! And feel the RFC doesn't go even remotely far enough in that direction. In particular, I commend to your attention this post about a Python async library that very deliberately works at a much higher level of abstraction, and is therefore vastly safer: https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/ I won't repeat the post, but suffice to say I agree with it almost entirely. (I dislike the name "nursery," but otherwise...) That is the direction we should be looking at for PHP, from the get-go. PHP doesn't have Python-style context managers (though I would like them), so a PHP version of that might look something like this (just spitballing): async $context { // $context is an object of AsyncContext, and can be passed around as such. // It is the *only* way to span anything async, or interact with the async controls. // If a function doesn't take an AsyncContext param, it cannot control async. This is good. $context->run(some_function(...)); $result = $context->run(function(AsyncContext $ctx) use ($someObj) { // This queues a thunk to run at the end of the closest async {} block. $ctx->defer($someObj->shutdown(...)); }); } catch (SomeException $e) { // Exception thrown by one of the fibers. } // This is an unwrapped value. print $result; Naturally there would be more to the API, but I'm just showing the basics. Importantly: * There is no global modal (schedulerStarted) to think about. * When the async {} block ends, you know with 100% certainty that there are no dangling background tasks. * It's explicitly obvious what functions are going to try and mess with the async context, and therefore cannot be called except within an async context. * An application can have sync portions and async portions very easily, without worrying about which "mode" it's in at a given time. It also means that writing a number of the utilities mentioned in the RFC do not require any engine code. Eg: function parallel_map(iterable $it, Closure $fn) { $result = []; async $ctx { foreach ($it as $k => $v) { $result[$k] = $ctx->run($fn($v)); } } return $result; } Now I know that's safe to call anywhere, whether I'm current in an active async mode or not. I'm not convinced that sticking arbitrary key/value pairs into the Context object is wise; that's global state by another name. But if we must, the above would handle all the inheritance and override stuff quite naturally. Possibly with: async $ctx from $parentCtx { // ... } Similarly, the two different modes for channels strike me as quite unnecessary. I also would tend to favor how Rust does channels (via a library, I don't think it's a built-in): have separate variables for the in-side and out-side. Again, just spitballing: [$in, $out] = Channel::create($buffer_size); $in->send($val); $out->receive($val); (Give or take variations of those methods.) Now you don't need to worry about fibers owning things. You just have a ChannelIn object and a ChannelOut object, and can pass either one to as many or as few functions as you want. And those functions could be spawning new fibers if you'd like, or not. (There's likely some complications here I'm not thinking of, but I've not dug into it in depth yet.) You can now close either side, or just let the objects go out of scope. In short, I am fully in favor of better async logic in PHP. I am very against an API that even allows me to do something stupid or deadlock-creating, or that relies on hidden global state. That would be worse than the status quo, and there are better models than what is shown here that offer much stronger "correct by construction" guarantees. --Larry Garfield