Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:128876 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 lists.php.net (Postfix) with ESMTPS id A154E1A00BC for ; Tue, 21 Oct 2025 06:47:09 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1761029234; bh=2nMccMpU7SxSoV0i0c0UXyy3muo34N3tuqrzZMJuOoE=; h=Date:From:To:In-Reply-To:References:Subject:From; b=NElM+IBgJXMaYjKPiX1FZc3ftWoYqzJvE4MrxW93UhYUiTmgkQ4Zr+BV9vIcDsNtG XpjeVscoK8G/vaXClktuSsgUWjv1tGD2Ia5YP4On8qEGR0B+PPUUSJg5aDwz5e1fsk WQ7vBR0gjp/x+hBE1ftv/Nl9nCcKg25qBWL4JwGMkc5RVH5ijDcLGf8T0r1AyOmX62 jda1M4fKdLftZI6FKsmBCEk54K8BDm9PTDzk/Dqvw2lHPdEeIKjrnzs4atzSD6zZnq corwhGTToag97umajxirLjk3qG32wxQHvlMIJx6U5HT9IckqDp2x670WoJwT2lfzVm 6ygRIbg5Fjuyw== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 9DAE4180055 for ; Tue, 21 Oct 2025 06:47:12 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) 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.1 X-Spam-Virus: No X-Envelope-From: Received: from fhigh-b2-smtp.messagingengine.com (fhigh-b2-smtp.messagingengine.com [202.12.124.153]) (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 ; Tue, 21 Oct 2025 06:47:12 +0000 (UTC) Received: from phl-compute-05.internal (phl-compute-05.internal [10.202.2.45]) by mailfhigh.stl.internal (Postfix) with ESMTP id A8A367A0170 for ; Tue, 21 Oct 2025 02:47:06 -0400 (EDT) Received: from phl-imap-05 ([10.202.2.95]) by phl-compute-05.internal (MEProxy); Tue, 21 Oct 2025 02:47:06 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bottled.codes; 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=fm3; t=1761029226; x=1761115626; bh=vjSPySe5bO wolo8n0Yx/OEj/bjU5ILgppIoIkc6Wxnk=; b=BhqOSDmuIy4F5VO8Q/OkXcw3HY izcuBlWLyOHP9CpVQA+ZabIaBRae8Lpr+B5zL9E2qbNZ1N6ImgZyJnTmt2ziasqJ JmXku2HjzvE1e2zmtiWCgH85YHzC20AT0WSgT03VkAnXS0BT/jgPOcuTyM+aQtRe bQoeLUgTWvrm9AXX0uMpdv+/N+3v9TP8GvITnGWG0eHdF8x2wYZGG0jM00CzpxKi SDqcK1Wj1tY+KTR0lX5+0IvJmadcsc3vEYTpFOxAF2WjRyCm/o/YVN0wRSWFzX6R o/K31U2+46oBqth4HTk9D3KsVbq//f6P5//GFCgcwuEbHcdN1OODUXiqIvDw== 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=fm2; t= 1761029226; x=1761115626; bh=vjSPySe5bOwolo8n0Yx/OEj/bjU5ILgppIo Ikc6Wxnk=; b=jDFb/SYBCj2G37rVByGsq56hC4s4QViK/UZAGmZr5vbPC2OD8YM FJiP+eRWX/kqIWewbB4l65lRsEtj4jIXuozcXb7ytfEOGajPFWScWlrFAQx4RSy9 Z8erQCHISXCbH8qVeA3zvkH45XDsKYMtDgsV7pjWcueBX9r22WbB2xZcb3ioP2qF p9benFgS1/eX01eId2zvnEO84XHXPpCSKBlkR9CcmBoqF23r09lsL7syn4L3b/HJ NHhJ8aKxGwNwPaw+QCZahOZO3Orh44XoXZZq9coA5bkFcCliMlxAKVPU79EEZEyC 9xLplE3bvQlJFeoUBIbc5ASQt9/PXvV3nYg== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeeffedrtdeggddugedttddvucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfurfetoffkrfgpnffqhgenuceu rghilhhouhhtmecufedttdenucenucfjughrpefoggffhffvkfgjfhfutgesrgdtreerre dtjeenucfhrhhomhepfdftohgsucfnrghnuggvrhhsfdcuoehrohgssegsohhtthhlvggu rdgtohguvghsqeenucggtffrrghtthgvrhhnpeehieffkeetleejueefjeffueffheevud fgteevfeeijeevfffhhfegueetteffleenucffohhmrghinhepphhhphdrnhgvthdpghhi thhhuhgsrdgtohhmnecuvehluhhsthgvrhfuihiivgeptdenucfrrghrrghmpehmrghilh hfrhhomheprhhosgessghothhtlhgvugdrtghouggvshdpnhgspghrtghpthhtohepuddp mhhouggvpehsmhhtphhouhhtpdhrtghpthhtohepihhnthgvrhhnrghlsheslhhishhtsh drphhhphdrnhgvth X-ME-Proxy: Feedback-ID: ifab94697:Fastmail Received: by mailuser.phl.internal (Postfix, from userid 501) id 0928E1820054; Tue, 21 Oct 2025 02:47:05 -0400 (EDT) X-Mailer: MessagingEngine.com Webmail Interface Precedence: list list-help: list-unsubscribe: list-post: List-Id: x-ms-reactions: disallow MIME-Version: 1.0 X-ThreadId: AG5VjklPAjNR Date: Tue, 21 Oct 2025 08:46:45 +0200 To: internals@lists.php.net Message-ID: <0e4e39d6-9cc9-4970-92e0-2463143b4011@app.fastmail.com> In-Reply-To: References: Subject: Re: [PHP-DEV] PHP True Async RFC Stage 4 Content-Type: multipart/alternative; boundary=51143d5635d549aaba4cd112a1ebfe74 From: rob@bottled.codes ("Rob Landers") --51143d5635d549aaba4cd112a1ebfe74 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable On Sun, Oct 5, 2025, at 07:23, Edmond Dantes wrote: > Good day, everyone. I hope you're doing well. >=20 > I=E2=80=99m happy to present the fourth version of the RFC. It wasn=E2= =80=99t just me > who worked on it =E2=80=94 members of the PHP community contributed as= well. > Many thanks to everyone for your input! >=20 > https://wiki.php.net/rfc/true_async >=20 > **What has changed in this version?** >=20 > The RFC has been significantly simplified: >=20 > 1. Components (such as TaskGroup) that can be discussed in separate > RFCs have been removed from the current one. > 2. Coroutines can now be created anywhere =E2=80=94 even inside shutdo= wn_function. > 3. Added Memory Management and Garbage Collection section >=20 > Although work on the previous API RFC was interrupted and we weren=E2=80= =99t > able to include it in PHP 8.5, it still provided valuable feedback on > the Async API code. >=20 > During this time, I managed to refactor and optimize the TrueAsync > code, which showed promising performance results in I/O scenarios. >=20 > A test integration between **NGINX UNIT** and the **TrueAsync API** > was implemented to evaluate the possibility of using PHP as an > asynchronous backend for a web server: > https://github.com/EdmondDantes/nginx-unit/tree/true-async/src/true-as= ync-php >=20 > During this time, the project has come very close to beta status. >=20 > Once again, I want to thank everyone who supported me during difficult > times, offered advice, and helped develop this project. >=20 > Given the maturity of both the code and the RFC, this time I hope to > proceed with a vote. >=20 > Wishing you all a great day, and thank you for your feedback! >=20 Hey Edmond, I'm not quite finished notating the whole thing, but let's start with th= is:=20 AWAITABLE There's something here that bothers me. The RFC says: > he `Awaitable` interface is a contract that allows objects to be used = in the `await` expression. > The `Awaitable` interface does not impose limitations on the number of= state changes. > In the general case, objects implementing the `Awaitable` interface ca= n act as triggers =E2=80=94 that is, they can change their state an unli= mited number of times. This means that multiple calls to `await ` may produce different results. But then coroutines say: > *Coroutines behave like Futures:* > once a coroutine completes (successfully, with an exception, or throug= h cancellation), > it preserves its final state. > Multiple calls to `await()` on the same coroutine will always return t= he same result or > throw the same exception. This seems a bit contradictory and confuses things. When I await(), do I= need to do it in a loop, or just once? It might be a good idea to make = a couple subtypes: Signal and Future. Coroutines become Future that only= await once, while Signal is something that can be awaited many times. It probably won't change much from the C point of view, but it would cha= nge how we implement them in general libraries: if ($awaitable instanceof Trigger) { // throw or maybe loop over the trigger value? } CANCELLATIONS The RFC says: > In the context of coroutines, it is not recommended to use `catch \Thr= owable` or `catch CancellationError`. But the example setChildScopeExceptionHandler does exactly this! Further= , much framework/app code uses the $previous to wrap exceptions as they = bubble up, so it might be nice to have an Async\isCancellation(Throwable= ): bool function that can efficiently walk the exception chain and tell = us if any cancellation was involved. Minor nit: in the Async\protect section, it would be nice to say that ca= ncellations being AFTER the protect() are guaranteed, and also specify r= eentry/nesting of protect(). Like what happens here: Async\protect(foo(...)); // foo also calls protect() And for reentrancy, foo() -> bar() -> foo() -> bar() and foo() calls pro= tect(). Which one gets the cancellation? I also think that calling it a "critica= l section" is a misnomer, as that traditionally indicates a "lock" (only= one thread can execute that section at a time) and not "this can't be c= ancelled". Also, if I'm reading this correctly, a coroutine can mark itself as canc= eled, yet run to completion; however anyone await()'ing it, will get a C= ancellationException instead of the completed value? DESTRUCTORS Allowing destructors to spawn feels extremely dangerous to me (but power= ful). These typically -- but not always -- run between the return statem= ent and the next line (typically best to visualize that as the "}" since= it runs in the original scope IIRC). That could make it 'feel like' met= hods/functions are hanging or never returning if a library abuses this b= y suspending or awaiting something. ZOMBIES async.zombie_coroutine_timeout says 2 seconds in the text, but 5 seconds= in the php.ini section. The RFC says: > Once the application is considered finished, zombie coroutines are giv= en a time limit within which they must complete execution. If this limit= is exceeded, all zombie coroutines are canceled. What is defined as "application considered finished?" FrankenPHP workers= , for instance, don=E2=80=99t "finish" =E2=80=94 is there a way to reap = zombies manually? Then there is dispose() and disposeSafely(), it would be good to specify= ordering and finally/onFinally execution here. ie, in nested scopes, do= es it go from inner -> outer scopes, in the order they are created? When= does finally/onFinally execute in that context? FIBERS Fibers are proliferant in existing code. It would be a good idea to prov= ide a few helpers to allow code to migrate. Maybe something like Async\i= sEnabled() to know whether I should use fibers or not. Nit: the error message has a grammatical error: "Cannot create a fiber w= hile **an** True Async is active" should be "Cannot create a fiber while= True Async is active"? SHUTDOWN Is there also a timeout on Phase 1 shutdown? Otherwise, if it is only an= exception, then this could hang forever. EXCEPTION IDENTITY The RFC says: > Multiple calls to `await()` on the same coroutine will always return t= he same result or > throw the same exception. Is this "same exception" mean this literally, or is it a clone? If it is= the same, what prevents another code path from mutating the original ex= ception before it gets to me? TYPOS - fix `file_get_content` to `file_get_contents` in examples. - I think get_last_error() should be error_get_last()? - you call "suspend" a keyword in several places but it is actually a fu= nction. - examples use sleep(), but don't clarify whether sleep() will be blocki= ng or non-blocking. - Sometimes you use AwaitCancelledException and other times Cancellation= Error. Which is it? =E2=80=94 Rob --51143d5635d549aaba4cd112a1ebfe74 Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable
On Sun, Oct = 5, 2025, at 07:23, Edmond Dantes wrote:
Good day, everyone. I hope you're doing well.

I=E2=80=99m happy to present the fourth version = of the RFC. It wasn=E2=80=99t just me
who worked on it =E2=80=94= members of the PHP community contributed as well.
Many thanks= to everyone for your input!

https://wiki.php.net/rfc/true_async

**What has changed in this version?**
<= br>
The RFC has been significantly simplified:

<= /div>
1. Components (such as TaskGroup) that can be discussed in sep= arate
RFCs have been removed from the current one.
2= . Coroutines can now be created anywhere =E2=80=94 even inside shutdown_= function.
3. Added Memory Management and Garbage Collection se= ction

Although work on the previous API RFC was= interrupted and we weren=E2=80=99t
able to include it in PHP = 8.5, it still provided valuable feedback on
the Async API code= .

During this time, I managed to refactor and o= ptimize the TrueAsync
code, which showed promising performance= results in I/O scenarios.

A test integration b= etween **NGINX UNIT** and the **TrueAsync API**
was implemente= d to evaluate the possibility of using PHP as an
asynchronous = backend for a web server:

During this time, the project has come very close to be= ta status.

Once again, I want to thank everyone= who supported me during difficult
times, offered advice, and = helped develop this project.

Given the maturity= of both the code and the RFC, this time I hope to
proceed wit= h a vote.

Wishing you all a great day, and than= k you for your feedback!


Hey Edmond,

I'm not quite finished notati= ng the whole thing, but let's start with this:

AWAITABLE

There's something here that bothers = me. The RFC says:

he Awaitable interface is a = contract that allows objects to be used in the await expression.
The Awaitable interface do= es not impose limitations on the number of state changes.
In the general case, objec= ts implementing the Awaitable interface can act as triggers =E2=80=94 that = is, they can change their state an unlimited number of times. This means= that multiple calls to await <Awaitable> may produce different resul= ts.

But then coroutines say:
=

Coroutines behave like = Futures:
once a coroutine completes (successfully, with an exceptio= n, or through cancellation),
it preserves its final state.
Multiple calls to await() on the same coroutine will always return the sam= e result or
throw the same exception.
<= div>
This seems a bit contradictory and confuses things. W= hen I await(), do I need to do it in a loop, or just once? It might be a= good idea to make a couple subtypes: Signal and Future. Coroutines beco= me Future that only await once, while Signal is something that can be aw= aited many times.

It probably won't change much= from the C point of view, but it would change how we implement them in = general libraries:

if ($awaitable instanceof Tr= igger) {
  // throw or maybe loop over the trigger value?=
}

CANCELLATIONS

=
The RFC says:

In the context of c= oroutines, it is not recommended to use catch \Throwable or catch CancellationError.

But the example setChildScopeExceptionHan= dler does exactly this! Further, much framework/app code uses the $previ= ous to wrap exceptions as they bubble up, so it might be nice to have an= Async\isCancellation(Throwable): bool function that can efficiently wal= k the exception chain and tell us if any cancellation was involved.

Minor nit: in the Async\protect section, it would b= e nice to say that cancellations being AFTER the protect() are guarantee= d, and also specify reentry/nesting of protect(). Like what happens here= :

Async\protect(foo(...)); // foo also calls pr= otect()

And for reentrancy, foo() -> bar() -= > foo() -> bar() and foo() calls protect().

Which one gets the cancellation? I also think that calling it a "crit= ical section" is a misnomer, as that traditionally indicates a "lock" (o= nly one thread can execute that section at a time) and not "this can't b= e cancelled".

Also, if I'm reading this correct= ly, a coroutine can mark itself as canceled, yet run to completion; howe= ver anyone await()'ing it, will get a CancellationException instead of t= he completed value?

DESTRUCTORS

<= /div>
Allowing destructors to spawn feels extremely dangerous to me = (but powerful). These typically -- but not always -- run between the ret= urn statement and the next line (typically best to visualize that as the= "}" since it runs in the original scope IIRC). That could make it 'feel= like' methods/functions are hanging or never returning if a library abu= ses this by suspending or awaiting something.

Z= OMBIES

async.zombie_coroutine_timeout says 2 se= conds in the text, but 5 seconds in the php.ini section.

<= /div>
The RFC says:

Once the appli= cation is considered finished, zombie coroutines are given a time limit = within which they must complete execution. If this limit is exceeded, al= l zombie coroutines are canceled.

What is defined as "application considered finished?" FrankenPHP = workers, for instance, don=E2=80=99t "finish" =E2=80=94 is there a way t= o reap zombies manually?

Then there is dispose(= ) and disposeSafely(), it would be good to specify ordering and finally/= onFinally execution here. ie, in nested scopes, does it go from inner -&= gt; outer scopes, in the order they are created? When does finally/onFin= ally execute in that context?

FIBERS
=
Fibers are proliferant in existing code. It would be a go= od idea to provide a few helpers to allow code to migrate. Maybe somethi= ng like Async\isEnabled() to know whether I should use fibers or not.

Nit: the error message has a grammatical error: "= Cannot create a f= iber while an True Async is active" should be "Cannot crea= te a fiber while True Async is active"?

= SHUTDOWN

Is there also a timeout on Phase 1 shutdown? Oth= erwise, if it is only an exception, then this could hang forever.
<= div>
EXCEPTION IDENTITY

The RFC s= ays:

Multiple calls to await() on th= e same coroutine will always return the same result or
= throw the same ex= ception.

Is this "same exce= ption" mean this literally, or is it a clone? If it is the same, what pr= events another code path from mutating the original exception before it = gets to me?

TYPOS

- fi= x `file_get_content` to `file_get_contents` in examples.
- I t= hink get_last_error() should be error_get_last()?
- you call "= suspend" a keyword in several places but it is actually a function.
- examples use sleep(), but don't clarify whether sleep() will be = blocking or non-blocking.
- Sometimes you use AwaitCancelledEx= ception and other times CancellationError. Which is it?

=E2=80=94 Rob
--51143d5635d549aaba4cd112a1ebfe74--