Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:128877 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 045211A00BC for ; Tue, 21 Oct 2025 11:32:21 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1761046346; bh=CAmPeZF4KlNlEFJwc77rRq6qOQXKyx5GW2sztJIpDkw=; h=Date:From:To:In-Reply-To:References:Subject:From; b=Ld4eI098reGg+g8nSSPgkiPJK1Cajv1oBVmK2q2Bz8byDKMqW+Jnva382IwXRwAQ3 NvxNEaUPOxxUAdBF73B079G/nWA3OcHTwSqXHZjFNnfXam3n4YNY0KnSYjrBt0eejS 0hDknpiZq4F9exEiuwaxd5QwL3TlOudhPisfXax8+hX+XTMYNMisoB6oj8aihkC+i2 /TZH7k25o4oylly768yu94XLUsq8SnTsVVCn8qHQJhnqMlqfBIXUSs1ApEfWM/dUKe jY3htNoRefHUmH2yLhuq0Chv/KGc86F/pFM/drOJU3yUQmNAB1loy97bFPtI5w/aT3 6vwZfN3DYJMSw== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 2AA86180057 for ; Tue, 21 Oct 2025 11:32:25 +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=-0.9 required=5.0 tests=BAYES_20,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-b5-smtp.messagingengine.com (fhigh-b5-smtp.messagingengine.com [202.12.124.156]) (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 11:32:24 +0000 (UTC) Received: from phl-compute-05.internal (phl-compute-05.internal [10.202.2.45]) by mailfhigh.stl.internal (Postfix) with ESMTP id 2DD2B7A00B5 for ; Tue, 21 Oct 2025 07:32:19 -0400 (EDT) Received: from phl-imap-05 ([10.202.2.95]) by phl-compute-05.internal (MEProxy); Tue, 21 Oct 2025 07:32:19 -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=1761046339; x=1761132739; bh=80zLqkfCro lEagEKvS6VVM/10YgfSyxxQBsFnLVS1io=; b=JZdMBJlmJI9eWkvMad+dumO8Ez 3a/ZEtVWp0JWGT6D+t3knfXr9ofP3wLrVBiyV3VVwHZ7s1SLFL+st4oKq4tY5w2X BgP0cNcYforEs3EGv2UWGaNFut6q3Il5z9vKar29ijqJ+r2jl3Nmx3U7JlKWkX1T NKJCpnxSJhCEkpCZv53Bh6YT0w7F9+JshyHe7Ceu5zUD2MOsQvjc1MiPwhd/kXA6 YWLNWd1IvcSQNbOHlJC1MisK+zc6rnETkgG5oAaVzLBnEKMcOkHCXUQnIXNPDvKg cJWWIhFX7B6L6lWY0j8+Xu8ifopNc31Hl724JL8NQ7JPSUU0ZcAhMLjc7z3A== 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= 1761046339; x=1761132739; bh=80zLqkfCrolEagEKvS6VVM/10YgfSyxxQBs FnLVS1io=; b=rj2C4cMVhNOj6CFNk6utCyAee23BU0ejxu9rO2yqs43g4qJP34a m2sQknjQpgzSqUPEkv/6V+/IQPqOdGZLEerxLnzXRfCOdJRaE0ShT+NZOnz3wlFl H0zqyJMOaraCIry/SO4BZxUeSgmfRpsW6HfTrztnjI1iY4hpNCI/n4EyIIm8QrLC pWcOzS/YtsVdO9l9aJJG4YJKntKOYEZ821VWo9VErDIa13BxXxcgQr3ZCO4h8Tcn /zz9MKv1SzUHnXX6yTR4rv6SrgX79ImP6E268+LyHlw0xpxi+ADZueaSBSKQDLS8 /qGjqfYwgMggWzoqZvAkQT60v3ZjCi4gQHg== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeeffedrtdeggddugedtheelucetufdoteggodetrf 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 8D1A31820054; Tue, 21 Oct 2025 07:32:18 -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 13:31:56 +0200 To: internals@lists.php.net Message-ID: In-Reply-To: <0e4e39d6-9cc9-4970-92e0-2463143b4011@app.fastmail.com> References: <0e4e39d6-9cc9-4970-92e0-2463143b4011@app.fastmail.com> Subject: Re: [PHP-DEV] PHP True Async RFC Stage 4 Content-Type: multipart/alternative; boundary=a5af5244273042b88a1c3be67abb707c From: rob@bottled.codes ("Rob Landers") --a5af5244273042b88a1c3be67abb707c Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable On Tue, Oct 21, 2025, at 08:46, Rob Landers wrote: > 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 a= s 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 shutd= own_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-a= sync-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 >=20 > Hey Edmond, >=20 > I'm not quite finished notating the whole thing, but let's start with = this: >=20 > AWAITABLE >=20 > There's something here that bothers me. The RFC says: >=20 >> 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 o= f state changes. >> In the general case, objects implementing the `Awaitable` interface c= an act as triggers =E2=80=94 that is, they can change their state an unl= imited number of times. This means that multiple calls to `await ` may produce different results. >=20 > But then coroutines say: >=20 >> *Coroutines behave like Futures:* >> once a coroutine completes (successfully, with an exception, or throu= gh cancellation), >> it preserves its final state. >> Multiple calls to `await()` on the same coroutine will always return = the same result or >> throw the same exception. >=20 > 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 mak= e a couple subtypes: Signal and Future. Coroutines become Future that on= ly await once, while Signal is something that can be awaited many times. >=20 > It probably won't change much from the C point of view, but it would c= hange how we implement them in general libraries: >=20 > if ($awaitable instanceof Trigger) { > // throw or maybe loop over the trigger value? > } >=20 > CANCELLATIONS >=20 > The RFC says: >=20 >> In the context of coroutines, it is not recommended to use `catch \Th= rowable` or `catch CancellationError`. >=20 > But the example setChildScopeExceptionHandler does exactly this! Furth= er, much framework/app code uses the $previous to wrap exceptions as the= y bubble up, so it might be nice to have an Async\isCancellation(Throwab= le): bool function that can efficiently walk the exception chain and tel= l us if any cancellation was involved. >=20 > Minor nit: in the Async\protect section, it would be nice to say that = cancellations being AFTER the protect() are guaranteed, and also specify= reentry/nesting of protect(). Like what happens here: >=20 > Async\protect(foo(...)); // foo also calls protect() >=20 > And for reentrancy, foo() -> bar() -> foo() -> bar() and foo() calls p= rotect(). >=20 > Which one gets the cancellation? I also think that calling it a "criti= cal section" is a misnomer, as that traditionally indicates a "lock" (on= ly one thread can execute that section at a time) and not "this can't be= cancelled". >=20 > Also, if I'm reading this correctly, a coroutine can mark itself as ca= nceled, yet run to completion; however anyone await()'ing it, will get a= CancellationException instead of the completed value? >=20 > DESTRUCTORS >=20 > Allowing destructors to spawn feels extremely dangerous to me (but pow= erful). These typically -- but not always -- run between the return stat= ement and the next line (typically best to visualize that as the "}" sin= ce it runs in the original scope IIRC). That could make it 'feel like' m= ethods/functions are hanging or never returning if a library abuses this= by suspending or awaiting something. >=20 > ZOMBIES >=20 > async.zombie_coroutine_timeout says 2 seconds in the text, but 5 secon= ds in the php.ini section. >=20 > The RFC says: >=20 >> Once the application is considered finished, zombie coroutines are gi= ven a time limit within which they must complete execution. If this limi= t is exceeded, all zombie coroutines are canceled. >=20 > What is defined as "application considered finished?" FrankenPHP worke= rs, for instance, don=E2=80=99t "finish" =E2=80=94 is there a way to rea= p zombies manually? >=20 > Then there is dispose() and disposeSafely(), it would be good to speci= fy ordering and finally/onFinally execution here. ie, in nested scopes, = does it go from inner -> outer scopes, in the order they are created? Wh= en does finally/onFinally execute in that context? >=20 > FIBERS >=20 > Fibers are proliferant in existing code. It would be a good idea to pr= ovide a few helpers to allow code to migrate. Maybe something like Async= \isEnabled() to know whether I should use fibers or not. >=20 > Nit: the error message has a grammatical error: "Cannot create a fiber= while **an** True Async is active" should be "Cannot create a fiber whi= le True Async is active"? >=20 > SHUTDOWN >=20 > Is there also a timeout on Phase 1 shutdown? Otherwise, if it is only = an exception, then this could hang forever. >=20 > EXCEPTION IDENTITY >=20 > The RFC says: >=20 >> Multiple calls to `await()` on the same coroutine will always return = the same result or >> throw the same exception. >=20 > 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 = exception before it gets to me? >=20 > TYPOS >=20 > - 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 = function. > - examples use sleep(), but don't clarify whether sleep() will be bloc= king or non-blocking. > - Sometimes you use AwaitCancelledException and other times Cancellati= onError. Which is it? >=20 > =E2=80=94 Rob For bike shedding purposes: It's also worth pointing out that "cancelled" is the British spelling, w= hile "canceled" is the American spelling. PHP is already inconsistent in= the docs/error messages, but I don't see any types/functions with eithe= r spelling. Generally, PHP tends to follow American spelling for the sta= ndard lib (color vs. colour, behavior vs behaviour, analyzes vs analyses= , initialize vs initialise, serialize vs serialise, etc). So, IMHO, we s= hould probably be using the american spelling here ... =E2=80=94 Rob --a5af5244273042b88a1c3be67abb707c Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable
On Tue, Oct = 21, 2025, at 08:46, Rob Landers wrote:
On Sun, Oct 5, 2025, at 07:23, Edmond Dantes wro= te:
Good day= , everyone. I hope you're doing well.

I=E2=80=99= m 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 co= ntributed as well.
Many thanks to everyone for your input!


**What ha= s changed in this version?**

The RFC has been s= ignificantly simplified:

1. Components (such as= TaskGroup) that can be discussed in separate
RFCs have been r= emoved from the current one.
2. Coroutines can now be created = anywhere =E2=80=94 even inside shutdown_function.
3. Added Mem= ory Management and Garbage Collection section

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

During = this time, I managed to refactor and optimize the TrueAsync
co= de, which showed promising performance results in I/O scenarios.

A test integration between **NGINX UNIT** and the **Tr= ueAsync API**
was implemented to evaluate the possibility of u= sing PHP as an
asynchronous backend for a web server:
https://github.com/EdmondDantes/nginx-unit/tree/true= -async/src/true-async-php

During this time,= the project has come very close to beta 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, thi= s time I hope to
proceed with a vote.

Wishing you all a great day, and thank you for your feedback!

Hey Edmond,

I'm not quite finished notating the whole thing, but let's star= t with this:

AWAITABLE

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

he Aw= aitable interface is a contract that allows objects to be us= ed in the await expression.
The Await= able interface does not impose limitations on the number of = state changes.
In the general case, objects 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 p= roduce different results.

But then= coroutines say:

C= oroutines behave like Futures:
once a coroutine completes (su= ccessfully, with an exception, or through cancellation),
it = preserves its final state.
Multiple calls to <= code style=3D"border-top-width:1px;border-right-width:1px;border-bottom-= width:1px;border-left-width:1px;border-top-style:solid;border-right-styl= e:solid;border-bottom-style:solid;border-left-style:solid;border-top-col= or:rgb(204, 204, 204);border-right-color:rgb(204, 204, 204);border-botto= m-color:rgb(204, 204, 204);border-left-color:rgb(204, 204, 204);border-i= mage-source:initial;border-image-slice:initial;border-image-width:initia= l;border-image-outset:initial;border-image-repeat:initial;border-top-lef= t-radius:3px;border-top-right-radius:3px;border-bottom-right-radius:3px;= border-bottom-left-radius:3px;background-image:initial;background-positi= on-x:initial;background-position-y:initial;background-size:initial;backg= round-repeat:initial;background-attachment:initial;background-origin:ini= tial;background-clip:initial;background-color:rgb(246, 246, 246);font-fa= mily:menlo, consolas, monospace;font-size:90%;padding-top:1px;padding-ri= ght:3px;padding-bottom:1px;padding-left:3px;">await() on the same= coroutine will always return the same result or
throw the= same exception.

This seems= a bit contradictory and confuses things. When I await(), do I need to d= o it in a loop, or just once? It might be a good idea to make a couple s= ubtypes: Signal and Future. Coroutines become Future that only await onc= e, while Signal is something that can be awaited many times.
<= br>
It probably won't change much from the C point of view, bu= t it would change 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 recomme= nded to use c= atch \Throwable or catch CancellationError.

But t= he 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 cancellation= s being AFTER the protect() are guaranteed, and also specify reentry/nes= ting of protect(). Like what happens here:

Asyn= c\protect(foo(...)); // foo also calls protect()

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

Which one gets the cancellat= ion? I also think that calling it a "critical 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 cancelled".

Also, if I'm reading this correctly, a coroutine can mark itself= as canceled, yet run to completion; however anyone await()'ing it, will= get a CancellationException instead of the completed value?
<= br>
DESTRUCTORS

Allowing destructors = to spawn feels extremely dangerous to me (but powerful). These typically= -- but not always -- run between the return statement and the next line= (typically best to visualize that as the "}" since it runs in the origi= nal scope IIRC). That could make it 'feel like' methods/functions are ha= nging or never returning if a library abuses this by suspending or await= ing something.

ZOMBIES

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

The RFC says:

Once the application is considered finishe= d, zombie coroutines are given a time limit within which they must compl= ete execution. If this limit is exceeded, all zombie coroutines are canc= eled.

What is defined as "a= pplication 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 wou= ld be good to specify ordering and finally/onFinally execution here. ie,= in nested scopes, does it go from inner -> outer scopes, in the orde= r they are created? When does finally/onFinally execute in that context?=

FIBERS

Fibers are pro= liferant in existing code. It would be a good idea to provide a few help= ers to allow code to migrate. Maybe something like Async\isEnabled() to = know whether I should use fibers or not.

Nit: t= he error message has a grammatical error: "Cannot create a fiber while an True Async is active" should be "Cannot create a fiber while True Asyn= c is active"?

SHUTDOWN

Is there also a timeout on Phase 1 shutdown? Otherwise, if it is only a= n exception, then this could hang forever.

EXCE= PTION IDENTITY

The RFC says:

Multiple calls to await() on the same coroutine = will always return the same result or
throw the same exception.

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

TYPOS

- fix `file_get_co= ntent` to `file_get_contents` in examples.
- I think get_last_= error() should be error_get_last()?
- you call "suspend" a key= word in several places but it is actually a function.
- exampl= es use sleep(), but don't clarify whether sleep() will be blocking or no= n-blocking.
- Sometimes you use AwaitCancelledException and ot= her times CancellationError. Which is it?

=E2=80=94 Rob

For bike shedding purposes:

It's also worth = pointing out that "cancelled" is the British spelling, while "canceled" = is the American spelling. PHP is already inconsistent in the docs/error = messages, but I don't see any types/functions with either spelling. Gene= rally, PHP tends to follow American spelling for the standard lib (color= vs. colour, behavior vs behaviour, analyzes vs analyses, initialize vs = initialise, serialize vs serialise, etc). So, IMHO, we should probably b= e using the american spelling here ...

=E2=80=94 Rob
--a5af5244273042b88a1c3be67abb707c--