Hello everyone, It's a nice Sunday evening, and I'd like to share some updates and thoughts from this week — kind of like a digest :)
1. Big thanks to Rowan Tommins for the syntax suggestions, ideas, and feedback. I decided to try using the `spawn block` syntax, and in practice, it turned out to be quite convenient. So I'll include it in the next phase. You can check out the details via the link: https://github.com/EdmondDantes/php-true-async-rfc/blob/main/basic.md#spawn-closure-syntax ```php function startServer(): void { async $serverSupervisor { // Secondary coroutine that listens for a shutdown signal spawn use($serverSupervisor) { await Async\signal(SIGINT); $serverSupervisor->cancel(new CancellationException("Server shutdown")); } // Main coroutine that listens for incoming connections await spawn { while ($socket = stream_socket_accept($serverSocket, 0)) { connectionHandler($socket); } }; } } ``` 2. suspend has become a statement. 3. The scope retrieval functions like currentScope, globalScope, and rootScope have been removed. This has consequences. One of them: it's no longer possible to create a "detached" coroutine. But that’s good news. 4. I decided to borrow Larry's example and create a special code block "*async block"* that interacts with coroutine Scope in a special way. ```php function startServer(): void { async $serverSupervisor { // Secondary coroutine that listens for a shutdown signal spawn use($serverSupervisor) { await Async\signal(SIGINT); $serverSupervisor->cancel(new CancellationException("Server shutdown")); } // Main coroutine that listens for incoming connections await spawn { while ($socket = stream_socket_accept($serverSocket, 0)) { connectionHandler($socket); } }; } } ``` It looks nice, even though it's syntactic sugar for: ```php $scope = new Scope(); try { await spawn in $scope { echo "Task 1\n"; }; } finally { $scope->dispose(); } ``` This syntax creates a code block that limits the lifetime of coroutines until the block is exited. It doesn't *wait*, it *limits*. Besides the fact that the block looks compact, it can be checked by static analysis for the presence of `await`, verify what exactly is being awaited, and report potential errors. In other words, such code is much easier to analyze and to establish relationships between groups of coroutines. The downside is that it's not suitable for classes with destructors. But that's not really a drawback, since there's a different approach for handling classes. 5. I decided to abandon `await all + scope`. Reason: it's too tempting to shoot yourself in the foot. Instead of `await $scope`, I want the programmer to explicitly choose what exactly they intend to wait for: only direct children or all others. If you're going to shoot yourself in the foot — do it with full awareness :) Drawback: it complicates the logic. But on the other hand, this approach makes the code better. The code only awaits the coroutines that were created inside the foreach: ```php function processAllUsers(string ...$users): array { $scope = new Scope(); foreach ($users as $user) { spawn in $scope processUser($user); } return await $scope->tasks(); } ``` Code that waits until all child coroutines — at any depth — of the launched background tasks have completed. ```php function processBackgroundJobs(string ...$jobs): array { $scope = new Scope(); foreach ($jobs as $job) { spawn with $scope processJob($users); } await $scope->all(); } ``` It doesn’t look terrible, but I’m concerned that this kind of functionality might feel “complex” from a learning curve perspective. On the other hand, Python’s approach to similar things is even more complex, largely because the language added async features in several stages. My main doubts revolve around the fact that Scope is passed implicitly between function calls. This creates multiple usage scenarios — i.e., a kind of flexibility that no other language really has. And as we know, flexibility has a dark side: it opens up ways to break everything. On one hand, this RFC effectively allows writing in the style of Go, Kotlin, C#, or even some other paradigms. On the other hand, identifying the “dark side of the force” becomes harder. If you don’t use Scope — you’re writing Go-style code. If you use Scope + all + async — it’s Kotlin. If you use Scope + tasks() — it’s more like Elixir. And you can also just pass $scope explicitly from function to function — then you get super-explicit structured concurrency. So I keep asking myself: wouldn’t it have been simpler to just implement the Go model? :) How can you know in advance that the chosen solution won’t lead to twisted practices or eventually result in anti-patterns? How can you tell if the chosen toolkit is strict enough — and not *too* flexible? These questions are the main reason why the next revision won’t be released very soon. More code and examples are needed to understand how reliable this really is. But in the meantime, you can keep an eye on this: https://github.com/EdmondDantes/php-true-async-rfc/blob/main/basic.md