Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:128882 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 0657A1A00BC for ; Tue, 21 Oct 2025 15:03:16 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1761059001; bh=uMCv1hYPxdyU0CQiiK3fI5Rfq7JkJ1SsiCdyvuEDFbw=; h=Date:From:To:Cc:In-Reply-To:References:Subject:From; b=NL6q8v/b5xt5JerlCQvQcrEZ077KZzBjSFquCs+1m7lW4WerurDoJqlgdYVSOFNrX MCtFE1ygkgJQp7bxms7Ej5/ZAAQalqqTFM31AXwd73HsQiBNcsiwgIBB6nuUvBd7d8 VgdxWWaJ3sZzSCVsF8hs/FO28B0IGpHADGo/3N7G0WvEnso3it86yXSKnGw45E+aPD uauyG0pH4rzuDFX4SDPdYCzS2ZKE75O7KiZ/mjkz/vHbPPNhLI/9Fu0WRkwIn8Ythu IVsEfslqED0PXdWrbDxgJ/sefXXm08NnqpocDUzD1aNgMeexfAg1fmBL/BwpoC+DST EmqvPg0DFOLjA== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id CE18B1801DD for ; Tue, 21 Oct 2025 15:03:19 +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 fout-a6-smtp.messagingengine.com (fout-a6-smtp.messagingengine.com [103.168.172.149]) (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 15:03:19 +0000 (UTC) Received: from phl-compute-05.internal (phl-compute-05.internal [10.202.2.45]) by mailfout.phl.internal (Postfix) with ESMTP id 1332CEC0074; Tue, 21 Oct 2025 11:03:14 -0400 (EDT) Received: from phl-imap-05 ([10.202.2.95]) by phl-compute-05.internal (MEProxy); Tue, 21 Oct 2025 11:03:14 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bottled.codes; h=cc: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=1761058994; x= 1761145394; bh=WaKuJSLyJFF01bqbtng8SA7F7wjmB7jqUgGxFc5NgzU=; b=N JvnU/JZfWDIn5uwmPOWHwESBUeVKAgbxtqcRRWpNk/Vet9Po9QXJIYCjf4VIJ11A 8mFixxu9g70ugH/B0bX53eHlTup3g0zv6cPdBRIsnqvRcW7Ifu2SFT37IQ0BK4Yr Fl/1vtTIiZUgCzStLUqBDkdJN+/WXe+gSc1Etxgxx5Md1RML+ie6GqX1nGB0aJvb 5+0LXjfqSj7hjWg1VtgSYlyyG/3AjpHYBpodtMlaa9q1bikdsDyYrD72yO4EN+WC J/JoK7vqaeLoirOzZOBQcT0xH2490LDWI4k0VD+aYiHt6gE7CgAEp6yMdUHefd2f Ly6ulvp0Pjdqc0MAQS4wQ== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc: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= 1761058994; x=1761145394; bh=WaKuJSLyJFF01bqbtng8SA7F7wjmB7jqUgG xFc5NgzU=; b=B/uxoc4zlJ4oLyoXXEXhFY3zmaNO4iloUgiyo5f6q9vhVfV2S77 a5pj4FC9jta+QqB4mRVsPZDpwlwPfX4X5imwVtrTtB3GBtSN5fJ0BTfZq8/hASZt hz/gVhfIwKrRBkO9kH2vYoNIKWoQ5UIIQK9OfoO+aH7pNKedg1o84AWKgxzrOHL4 +ndHN4R5rxeNTQx6r3ZT0yq81XjyOqPmHhAUJtYyTr9TNldDjYfls4NifVrN05C4 wmDWKiuFE2JjO3AJWybL2WogWJURZO4pW3l2ObKepeIyqVfWLqjRfnvw2EN2oqYC 4+vjd+frPbaLLdLYIHQrHw58S5nZz5BoVYA== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeeffedrtdeggddugedutdduucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfurfetoffkrfgpnffqhgenuceu rghilhhouhhtmecufedttdenucenucfjughrpefoggffhffvvefkjghfufgtsegrtderre ertdejnecuhfhrohhmpedftfhosgcunfgrnhguvghrshdfuceorhhosgessghothhtlhgv ugdrtghouggvsheqnecuggftrfgrthhtvghrnhepffegveevgedtfffgffdvffdtvdehue elgeevkeetffeuleeitdegkeejtedtteeknecuffhomhgrihhnpehphhhprdhnvghtnecu vehluhhsthgvrhfuihiivgeptdenucfrrghrrghmpehmrghilhhfrhhomheprhhosgessg hothhtlhgvugdrtghouggvshdpnhgspghrtghpthhtohepvddpmhhouggvpehsmhhtphho uhhtpdhrtghpthhtohepvggumhhonhgurdhhthesghhmrghilhdrtghomhdprhgtphhtth hopehinhhtvghrnhgrlhhssehlihhsthhsrdhphhhprdhnvght X-ME-Proxy: Feedback-ID: ifab94697:Fastmail Received: by mailuser.phl.internal (Postfix, from userid 501) id 848871820054; Tue, 21 Oct 2025 11:03:13 -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 17:02:51 +0200 To: "Edmond Dantes" Cc: internals@lists.php.net Message-ID: In-Reply-To: References: <0e4e39d6-9cc9-4970-92e0-2463143b4011@app.fastmail.com> Subject: Re: [PHP-DEV] PHP True Async RFC Stage 4 Content-Type: multipart/alternative; boundary=a17ed135aa9449019bed667909b4e0cf From: rob@bottled.codes ("Rob Landers") --a17ed135aa9449019bed667909b4e0cf Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable On Tue, Oct 21, 2025, at 15:33, Edmond Dantes wrote: > > When I await(), do I need to do it in a loop, or just once? >=20 > It depends on what is being awaited. > On one hand, it would probably be convenient to have many different > operations for different cases, but then we make the language > semantics more complex. > > Coroutines become Future that only await once, while Signal is somet= hing that can be awaited many times. > At the moment, only such objects exist. It=E2=80=99s hard to say wheth= er there > will be others. > Although one can imagine an Interval object, there are some doubts > about whether such an object should be used in a while await loop, > because from a performance standpoint, it=E2=80=99s not very efficient. It might be good to clarify this when we talk about the Awaitable Interf= ace then? Maybe something like: "In PHP 8.6 the only awaitables are single-completion. Future versions m= ay add multi-event awaitables." just to clear it up for early adopters? > > But the example setChildScopeExceptionHandler does exactly this! > The Scope-level handler does not interfere with coroutine completion. > And it is not called because the cancellation exception is "absorbed" > by the coroutine. :thumbsup: > > Further, much framework/app code uses the $previous to wrap exceptio= ns as they bubble up, >=20 > If a programmer wants to wrap an exception in their own one let them. > No one forbids catching exceptions; they just shouldn=E2=80=99t be sup= pressed. >=20 > > Async\isCancellation(Throwable): bool > Why make a separate function if you can just walk through the chain? If everyone writes their own isCancellation() we risk divergence (not to= mention, it will be faster in C and basically need to be checked on eve= ry catch that might await); having one blessed function guarantees consi= stent detection and can allow for static-analysis support. >=20 > > Minor nit: in the Async\protect section, it would be nice to say tha= t cancellations being AFTER the protect() are guaranteed, and also speci= fy reentry/nesting of protect(). Like what happens here: >=20 > That=E2=80=99s a good case! Re-entering protect should be forbidden th= at must > not be allowed. <3 that's good to know! It definately needs to be in the RFC. If you don= 't mind me asking: why is this the case? >=20 > > 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? > If a coroutine is canceled, its return value will be ignored. > However, of course, it can still call return, and that will work > without any issues. > I considered issuing a warning for such behavior but later removed it, > since I don=E2=80=99t see it as particularly dangerous. > This point requires attention, because there=E2=80=99s a certain =E2=80= =9Cflexibility=E2=80=9D > here that can be confusing. However, the risk in this case is low. I would find it surprising behaviour -- if you cancel a context in go, i= t may or may not complete, but you get back both the completion (if it c= ompleted) and/or the error. In C#, it throws an exception and it never c= ompletes. Languages have different ways to do it, but it should be docum= ented in the RFC what the behaviour is and how to handle this case. Ergo= nomics matter as much as the feature existing. As far as observability goes, it might be a good idea to issue a notice = instead of a warning. Notice is often suppressed and rarely causes any i= ssues, but in development, seeing that would at least let me know someth= ing was going on that I should investigate. >=20 > > Allowing destructors to spawn feels extremely dangerous to me (but p= owerful). These typically -- but not always -- run between the return st= atement and the next line (typically best to visualize that > as the "}"= since it runs in the original scope IIRC). That could make it 'feel lik= e' methods/functions are hanging or never returning if a library abuses = this by suspending or awaiting something. >=20 > Launching coroutines in destructors is indeed a relatively dangerous > operation, but for different reasons mainly related to who owns such > coroutines. However, I didn=E2=80=99t quite understand what danger you= were > referring to? >=20 > Asynchronous operations, as well as coroutine launching, are indeed > used in practice. The code executes properly, so I don=E2=80=99t quite= see > what risks there could be, apart from potential resource leaks caused > by faulty coroutines. I think we missed each other here. Consider the following code: function test() { $r =3D new AsyncResource(); return 42; // destructor suspends here } Would this delay the caller's return until the destructor's coroutine fi= nished, or is it detached? If detached, can it interleave safely with su= bsequent code? This should be documented in the RFC so people can plan f= or it and use it appropriately (such as managing transactions or locks i= nside destructors). > > async.zombie_coroutine_timeout says 2 seconds in the text, but 5 sec= onds in the php.ini section. > Thanks. >=20 > > What is defined as "application considered finished?" FrankenPHP wor= kers, for instance, don=E2=80=99t "finish" =E2=80=94 is there a way to r= eap zombies manually? >=20 > The Scheduler keeps track of the number of coroutines being executed. > When the number of active coroutines reaches zero, the Scheduler stops > execution. Zombie coroutines are not counted among those that keep the > execution running. If PHP is running in worker mode, then the worker > code must correctly keep the execution active. But even workers > sometimes need to shutdown. I have some workers that haven't restarted since April. :) So, having a = way to manually reap zombies (much like we do with OS-level code when ru= nning as PID 1) and track them, would be nice to have. At least, as part= of the scheduler API. >=20 > > it would be good to specify ordering and finally/onFinally executio= n here > Doesn=E2=80=99t the RFC define the order of onFinally handler executio= n? > onFinally handlers are executed after the coroutine or the Scope has c= ompleted. > onFinally is not directly related to dispose() in any way. >=20 > When dispose() is called, coroutine cancellation begins. This process > may take some time. Only after the last coroutine has stopped will > onFinally be invoked. In other words, you should not attempt to link > the calls of these methods in any way. This should be documented on the RFC, it still doesn't explain what the = order of operations is though. This matters because if you are doing cle= anup during disposal, you need to know what things will still be around = (for reference, order of operations for GC is well documented and define= d https://www.php.net/manual/en/features.gc.collecting-cycles.php which = is what I'm expecting to see here). >=20 > > Maybe something like Async\isEnabled() to know whether I should use = fibers or not. > Good idea!, > considering that such a function actually exists at the C code level. >=20 > > Is this "same exception" mean this literally, or is it a clone? If i= t is the same, what prevents another code path from mutating the origina= l exception before it gets to me? > It=E2=80=99s the exact same object that is, a reference to the same in= stance. > So if someone modifies it, those changes will, of course, take effect. This should probably be documented in the RFC: "Exceptions and returned = objects are shared objects; mutating them is undefined behavior if there= are multiple awaiters." >=20 > > Is there also a timeout on Phase 1 shutdown? Otherwise, if it is onl= y an exception, then this could hang forever. >=20 > That=E2=80=99s true! A hang is indeed possible. I=E2=80=99m still not = sure whether > it=E2=80=99s worth adding an auxiliary mechanism to handle such cases,= because > that would effectively make PHP =E2=80=9Csmarter=E2=80=9D than the pro= grammer. I > believe that a language should not try to be smarter than the > programmer if the application runs in a certain way, then it=E2=80=99s > probably meant to be that way. I think of it more as observability than smarts (esp if the timeout is c= onfigurable) ... otherwise, how would you even know if it is hanging on = shutdown vs. doesn't even know it is supposed to be shutting down? I'm r= eminded of certain CLI tools that require me issuing a SIGTSTP (ctrl-z) = to issue a SIGTERM or SIGKILL because SIGINT (ctrl-c) doesn't appear to = work. If it is my program, is it that there is a bug with SIGINT handler= s -- or is it hanging during shutdown? Having a timeout there would at l= east protect me from my customers/users getting stuck due to a bug, and = I could always set the timeout to something infinite-ish (0? -1?) if tha= t is the behaviour I want. =E2=80=94 Rob --a17ed135aa9449019bed667909b4e0cf Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable
On Tue, Oct = 21, 2025, at 15:33, Edmond Dantes wrote:
> When I await(), do I need to do it in a l= oop, or just once?

It depends on what is being = awaited.
On one hand, it would probably be convenient to have = many different
operations for different cases, but then we mak= e the language
semantics more complex.
<= div>
> C= oroutines become Future that only await once, while Signal is something = that can be awaited many times.
At the moment, only such objec= ts exist. It=E2=80=99s hard to say whether there
will be other= s.
Although one can imagine an Interval object, there are some= doubts
about whether such an object should be used in a while= await loop,
because from a performance standpoint, it=E2=80=99= s not very efficient.

It might be = good to clarify this when we talk about the Awaitable Interface then? Ma= ybe something like:

"In PHP 8.6 the only awaita= bles are single-completion. Future versions may add multi-event awaitabl= es." just to clear it up for early adopters?

> But the example se= tChildScopeExceptionHandler does exactly this!
The Scope-level= handler does not interfere with coroutine completion.
And it = is not called because the cancellation exception is "absorbed"
by the coroutine.

:thumbsup:

>= Further, much framework/app code uses the $previous to wrap exceptions = as they bubble up,

If a programmer wants to wra= p an exception in their own one let them.
No one forbids catch= ing exceptions; they just shouldn=E2=80=99t be suppressed.

<= div>
> Async\isCancellation(Throwable): bool
= Why make a separate function if you can just walk through the chain?

If everyone writes their own isCancel= lation() we risk divergence (not to mention, it will be faster in C and = basically need to be checked on every catch that might await); having on= e blessed function guarantees consistent detection and can allow for sta= tic-analysis support.


> Minor nit: in the Async\protect= section, it would be nice to say that cancellations being AFTER the pro= tect() are guaranteed, and also specify reentry/nesting of protect(). Li= ke what happens here:

That=E2=80=99s a good cas= e! Re-entering protect should be forbidden that must
not be al= lowed.

<3 that's good to know! = It definately needs to be in the RFC. If you don't mind me asking: why i= s this the case?


> Also, if I'm reading this correctly= , a coroutine can mark itself as canceled, yet run to completion; howeve= r anyone await()'ing it, will get a CancellationException instead of the= completed value?
If a coroutine is canceled, its return value= will be ignored.
However, of course, it can still call return= , and that will work
without any issues.
I considere= d issuing a warning for such behavior but later removed it,
si= nce I don=E2=80=99t see it as particularly dangerous.
This poi= nt requires attention, because there=E2=80=99s a certain =E2=80=9Cflexib= ility=E2=80=9D
here that can be confusing. However, the risk i= n this case is low.

I would find i= t surprising behaviour -- if you cancel a context in go, it may or may n= ot complete, but you get back both the completion (if it completed) and/= or the error. In C#, it throws an exception and it never completes. Lang= uages have different ways to do it, but it should be documented in the R= FC what the behaviour is and how to handle this case. Ergonomics matter = as much as the feature existing.

As far as obse= rvability goes, it might be a good idea to issue a notice instead of a w= arning. Notice is often suppressed and rarely causes any issues, but in = development, seeing that would at least let me know something was going = on that I should investigate.


> Allowing destructors t= o 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 o= riginal scope IIRC). That could make it 'feel like' methods/functions ar= e hanging or never returning if a library abuses this by suspending or a= waiting something.

Launching coroutines in dest= ructors is indeed a relatively dangerous
operation, but for di= fferent reasons mainly related to who owns such
coroutines. Ho= wever, I didn=E2=80=99t quite understand what danger you were
= referring to?

Asynchronous operations, as well = as coroutine launching, are indeed
used in practice. The code = executes properly, so I don=E2=80=99t quite see
what risks the= re could be, apart from potential resource leaks caused
by fau= lty coroutines.

I think we missed = each other here. Consider the following code:

f= unction test() {
    $r =3D new AsyncResource();=
    return 42; // destructor suspends here
<= /div>
}

Would this delay the caller's retur= n until the destructor's coroutine finished, or is it detached? If detac= hed, can it interleave safely with subsequent code? This should be docum= ented in the RFC so people can plan for it and use it appropriately (suc= h as managing transactions or locks inside destructors).
<= br>
> <= a href=3D"http://async.zombie">async.zombie_coroutine_timeout says 2= seconds in the text, but 5 seconds in the php.ini section.
Thanks.

> = 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?

The Scheduler keeps track of = the number of coroutines being executed.
When the number of ac= tive coroutines reaches zero, the Scheduler stops
execution. Z= ombie coroutines are not counted among those that keep the
exe= cution running. If PHP is running in worker mode, then the worker
<= div>code must correctly keep the execution active. But even workers
sometimes need to shutdown.

= I have some workers that haven't restarted since April. :) So, having a = way to manually reap zombies (much like we do with OS-level code when ru= nning as PID 1) and track them, would be nice to have. At least, as part= of the scheduler API.


>  it would be good to speci= fy ordering and finally/onFinally execution here
Doesn=E2=80=99= t the RFC define the order of onFinally handler execution?
onF= inally handlers are executed after the coroutine or the Scope has comple= ted.
onFinally is not directly related to dispose() in any way= .

When dispose() is called, coroutine cancellat= ion begins. This process
may take some time. Only after the la= st coroutine has stopped will
onFinally be invoked. In other w= ords, you should not attempt to link
the calls of these method= s in any way.

This should be docum= ented on the RFC, it still doesn't explain what the order of operations = is though. This matters because if you are doing cleanup during disposal= , you need to know what things will still be around (for reference, = ;order of operations for GC is well documented and defined https://w= ww.php.net/manual/en/features.gc.collecting-cycles.php which is= what I'm expecting to see here).


> Maybe something lik= e Async\isEnabled() to know whether I should use fibers or not.
Good idea!,
considering that such a function actually exists= at the C code level.

> Is this "same except= ion" mean this literally, or is it a clone? If it is the same, what prev= ents another code path from mutating the original exception before it ge= ts to me?
It=E2=80=99s the exact same object that is, a refere= nce to the same instance.
So if someone modifies it, those cha= nges will, of course, take effect.

This should probably be documented in the RFC: "Exceptions and returned= objects are shared objects; mutating them is undefined behavior if ther= e are multiple awaiters."


> Is there also a timeou= t on Phase 1 shutdown? Otherwise, if it is only an exception, then this = could hang forever.

That=E2=80=99s true! A hang= is indeed possible. I=E2=80=99m still not sure whether
it=E2=80= =99s worth adding an auxiliary mechanism to handle such cases, because
that would effectively make PHP =E2=80=9Csmarter=E2=80=9D than = the programmer. I
believe that a language should not try to be= smarter than the
programmer if the application runs in a cert= ain way, then it=E2=80=99s
probably meant to be that way.

I think of it more as observability th= an smarts (esp if the timeout is configurable) ... otherwise, how would = you even know if it is hanging on shutdown vs. doesn't even know it is s= upposed to be shutting down? I'm reminded of certain CLI tools that requ= ire me issuing a SIGTSTP (ctrl-z) to issue a SIGTERM or SIGKILL because = SIGINT (ctrl-c) doesn't appear to work. If it is my program, is it that = there is a bug with SIGINT handlers -- or is it hanging during shutdown?= Having a timeout there would at least protect me from my customers/user= s getting stuck due to a bug, and I could always set the timeout to some= thing infinite-ish (0? -1?) if that is the behaviour I want.

=E2=80=94 Rob
--a17ed135aa9449019bed667909b4e0cf--