Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:126917 X-Original-To: internals@lists.php.net Delivered-To: internals@lists.php.net Received: from php-smtp4.php.net (php-smtp4.php.net [45.112.84.5]) by qa.php.net (Postfix) with ESMTPS id 4BA811A00BC for ; Sun, 23 Mar 2025 17:48:52 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1742751982; bh=AMGLip+umNo3ZIILuS1vqrZzZzTH6UpKuJuDu4xtVa8=; h=References:In-Reply-To:From:Date:Subject:To:From; b=VIDkWlEQx9EFhKIs9n6ufLOxI6KH9/3PQTU7uBejujhkVQN6ww0dtXBy4MYSGpcvo NIXntSe/WpdNfWsUnvYrfTp0I2yX13gNQqJo22R+lahepqv6IrFb+SCjTa0zCBlxfB KuM+/SgD5a+laOengIoL5Scuv/ji3Vd2ejoMhm8IClsBN2VjRdlOk5nVrQmmPdAZck wqxwiqrKLWdUZEbL8OfiHKPX1SUxbwcflir0cyIUCfx5Yvxxpw4JHK40ddyTshzbnU higsx5pPqGLq0MD3fa7k7ikNcH3rec6B0rJK8k9XBgSRw2ubpjWwtURA9DVGmoA6jR YDhS/7GPIPW3A== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 306C018003C for ; Sun, 23 Mar 2025 17:46:21 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.0 (2022-12-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-2.0 required=5.0 tests=BAYES_20,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_PASS,FREEMAIL_FROM, HTML_MESSAGE,RCVD_IN_MSPIKE_H2,SPF_HELO_NONE,SPF_PASS autolearn=no autolearn_force=no version=4.0.0 X-Spam-Virus: No X-Envelope-From: Received: from mail-yb1-f179.google.com (mail-yb1-f179.google.com [209.85.219.179]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Sun, 23 Mar 2025 17:46:20 +0000 (UTC) Received: by mail-yb1-f179.google.com with SMTP id 3f1490d57ef6-e46ebe19368so2791176276.0 for ; Sun, 23 Mar 2025 10:48:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1742752130; x=1743356930; darn=lists.php.net; h=to:subject:message-id:date:from:in-reply-to:references:mime-version :from:to:cc:subject:date:message-id:reply-to; bh=vb5vYPl2z4xys4MuKF6Td0Ecc5K68onuDex6c4mxha8=; b=gxi+4LWHW/DJE2sdXITJU/RWWXYGW3Y4xTTpulwtu7Kzw4BB12cVIQqKJVZ6AVzoVA 157bgCdE8cFbq+hNr8+o/hzjZsF4+3LFJtl+kAaEttJ7WevU6IKZBr+Hp8GzIwyI+DxX L5F3ca0bDzUMgpmNEoyfYVV1gzQOcxkUJAsHJzuIV7sK5banWGptejHyUSrZoMHnPI+H lAeONKFt4QF4zEWAxViwzmCWSW8a+0ukbIDMubQSISZquQ3gDdQHu/koA5kuuLbk7WmJ NZpVu3UJFF0P9rM5I4DDjNvpPen6zI9ni40n8vXyAkzcXG93eKQjewhO+q16BYNLg8Gn mlOg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1742752130; x=1743356930; h=to:subject:message-id:date:from:in-reply-to:references:mime-version :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=vb5vYPl2z4xys4MuKF6Td0Ecc5K68onuDex6c4mxha8=; b=c9PibMCGbqlzRouXj2cwtIRVvRO6/XZ4gRZeMIaCkTqLGuMbWkSAIszpjJj2KM65v9 tjRlqrh0kNNwgGM0zsaV6pUueHdDAmS7CQTh9MB1WyVcTO7QrTPEFUxpvxFEPxNUtcNZ 18clTl1tDTXppBrtrZy3yjxHgE4Q7eBLuP9bUIwFbV5FJkDRwQF6at6TkyzORJ0vRmNs dz3SmGEWi0Ha5ZFzanwyBO2VQfzQPPL8MZQ3EDj79G/d4ikE1OoJwzJi4VjxJl5u/Sre j83htlFFKXnEZU5+MmuNwqNyy+ExVqYHfoRmCibGSb60Qx4pSX26l6gMhr5U2xwBbTuB rlWg== X-Gm-Message-State: AOJu0YzYtEn4itLn0qE3xFg9D0+DyTqCflNFYdjwkDLHm557pPHuo085 3TLjW+7bQIMLLTEkadsKfvcU/xL/JWdGDqpeVSsB0TA3sqjZHCuGjk+kI/O05QRAU1+fDvJzV8a t/5kFk36e9YPOlYxa+eaqzNOMyu8pK/20 X-Gm-Gg: ASbGnctSaSIM69GdVp3Oc8+JWwv36rqbUZy7kJsa6X/rjX8El1supc+q3H3GCNJ7UGg ODAO0E5PcGbF22jkINV9u4Ffc/vOKQv7WHTqYjjDa4c0YTuU5q9Xu8PKPurPv/LnPsJ137Zadqb C1XDx9NRR/cs9QD7aRl+kkJb7GCQ== X-Google-Smtp-Source: AGHT+IFUMDd0k3aecIImmEs3Xt0SgU+AW4e12qKL0S0rUe3zGyRvmSdEJBy9YA9IS5rPrsF67mMiYHvDOHII4yHIDJY= X-Received: by 2002:a05:6902:468e:b0:e5b:171c:35ee with SMTP id 3f1490d57ef6-e66a4fd4396mr12580030276.48.1742752129688; Sun, 23 Mar 2025 10:48:49 -0700 (PDT) Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net x-ms-reactions: disallow MIME-Version: 1.0 References: In-Reply-To: Date: Sun, 23 Mar 2025 19:48:38 +0200 X-Gm-Features: AQ5f1Joj1mvWF5J02kaYpKSM6FD-Tao8UhqI3yVXOoPHkL_FqX4Z66OE7vCmaPg Message-ID: Subject: [PHP-DEV] Re: PHP True Async RFC - Stage 2 To: php internals Content-Type: multipart/alternative; boundary="0000000000009f2d0506310619d4" From: edmond.ht@gmail.com (Edmond Dantes) --0000000000009f2d0506310619d4 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Hello everyone, It's a nice Sunday evening, and I'd like to share some updates and thoughts from this week =E2=80=94 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 =3D 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=E2=80=99s good n= ews. 4. I decided to borrow Larry's example and create a special code block "*as= ync 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 =3D stream_socket_accept($serverSocket, 0)) { connectionHandler($socket); } }; } } ``` It looks nice, even though it's syntactic sugar for: ```php $scope =3D 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 =E2=80=94 do it with full awaren= ess :) 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 =3D new Scope(); foreach ($users as $user) { spawn in $scope processUser($user); } return await $scope->tasks(); } ``` Code that waits until all child coroutines =E2=80=94 at any depth =E2=80=94= of the launched background tasks have completed. ```php function processBackgroundJobs(string ...$jobs): array { $scope =3D new Scope(); foreach ($jobs as $job) { spawn with $scope processJob($users); } await $scope->all(); } ``` It doesn=E2=80=99t look terrible, but I=E2=80=99m concerned that this kind = of functionality might feel =E2=80=9Ccomplex=E2=80=9D from a learning curve perspective. On = the other hand, Python=E2=80=99s approach to similar things is even more complex, largely b= ecause 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 =E2=80=94 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 =E2=80=9Cdark side of the force=E2=80=9D becomes harder. If you don=E2=80=99t use Scope =E2=80=94 you=E2=80=99re writing Go-style co= de. If you use Scope + all + async =E2=80=94 it=E2=80=99s Kotlin. If you use Scope + tasks() =E2=80=94 it=E2=80=99s more like Elixir. And you can also just pass $scope explicitly from function to function =E2= =80=94 then you get super-explicit structured concurrency. So I keep asking myself: wouldn=E2=80=99t it have been simpler to just impl= ement the Go model? :) How can you know in advance that the chosen solution won=E2=80=99t lead to = twisted practices or eventually result in anti-patterns? How can you tell if the chosen toolkit is strict enough =E2=80=94 and not *= too* flexible? These questions are the main reason why the next revision won=E2=80=99t 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 --0000000000009f2d0506310619d4 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable

Hello everyone,

It's a nice= Sunday evening, and I'd like to share some updates and thoughts from t= his week =E2=80=94 kind of like a digest :)

1. Big thanks to Rowan T= ommins for the syntax suggestions, ideas, and feedback. I decided to try us= ing the `spawn block` syntax, and in practice, it turned out to be quite co= nvenient. So I'll include it in the next phase. You can check out the d= etails via the link: https://github.com/EdmondD= antes/php-true-async-rfc/blob/main/basic.md#spawn-closure-syntax
<= div>
```php
function startServer(): void
{
= =C2=A0 =C2=A0 async $serverSupervisor {
=C2=A0 =C2=A0
=C2=A0 =C2=A0 = =C2=A0 // Secondary coroutine that listens for a shutdown signal
=C2=A0 = =C2=A0 =C2=A0 spawn use($serverSupervisor) {
=C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0await Async\signal(SIGINT);
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0$se= rverSupervisor->cancel(new CancellationException("Server shutdown&q= uot;));
=C2=A0 =C2=A0 =C2=A0 } =C2=A0 =C2=A0
=C2=A0 =C2=A0
=C2=A0= =C2=A0 =C2=A0 // Main coroutine that listens for incoming connections
= =C2=A0 =C2=A0 =C2=A0 await spawn {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 while ($socket =3D stream_socket_accept($serverSocket, 0)) { =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 connectionHandler($socket);
=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 }
=C2=A0 =C2=A0 =C2=A0 =C2=A0 };
=C2=A0 =C2=A0 }=
}
```

2.=C2=A0suspend h= as become a statement.

3. The scope retrieval func= tions like currentScope, globalScope, and r= ootScope have been removed. This has consequences. One of them: it&#= 39;s no longer possible to create a "detached" coroutine. But tha= t=E2=80=99s good news.

4. I decided to borrow Larr= y's example and create a special code block "async block"<= /b> that interacts with coroutine Scope in a special way.=C2= =A0

```php
function startServer(): void
{=C2=A0 =C2=A0 async $serverSupervisor {
=C2=A0 =C2=A0
=C2=A0 =C2=A0= =C2=A0 // Secondary coroutine that listens for a shutdown signal
=C2=A0= =C2=A0 =C2=A0 spawn use($serverSupervisor) {
=C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0await Async\signal(SIGINT);
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= $serverSupervisor->cancel(new CancellationException("Server shutdow= n"));
=C2=A0 =C2=A0 =C2=A0 } =C2=A0 =C2=A0
=C2=A0 =C2=A0
=C2= =A0 =C2=A0 =C2=A0 // Main coroutine that listens for incoming connections=C2=A0 =C2=A0 =C2=A0 await spawn {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 while ($socket =3D stream_socket_accept($serverSocket, 0)) { =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 connectionHandler($socket);
=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 }
=C2=A0 =C2=A0 =C2=A0 =C2=A0 };
=C2=A0 =C2=A0 }=
}
```

It looks nice, even though it'= ;s syntactic sugar for:

```php
$scope =3D new Scope();=

try {
=C2=A0 =C2=A0await spawn in $scope {
=C2=A0 =C2=A0 =C2= =A0 =C2=A0echo "Task 1\n";
=C2=A0 =C2=A0};
} finally {
= =C2=A0 =C2=A0 $scope->dispose();
}
```

This syntax creates a code block that limits the lifetime of coroutines u= ntil the block is exited. It doesn't *wait*, it *limits*. Besides the f= act 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 p= otential 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 foo= t.

Instead of `await $scope`, I want the programmer to explicitly ch= oose what exactly they intend to wait for: only direct children or all othe= rs. If you're going to shoot yourself in the foot =E2=80=94 do it with = full awareness :)

Drawback: it complicates the logic. But on the oth= er hand, this approach makes the code better.

The co= de only awaits the coroutines that were created inside the foreach:=C2=A0=C2=A0

```php
function processAllUse= rs(string ...$users): array
{
=C2=A0 =C2=A0 $scope =3D new Scope();=C2=A0 =C2=A0
=C2=A0 =C2=A0 foreach ($users as $user) {
=C2=A0 =C2= =A0 =C2=A0 =C2=A0 spawn in $scope processUser($user);
=C2=A0 =C2=A0 }=C2=A0 =C2=A0
=C2=A0 =C2=A0 return await $scope->tasks();
}
`= ``

Code that waits until all child coroutines = =E2=80=94 at any depth =E2=80=94 of the launched background tasks have comp= leted.

```php
function processBackground= Jobs(string ...$jobs): array
{
=C2=A0 =C2=A0 $scope =3D new Scope();<= br>=C2=A0 =C2=A0
=C2=A0 =C2=A0 foreach ($jobs as $job) {
=C2=A0 =C2= =A0 =C2=A0 =C2=A0 spawn with $scope processJob($users);
=C2=A0 =C2=A0 }<= br>=C2=A0 =C2=A0
=C2=A0 =C2=A0 await $scope->all();
}
```
<= /div>

It doesn=E2=80=99t look terrib= le, but I=E2=80=99m concerned that this kind of functionality might feel = =E2=80=9Ccomplex=E2=80=9D from a learning curve perspective. On the other h= and, Python=E2=80=99s approach to similar things is even more complex, larg= ely 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 =E2=80=94 i.e., a kind of flexibility that no other languag= e 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 =E2=80=9Cdark side of the force=E2=80=9D becomes harder.

If you don=E2=80=99t use Scope =E2=80=94 y= ou=E2=80=99re writing Go-style code.
If you use Scope + all + async =E2= =80=94 it=E2=80=99s Kotlin.
If you use Scope + tasks() =E2=80=94 it=E2=80=99s= more like Elixir.

And you can also just pass $scope explicit= ly from function to function =E2=80=94 then you get super-explicit structur= ed concurrency.

So I keep asking myself: wouldn=E2=80=99t it have been = simpler to just implement the Go model? :)

How can you know i= n advance that the chosen solution won=E2=80=99t lead to twisted practices = or eventually result in anti-patterns?
How can you tell if the chosen toolkit is strict enough =E2=80=94 and not <= em>too flexible?=C2=A0=C2=A0

These questions are the main reason why the next revision won=E2=80=99t= 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:

--0000000000009f2d0506310619d4--