> 
> P.S. + 1 example:
> 
> <?php
> 
> declare(strict_types=1);
> 
> use Async\Scope;
> use function Async\currentScope;
> 
> function fetchUrl(string $url): string {
>     $ctx = stream_context_create(['http' => ['timeout' => 5]]);
>     return file_get_contents($url, false, $ctx);
> }
> 
> function fetchAllUrls(array $urls): array
> {
>     $futures = [];
>     
>     foreach ($urls as $url) {
>         $futures[$url] = (spawn fetchUrl($url))->getFuture();
>     }
>     
>     await currentScope();
>     
>     $results = [];
>     
>     foreach ($futures as $url => $future) {
>         $results[$url] = $future->getResult();
>     }
>     
>     return $results;
> }
> 
> $urls = [
>     'https://example.com <https://example.com/>',
>     'https://php.net <https://php.net/>',
>     'https://openai.com <https://openai.com/>'
> ];
> 
> $results = await spawn fetchAllUrls($urls);
> print_r($results);
> 

I still strongly believe the RFC should not include the footgun that is the 
await on the current scope, and this example you sent shows exactly why: you 
gather an array of futures, and instead of awaiting the array, you await the 
scope (potentially causing a deadlock if client libraries do not use a 
self-managed scope manually, using whatever extra syntax is required to do 
that), and then manually extract the values for some reason.

This is still using the kotlin approach equivalent to its runBlocking function, 
with all the footguns that come with it.

I would like to invite you to google “runBlocking deadlock” on google, and see 
the vast amount of results, blogposts and questions regarding its dangers: 
https://letmegooglethat.com/?q=kotlin+runblocking+deadlock

A few examples:
- https://discuss.kotlinlang.org/t/coroutines-and-deadlocks/18060/2 - "You 
launched a runBlocking inside the default dispatcher for each core… You are 
abusing the coroutine machinery! Do not use runBlocking from an asynchronous 
context, it is meant to enter an asynchronous context!” (a newbie abused an 
async {}/ await currentScope())
- 
https://medium.com/better-programming/how-i-fell-in-kotlins-runblocking-deadlock-trap-and-how-you-can-avoid-it-db9e7c4909f1
 - How I Fell in Kotlin’s RunBlocking Deadlock Trap, and How You Can Avoid It 
(async {}/await currentScope() blocks on internal kotlin runtime fibers, 
causing a deadlock in some conditions)

Even the official kotlin documentation 
(https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html)
 says "Calling runBlocking from a suspend function is redundant. For example, 
the following code is incorrect:”

suspend fun loadConfiguration() {
    // DO NOT DO THIS:
    val data = runBlocking { // <- redundant and blocks the thread, do not do 
that
        fetchConfigurationData() // suspending function
    }
}

Which is fully equivalent to the following PHP (note that I use the “async {}” 
nursery still used by some people in the discussion, but fully equivalent logic 
could be written using await currentScope):

function loadConfiguration() {
    // DO NOT DO THIS:
    async { // <- redundant and blocks the thread, do not do that
        $data = fetchConfigurationData(); // suspending function
    }
}


With current rfc syntax:

function loadConfiguration() {
    // DO NOT DO THIS:
    $data = fetchConfigurationData(); // suspending function
    await currentScope(); // <- redundant and blocks the thread, do not do that
}


When even the official language documentation is telling you in ALL CAPS to not 
use something, you automatically know it’s a major footgun which has already 
been abused by newbies.

As I reiterated before, making a scope awaitable is a footgun waiting to 
happen, and while at least now there’s an escape hatch in the form of custom 
scopes, forcing libraries to use them is a very bad idea IMO, as other people 
said in this thread, if there’s an “easy” way of spawning fibers (using the 
global/current context), you discourage people from using the “less easy” way 
of spawning fibers through custom contexts, which will inevitably lead to 
deadlocks.

I strongly believe that golang’s scopeless approach (which is the current 
approach already used by async php) is the best approach, and there should be 
no ways for users to mess with the internals of libraries that accidentally 
spawn a fiber in the current scope instead of a custom one.

Regards,
Daniil Gentili.

Reply via email to