Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:126797 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 1E0511A00BC for ; Sun, 16 Mar 2025 23:01:09 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1742165916; bh=R6H4xomYs+0Ujmum3rqY5+4C40iyO2Q1TukRIrWC1SM=; h=Date:Subject:To:References:From:In-Reply-To:From; b=NFcqvO5fEogIGrf11ZVK8aeLtdBFCOphqDoi6vTrtOrz+Ytgk/KbtssiRBtDx64k2 KEYjGAxefan9V+wA7MmlqTtLqnUnzPSy0sMnmekkWl0vt0wuU5BNIegcpfw6O06Rml 9icjQZRpswMcNPL1x5LrJhTAJ7UMfVAT8T8qKNLONlF02AzBtB90H4iOzaM2ILmJ2p F4QYr+bAG5W4DxUeKRaTm2qazExg3ivHsTy/FSdCvz6HJle/N9xhGBk4//RFhJ8lJz KfOMLGjPMOt0VS3RjhoukEnFbSGxkyuhUcQB7ht8bE+bmr6AjyFtl21/CSehUVxtzW zMXW+wauTEcRA== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id D608318003C for ; Sun, 16 Mar 2025 22:58:31 +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.8 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_MISSING,HTML_MESSAGE, RCVD_IN_DNSWL_LOW,SPF_HELO_PASS,SPF_PASS autolearn=no autolearn_force=no version=4.0.0 X-Spam-Virus: No X-Envelope-From: Received: from fhigh-a4-smtp.messagingengine.com (fhigh-a4-smtp.messagingengine.com [103.168.172.155]) (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, 16 Mar 2025 22:58:31 +0000 (UTC) Received: from phl-compute-10.internal (phl-compute-10.phl.internal [10.202.2.50]) by mailfhigh.phl.internal (Postfix) with ESMTP id 0257A11400C4 for ; Sun, 16 Mar 2025 19:01:03 -0400 (EDT) Received: from phl-mailfrontend-01 ([10.202.2.162]) by phl-compute-10.internal (MEProxy); Sun, 16 Mar 2025 19:01:03 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=rwec.co.uk; h=cc :content-type:content-type:date:date:from:from:in-reply-to :in-reply-to:message-id:mime-version:references:reply-to:subject :subject:to:to; s=fm1; t=1742166062; x=1742252462; bh=sbDNs5v0X0 Gn9DEcIqqluEN8rVYn8zFC0luCL4XJr+Q=; b=iaqKtM4Eveituz7rF7wb+tJGHU 3nfcZMPmAQhpw9manQkNWjfIgYYqpW5G1s/oSDWAwwC+4mWCTgk+dTqXRr/qr6GV 7LTW2tqyTJOdm+Wj8kpOtxAa4jzUbPhcjoWHHjYaaCfHEPV8raiIRAag+9FTxBFX dhhwuulv/dSDtW3QMCgKs32Dofex2g/KBYccLbg74T12ZU5N9mUJwtjyHY7OksJk 57m7AkPSlUc9Wl9VqLnJ+brrTq0SE94c3ELrnkgaaxqpGYBIMff4Bk4zwQbsaf4D /COhlNmPQQe1xxVrl8FlNawE8IaYnJAsap7Fm58eMwb2VKVn4aqnfRp1o31Q== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-type:content-type:date:date :feedback-id:feedback-id:from:from:in-reply-to:in-reply-to :message-id:mime-version:references:reply-to:subject:subject:to :to:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s=fm1; t= 1742166062; x=1742252462; bh=sbDNs5v0X0Gn9DEcIqqluEN8rVYn8zFC0lu CL4XJr+Q=; b=FbbZUXCVAWr2Li7TMMllyYqBar6VX5WtUkUDTUZn1wpZAv7KK79 rRhLFMq0qfqnQMtz5f4CeY3GJJ2Re+xJYxqHhpDDexOtbWSbbZcXE/u7lQ5Ni73I apBm/DvzLkssGkvk5I/opsZTfyMy4+s9NbOZdaMNf2hqJo9TEHXFQoi6evOr5sz/ lLQKS9sV1znuwX8mJmUXzD14NP3ddG5XnEbjhjWuYJArGgtI40YnRbje96JFNBN+ 3bj1X9+Elh17NVyPlosloszWhoXvxVcrNyfFXby56oUAg08gnxmfvM9MqFH7zyqZ NuYZg2TzMZz/BzNyMJDE9qF0tST+h2fmjUw== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefvddrtddtgddufeejkeekucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdggtfgfnhhsuhgsshgtrhhisggv pdfurfetoffkrfgpnffqhgenuceurghilhhouhhtmecufedttdenucenucfjughrpegtkf ffgggfuffvfhfhjgesrgdtreertddvjeenucfhrhhomhepfdftohifrghnucfvohhmmhhi nhhsucglkffoufhorfgnfdcuoehimhhsohhprdhphhhpsehrfigvtgdrtghordhukheqne cuggftrfgrthhtvghrnhepgefghfdvgeefueffteekkeelfeduvdegjeetudevveetgedv feejleefgedtkeevnecuffhomhgrihhnpehphhhprdhnvghtnecuvehluhhsthgvrhfuih iivgeptdenucfrrghrrghmpehmrghilhhfrhhomhepihhmshhophdrphhhphesrhifvggt rdgtohdruhhkpdhnsggprhgtphhtthhopedupdhmohguvgepshhmthhpohhuthdprhgtph htthhopehinhhtvghrnhgrlhhssehlihhsthhsrdhphhhprdhnvght X-ME-Proxy: Feedback-ID: id5114917:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA for ; Sun, 16 Mar 2025 19:01:01 -0400 (EDT) Content-Type: multipart/alternative; boundary="------------e0FXcx0ehvLG06ekQVsq1FM6" Message-ID: <72bd5401-53a9-409f-ad45-687333401961@rwec.co.uk> Date: Sun, 16 Mar 2025 23:01:01 +0000 Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net x-ms-reactions: disallow MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: [PHP-DEV] PHP True Async RFC - Stage 2 To: internals@lists.php.net References: Content-Language: en-GB In-Reply-To: From: imsop.php@rwec.co.uk ("Rowan Tommins [IMSoP]") This is a multi-part message in MIME format. --------------e0FXcx0ehvLG06ekQVsq1FM6 Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 8bit On 16/03/2025 09: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. > I would like to once again thank everyone who participated in the > previous discussion. It was great! Thank you for taking such a positive approach to the feedback, and continuing to really work hard on this! I found this version of the RFC much more digestible than the last, and have some specific comments on various sections... ## Possible Syntax > In this RFC, you can see a potential new syntax for describing concurrency. This syntax is NOT a mandatory part of this RFC and may be adopted separately. As I said in the previous thread, I disagree with this - control flow should use keywords, not functions. We don't expose a function Loop\while(callable $condition, callable $action): void Having both just leads to confusing caveats, like: > Warning: The spawn function does not allow passing reference data as parameters. This limitation can be overcome using the spawn operator. Specifically, I suggest keywords (and no function equivalents) for the following (names subject to bikeshedding): - spawn - spawn in $scope  - await - suspend - defer Some things proposed as methods can be easily composed from these keywords by the user: // instead of $scope->awaitDirectChildren(); Async\await_all( $scope->getDirectChildren() ); // instead of $crucialCoroutine = $boundedScope->spawnAndBound( some_function() ); $crucialCoroutine = spawn in $boundedScope some_function(); $boundedScope->boundedBy($crucialCoroutine); > The spawn function can be replaced using the spawn operator, which has two forms ... executing a closure This leads to an ambiguity / source of confusion: function x(): int { return 42; } spawn x(); // spawns x(); spawn function(): int { return 42; } // immediately evaluates the function() expression to a Closure, and then spawns it $var = 42; spawn $var; // presumably throws an error $callable = function(): int { return 42; }; spawn $callable; // does this spawn the callable, or throw an error? function foo(): callable { return bar(...); } spawn foo(); // does this spawn foo(), or run foo() immediately and then spawn bar() ?? I suggest we instead allow "spawn" to be followed by a block, which implicitly creates and spawns a zero-argument Closure: function x(): int { return 42; } spawn x(); // spawns x(); $callable = function() { foo(); bar(); }; spawn $callable(); // spawns the Closure spawn ( function() { foo(); bar(); } )(); // legal, but unlikely in practice; note the last (), to call the closure, not just define it spawn { foo(); bar(); } // shorthand for the previous example I'm not sure about variable capture - probably we would need a use() clause for consistency with function{}, and to avoid inventing a new capture algorithm: $x = 42; spawn use ($x) { do_something($x); do_something_else($x); } ## Lifetime Limitation The RFC currently mentions "the Actor model" but doesn't actually explain what it is, or what "other languages" implement it. I imagine you wanted to avoid the explanation getting any longer, but maybe some links could be added to help people find examples of it? ## BoundedScope This API needs a bit more thought I think: * The name of "withTimeout" sounds like it will return a modified clone, and the description says it "creates" something, but the example shows it mutating an existing object * What happens if boundedBy and/or spawnAndBound are called multiple times on the same BoundedScope? Is the lifetime the shortest of those provided? Or the longest? Or just the last one called? * boundedBy should not accept "mixed", but a specific union (e.g. CancellationToken|Future|Coroutine) or interface (e.g. ScopeConstraint) ## Coroutine Scope Slots The Async\Key class doesn't belong in the Async namespace or this RFC, but as a top-level feature of the language, in its own RFC. In general, the "scope slots" API still feels over-engineered, and lacking a separation of concerns: - I'm not convinced that supporting object keys is necessary; is it something userland libraries are widely implementing, or just personal taste? - I don't understand the difference between find(), get(), findLocal(), and getLocal() - Scope and Context seem like separate concerns, which should be composed not inherited - automatic dereferencing of WeakReference seems an unnecessary piece of magic - unless I'm missing something, it just saves the user typing "->get()" I haven't worked through all the use cases, but I think the key primitives are: - Scope->getParent(): ?Scope - Scope->getContext(): Context - Coroutine->getContext(): Context - Context->set(string $key, mixed $value): void - Context->get(string $key): mixed That allows for: currentScope()->getContext()->get('scope_value'); currentScope()->getParent()->getContext()->get('inherited_value'); currentCoroutine()->getContext()->get('local_value'); A framework can build more complex inheritance relationships on top of that, e.g. function get_from_request_scope(string $key): mixed {     $scope = currentScope();     while ( $scope !== null && ! $scope->getContext()->get('acme_framework::is_request_scope') ) {         $scope = $scope->getParent();     }     return $scope?->get($key); } ## Error Handling > If multiple points are awaiting, each will receive the exception. > ... > If the |Scope| has responsibility points, i.e., the construction |await $scope|, all responsibility points receive the exception. Is the exception cloned into each of these coroutines, or are they all given exactly the same instance? An exception being thrown in multiple places "simultaneously" is hard to visualise, and I wonder if it will lead to confusing situations. > $coroutine->cancel(new Async\CancellationException('Task was cancelled')); This looks odd - why is the caller creating the exception? Can any exception be used there? I suggest: $coroutine->cancel('some message'); // constructs an Async\CancellationException and throws it inside the coroutine > Warning: You should not attempt to suppress CancellationException exception, as it may cause application malfunctions. This and other special behaviours suggest that this should inherit from Error rather than Exception, or possibly directly from Throwable That's all for now. To reiterate: thank you so much for working on this, and I really like the shape it's beginning to take :) -- Rowan Tommins [IMSoP] --------------e0FXcx0ehvLG06ekQVsq1FM6 Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: 8bit
On 16/03/2025 09:24, Edmond Dantes wrote:
Good day, everyone. I hope you're doing well.


Here is a new version of the RFC dedicated to asynchrony.


I would like to once again thank everyone who participated in the previous discussion. It was great! 


Thank you for taking such a positive approach to the feedback, and continuing to really work hard on this!

I found this version of the RFC much more digestible than the last, and have some specific comments on various sections...


## Possible Syntax


> In this RFC, you can see a potential new syntax for describing concurrency. This syntax is NOT a mandatory part of this RFC and may be adopted separately.

As I said in the previous thread, I disagree with this - control flow should use keywords, not functions. We don't expose a function Loop\while(callable $condition, callable $action): void

Having both just leads to confusing caveats, like:

> Warning: The spawn function does not allow passing reference data as parameters. This limitation can be overcome using the spawn operator.


Specifically, I suggest keywords (and no function equivalents) for the following (names subject to bikeshedding):

- spawn
- spawn in $scope
 - await
- suspend
- defer

Some things proposed as methods can be easily composed from these keywords by the user:

// instead of $scope->awaitDirectChildren();
Async\await_all( $scope->getDirectChildren() );

// instead of $crucialCoroutine = $boundedScope->spawnAndBound( some_function() );
$crucialCoroutine = spawn in $boundedScope some_function();
$boundedScope->boundedBy($crucialCoroutine);


> The spawn function can be replaced using the spawn operator, which has two forms ... executing a closure

This leads to an ambiguity / source of confusion:

function x(): int { return 42; }
spawn x(); // spawns x();
spawn function(): int { return 42; } // immediately evaluates the function() expression to a Closure, and then spawns it

$var = 42;
spawn $var; // presumably throws an error

$callable = function(): int { return 42; };
spawn $callable; // does this spawn the callable, or throw an error?

function foo(): callable { return bar(...); }
spawn foo(); // does this spawn foo(), or run foo() immediately and then spawn bar() ??


I suggest we instead allow "spawn" to be followed by a block, which implicitly creates and spawns a zero-argument Closure:

function x(): int { return 42; }
spawn x(); // spawns x();

$callable = function() { foo(); bar(); };
spawn $callable(); // spawns the Closure

spawn ( function() { foo(); bar(); } )(); // legal, but unlikely in practice; note the last (), to call the closure, not just define it

spawn { foo(); bar(); } // shorthand for the previous example

I'm not sure about variable capture - probably we would need a use() clause for consistency with function{}, and to avoid inventing a new capture algorithm:

$x = 42;
spawn use ($x) { do_something($x); do_something_else($x); }


## Lifetime Limitation


The RFC currently mentions "the Actor model" but doesn't actually explain what it is, or what "other languages" implement it. I imagine you wanted to avoid the explanation getting any longer, but maybe some links could be added to help people find examples of it?


## BoundedScope


This API needs a bit more thought I think:

* The name of "withTimeout" sounds like it will return a modified clone, and the description says it "creates" something, but the example shows it mutating an existing object
* What happens if boundedBy and/or spawnAndBound are called multiple times on the same BoundedScope? Is the lifetime the shortest of those provided? Or the longest? Or just the last one called?
* boundedBy should not accept "mixed", but a specific union (e.g. CancellationToken|Future|Coroutine) or interface (e.g. ScopeConstraint)


## Coroutine Scope Slots


The Async\Key class doesn't belong in the Async namespace or this RFC, but as a top-level feature of the language, in its own RFC.


In general, the "scope slots" API still feels over-engineered, and lacking a separation of concerns:

- I'm not convinced that supporting object keys is necessary; is it something userland libraries are widely implementing, or just personal taste?
- I don't understand the difference between find(), get(), findLocal(), and getLocal()
- Scope and Context seem like separate concerns, which should be composed not inherited
- automatic dereferencing of WeakReference seems an unnecessary piece of magic - unless I'm missing something, it just saves the user typing "->get()"

I haven't worked through all the use cases, but I think the key primitives are:

- Scope->getParent(): ?Scope
- Scope->getContext(): Context
- Coroutine->getContext(): Context
- Context->set(string $key, mixed $value): void
- Context->get(string $key): mixed

That allows for:

currentScope()->getContext()->get('scope_value');
currentScope()->getParent()->getContext()->get('inherited_value');
currentCoroutine()->getContext()->get('local_value');

A framework can build more complex inheritance relationships on top of that, e.g.

function get_from_request_scope(string $key): mixed {
    $scope = currentScope();
    while ( $scope !== null && ! $scope->getContext()->get('acme_framework::is_request_scope') ) {
        $scope = $scope->getParent();
    }
    return $scope?->get($key);
}



## Error Handling


> If multiple points are awaiting, each will receive the exception.
> ...
> If the Scope has responsibility points, i.e., the construction await $scope, all responsibility points receive the exception.

Is the exception cloned into each of these coroutines, or are they all given exactly the same instance?

An exception being thrown in multiple places "simultaneously" is hard to visualise, and I wonder if it will lead to confusing situations.


> $coroutine->cancel(new Async\CancellationException('Task was cancelled'));

This looks odd - why is the caller creating the exception? Can any exception be used there? I suggest:

$coroutine->cancel('some message'); // constructs an Async\CancellationException and throws it inside the coroutine


> Warning: You should not attempt to suppress CancellationException exception, as it may cause application malfunctions.

This and other special behaviours suggest that this should inherit from Error rather than Exception, or possibly directly from Throwable



That's all for now. To reiterate: thank you so much for working on this, and I really like the shape it's beginning to take :)

-- 
Rowan Tommins
[IMSoP]
--------------e0FXcx0ehvLG06ekQVsq1FM6--