Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:128906 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 51D0B1A00BC for ; Wed, 22 Oct 2025 17:05:52 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1761152757; bh=tbWLhx2Sn/1fHwdQ+Ad8G5wfwxtGSg0QNwKxhq8b02Q=; h=Date:From:To:Cc:In-Reply-To:References:Subject:From; b=Y0BjxPjGiMMrIL1h4m2AegJTTKTDDgLB9Z76TaGBQzJNNmY0slx78xfuOGF8M6J0Y sluRO2vSLq8fRiRG5Dtg3Q+uBMqFn7F79CTIKeWe2yjyWmPCihW8TtUhjpQh9UhVU5 l13eFwv03EMtotRKLNbawzvAomx1fq+XUHW24vBqnZ3kTkkoziKAbfKVbJv/7beQJ1 EceImDI6uNPVOkXvlrpp7bLM4VrEgaQBLT0OjEPYX+zm75W3a1MpP5rsq5ilUs0KPv CIuC0D4+HcH1uAYem/v7KCq1+GRegjnwzw3IyBZ4PyJ6xIBRwlo11M/E8HAIstvibj GI8w7zSDPeiLw== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id C003E180382 for ; Wed, 22 Oct 2025 17:05:55 +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.1 required=5.0 tests=BAYES_50,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_MISSING,HTML_MESSAGE, RCVD_IN_DNSWL_LOW,RCVD_IN_MSPIKE_H2,SPF_HELO_PASS,SPF_PASS autolearn=no autolearn_force=no version=4.0.1 X-Spam-Virus: No X-Envelope-From: Received: from fhigh-a2-smtp.messagingengine.com (fhigh-a2-smtp.messagingengine.com [103.168.172.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 ; Wed, 22 Oct 2025 17:05:55 +0000 (UTC) Received: from phl-compute-05.internal (phl-compute-05.internal [10.202.2.45]) by mailfhigh.phl.internal (Postfix) with ESMTP id 08EBC14001F3; Wed, 22 Oct 2025 13:05:50 -0400 (EDT) Received: from phl-imap-05 ([10.202.2.95]) by phl-compute-05.internal (MEProxy); Wed, 22 Oct 2025 13:05:50 -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=1761152750; x= 1761239150; bh=tbWLhx2Sn/1fHwdQ+Ad8G5wfwxtGSg0QNwKxhq8b02Q=; b=W h7kifAwRGQtKdAuG+pljtl63CYtMCk5bH+B3+kWj0hrYgdpbEi7Bk1u8Hgz5lGtw pr3Ugrn4COmzJy0/IeP4nk4sYX032jSk4lcLk0/ow8dFEmIvtrM9KKQYLoqThIYP Zjsq9ORdhSY5jlb2iBhu7s3y+oX5y76cEWdAofe53FQvhH/PDHwbr1SPj5Dhmkxj DZvv8JD7AYSPDjAblNxpdA3U38Tv1uuFLVtsl30NcoRhQ1Dmeyghct6m1PzEFy0h 1JJ5HCgl5a0Mq66PTibkGOiptGdyo67xyju6eaE75mcW9NX7k0JwkzOsV3i2talz G0xuohYmoTtDkZ7J6WprA== 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= 1761152750; x=1761239150; bh=tbWLhx2Sn/1fHwdQ+Ad8G5wfwxtGSg0QNwK xhq8b02Q=; b=lFQnGS0pOdC5tD++eybYLbpb5WhpW3plCviCBf16Lll/KcWhFas EWR22HjbHo2Eb9dR8IHRyNHG1LafJxSzuYU5nWYWWWiBY3ZubxiCOwBDv9qziYYj f+rB/GUb7DLd4ibDFwpxyGu+l5Wl9X+6Lz7O5Lspt6Czu0gkcmxGT1VTqkIMWWLZ 7dcITe8XQbiSN2BmpDunkRwa8zGpn8SRnTLNPZe1AjnYaSt4eEHAtfpSy5P7pW/F um6TdIb1o5KtzaOcMfy4gUVcEDca7ZYHlz37e6tNYySTAkaNxDhuHUwVO9q+scuI 1Czm/VysRxdS7RxixGIFXx4lLpxxtW6IBMA== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeeffedrtdeggddugeegudegucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfurfetoffkrfgpnffqhgenuceu rghilhhouhhtmecufedttdenucesvcftvggtihhpihgvnhhtshculddquddttddmnecujf gurhepofggfffhvfevkfgjfhfutgesrgdtreerredtjeenucfhrhhomhepfdftohgsucfn rghnuggvrhhsfdcuoehrohgssegsohhtthhlvggurdgtohguvghsqeenucggtffrrghtth gvrhhnpeeiueethedvvdefjefhgfeiheelheehtdfhfeekjefflefgvedvkeduteejjedt tdenucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmhepmhgrihhlfhhrohhmpehroh gssegsohhtthhlvggurdgtohguvghspdhnsggprhgtphhtthhopeefpdhmohguvgepshhm thhpohhuthdprhgtphhtthhopegvughmohhnugdrhhhtsehgmhgrihhlrdgtohhmpdhrtg hpthhtohepihhnthgvrhhnrghlsheslhhishhtshdrphhhphdrnhgvthdprhgtphhtthho pegrrghrohhnsehtrhhofihskhhirdgtohhm X-ME-Proxy: Feedback-ID: ifab94697:Fastmail Received: by mailuser.phl.internal (Postfix, from userid 501) id 63E061820054; Wed, 22 Oct 2025 13:05:49 -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: Wed, 22 Oct 2025 19:05:29 +0200 To: "Edmond Dantes" Cc: "Aaron Piotrowski" , "PHP Internals" Message-ID: <8c20e8cc-cec7-4aa5-b6a7-663ef8159e1a@app.fastmail.com> In-Reply-To: References: <0e4e39d6-9cc9-4970-92e0-2463143b4011@app.fastmail.com> <37180d8d-85b4-49a3-a672-334bf4329470@app.fastmail.com> <2f8524a7-dea2-4fbf-933a-c538d3706253@app.fastmail.com> <151800a7-1094-49bc-8e43-c593a74741af@app.fastmail.com> <772a457f-69b6-4a76-8224-081917d719f6@app.fastmail.com> Subject: Re: [PHP-DEV] PHP True Async RFC Stage 4 Content-Type: multipart/alternative; boundary=237b15f60a91441d82a4c309023f4ea3 From: rob@bottled.codes ("Rob Landers") --237b15f60a91441d82a4c309023f4ea3 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable On Wed, Oct 22, 2025, at 17:30, Edmond Dantes wrote: > > This isn't even the same example. We're not talking about type juggl= ing, but an interface. Mixed is not an object nor an interface. > Why? > Type and Interface are contracts. >=20 > > The "FutureLike" type is exactly what I'm arguing for! >=20 > I have nothing against this interface. My point is different: > 1. Should the await and awaitXX functions accept only Future? > 2. Should Awaitable be hidden from the PHP userland? >=20 > > foreach($next =3D await($awaitable)) { } > What=E2=80=99s the problem here? The object will return a result. > If the result is iterable, it will go into the foreach loop. An > absolutely normal situation. Assuming an awaitable is idempotent (such as the result of a coroutine),= this is an infinite loop. There=E2=80=99s a 99.9% chance that=E2=80=99s= unintended, which is part of the point of static analysis. > > error: multiple usages of multi-shot Awaitable; you may get a differ= ent result on each invocation of await() > Why can=E2=80=99t you call await twice? What=E2=80=99s illegal about i= t? > If it=E2=80=99s a Future, you=E2=80=99ll get the previous result; if i= t=E2=80=99s an > Awaitable, you=E2=80=99ll get the second value. > But the correctness of the code here 100% depends on the programmer=E2= =80=99s intent. There=E2=80=99s no world this would be intentional to get two separate v= alues in two separate places, especially concurrently, except in very ra= re circumstances. You can=E2=80=99t guarantee ordering, and I can=E2=80=99= t imagine wanting to interleave results across multiple code paths. Well= , I can, but that feels like a very brittle system and a pita to maintai= n/debug. >=20 > > The RFC doesn't spell this out and needs some work here. > I don=E2=80=99t understand what exactly isn=E2=80=99t specified in the= RFC? >=20 > > When I say "violations" I mean that, assuming $a and $b resolve inst= antly: > > awaitAll([$a, $b]) !=3D=3D [await($a), await($b)] > > ... >=20 > This is a logical error. =D0=A1ircular reasoning. The desired behavior= is > being used here as proof of the undesired one. (recursion) > In the general case, these equations should not work =E2=80=94 and tha= t=E2=80=99s > normal if the code expects entities that are not Future, but, for > example, channels. Ok. If it=E2=80=99s circular reasoning, then what=E2=80=99s the definiti= on of awaitAll? How do we explain it to junior devs, what guarantees can= we make from it? > > This is my mistake though. The discussion about await() is all mixed= up with coroutines so its hard to tell what the actual behaviour for aw= ait() is outside of the context of coroutines. > > The violation I thought of only applies to coroutines, the actual be= haviour is "undefined" in the RFC. >=20 > `await()` does two things: > 1. Puts the coroutine into a waiting state. > 2. Wakes it up when the `Awaitable` object emits an event. >=20 > `awaitAny` / `awaitAll` do the same but for a list of objects or an it= erator. The RFC says that for *coroutines, *but not *Awaitable.* It is undefined. >=20 > However, the result of `await()` depends on the object being awaited. > Thus, there are two separate contracts here: > 1. **The waiting contract** =E2=80=94 how the waiting occurs. > 2. **The result contract** =E2=80=94 how the result is obtained. >=20 > From a language design perspective, there should be an explicit > representation for these contracts. > Something similar appeared only in Swift (e.g., `try await`). >=20 > So, from the standpoint of =E2=80=9Cperfect design,=E2=80=9D this is a= simplification, > which makes it not ideal. >=20 > I can remove the `Awaitable` interface from the `RFC` and replace it > with `FutureLike`. It costs nothing to do so. > But before doing that, I want to be at least 95% sure that in 2=E2=80=933 > years we won=E2=80=99t have to bring it back or add workarounds. Bringing it back is much much much easier than taking it away. >=20 > > It has everything to do with it! If a multi-shot Awaitable keeps emi= tting, this causes repeated wakeups to the same continuation. It has to = schedule this every time and there isn't a way to say "don't > produce u= ntil consumed". You can basically starve other tasks from executing. How= ever, if you use iterables of single-shot Awaitables, you get backpressu= re "for free" and don't request the next > > future until the previous one is complete, otherwise, the buffering = pressure lands in the awaitable (unbounded memory) or in the schedulers = ready queue (which affects fairness). Further, when > > cancelling a multi-shot awaitable, should the scheduler drop pending= emissions and what happens if it keeps re-enqueing it anyway? It makes = the scheduler far more complicated than it needs to > be! >=20 > If an Awaitable object isn=E2=80=99t being awaited, it can=E2=80=99t e= mit events, nor > can it interfere with the scheduler. > Unless the programmer explicitly creates new coroutines for every even= t by hand. > If an Awaitable has a lot of data and wakes up 1000 coroutines, then > the scheduler will ensure that all those coroutines get executed. > But until these 1000 coroutines finish processing the data, the > Awaitable cannot wake anyone else. > So there is a natural limit to the number of subscribers. it can only > be exceeded through explicitly incorrect handling of coroutines. >=20 > Because the scheduler=E2=80=99s algorithm is extremely simple, it has = little > to no starvation issues. > Starvation problems usually arise in schedulers that use more > =E2=80=9Cintelligent=E2=80=9D approaches. You=E2=80=99re making some assumptions: 1. That the scheduler will always be cooperative. 2. That the scheduler you implemented as a proof-of-concept will be the= one shipped. There=E2=80=99ll come a day where someone will want to write a preemptiv= e coroutine scheduler, or even a multi-threaded scheduler. That might be= in 20=E2=80=9330 years, or next year; we don=E2=80=99t know. But if the= y do, having Awaitables multi-shot *will* prevent that from being a real= istic PR. The amount of complexity required for such an implementation w= ould be huge. >=20 > > A footgun is any reasonable looking code that subtly breaks because = the contract is weak, not because the programmer didn't RTFM. > The code cannot be considered =E2=80=9Creasonable,=E2=80=9D because in= this case the > programmer is clearly violating the contract. > And not because the contract is complex or confusing, but because they > completely ignored it. This doesn=E2=80=99t look like a footgun. >=20 At this point, we=E2=80=99re debating philosophy, but according to the R= FC, coroutines will be entirely safe to handle this way, but not ALL awa= itables. *That=E2=80=99s what makes it a footgun.* Sometimes, it=E2=80=99= s reasonable, and it works ... and in certain cases, it doesn=E2=80=99t = work at all. Nobody can tell by simply reading the code in a code review= unless they had been shot in the foot before. =E2=80=94 Rob --237b15f60a91441d82a4c309023f4ea3 Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable
On Wed, Oct = 22, 2025, at 17:30, Edmond Dantes wrote:
> This isn't even the same example. We're n= ot talking about type juggling, but an interface. Mixed is not an object= nor an interface.
Why?
Type and Interface are contr= acts.

> The "FutureLike" type is exactly wha= t I'm arguing for!

I have nothing against this = interface. My point is different:
1. Should the await and awai= tXX functions accept only Future?
2. Should Awaitable be hidde= n from the PHP userland?

> foreach($next =3D= await($awaitable)) { }
What=E2=80=99s the problem here? The o= bject will return a result.
If the result is iterable, it will= go into the foreach loop. An
absolutely normal situation.

Assuming an awaitable is idempotent (= such as the result of a coroutine), this is an infinite loop. There=E2=80= =99s a 99.9% chance that=E2=80=99s unintended, which is part of the poin= t of static analysis.

> error: multiple usages of multi-shot Awaitable= ; you may get a different result on each invocation of await()
Why can=E2=80=99t you call await twice? What=E2=80=99s illegal about it= ?
If it=E2=80=99s a Future, you=E2=80=99ll get the previous re= sult; if it=E2=80=99s an
Awaitable, you=E2=80=99ll get the sec= ond value.
But the correctness of the code here 100% depends o= n the programmer=E2=80=99s intent.

There=E2=80=99s no world this would be intentional to get two separate = values in two separate places, especially concurrently, except in very r= are circumstances. You can=E2=80=99t guarantee ordering, and I can=E2=80= =99t imagine wanting to interleave results across multiple code paths. W= ell, I can, but that feels like a very brittle system and a pita to main= tain/debug.


> The RFC doesn't spell this out and needs= some work here.
I don=E2=80=99t understand what exactly isn=E2= =80=99t specified in the RFC?

> When I say "= violations" I mean that, assuming $a and $b resolve instantly:
> awaitAll([$a, $b]) !=3D=3D [await($a), await($b)]
> .= ..

This is a logical error. =D0=A1ircular reaso= ning. The desired behavior is
being used here as proof of the = undesired one. (recursion)
In the general case, these equation= s should not work =E2=80=94 and that=E2=80=99s
normal if the c= ode expects entities that are not Future, but, for
example, ch= annels.

Ok. If it=E2=80=99s circul= ar reasoning, then what=E2=80=99s the definition of awaitAll? How do we = explain it to junior devs, what guarantees can we make from it?

> Thi= s is my mistake though. The discussion about await() is all mixed up wit= h coroutines so its hard to tell what the actual behaviour for await() i= s outside of the context of coroutines.
> The violation I t= hought of only applies to coroutines, the actual behaviour is "undefined= " in the RFC.

`await()` does two things:
<= div>1. Puts the coroutine into a waiting state.
2. Wakes it up= when the `Awaitable` object emits an event.

`a= waitAny` / `awaitAll` do the same but for a list of objects or an iterat= or.

The RFC says that for = coroutines, but not Awaitable. It is undefined.


However, the result of `await()` depends on the object being awai= ted.
Thus, there are two separate contracts here:
1.= **The waiting contract** =E2=80=94 how the waiting occurs.
2.= **The result contract** =E2=80=94 how the result is obtained.

From a language design perspective, there should be an e= xplicit
representation for these contracts.
Somethin= g similar appeared only in Swift (e.g., `try await`).

So, from the standpoint of =E2=80=9Cperfect design,=E2=80=9D this= is a simplification,
which makes it not ideal.

=
I can remove the `Awaitable` interface from the `RFC` and rep= lace it
with `FutureLike`. It costs nothing to do so.
But before doing that, I want to be at least 95% sure that in 2=E2=80=93= 3
years we won=E2=80=99t have to bring it back or add workarou= nds.

Bringing it back is much much= much easier than taking it away.


> It has everything t= o do with it! If a multi-shot Awaitable keeps emitting, this causes repe= ated wakeups to the same continuation. It has to schedule this every tim= e and there isn't a way to say "don't > produce until consumed". You = can basically starve other tasks from executing. However, if you use ite= rables of single-shot Awaitables, you get backpressure "for free" and do= n't request the next
> future until the previous one is com= plete, otherwise, the buffering pressure lands in the awaitable (unbound= ed memory) or in the schedulers ready queue (which affects fairness). Fu= rther, when
> cancelling a multi-shot awaitable, should the= scheduler drop pending emissions and what happens if it keeps re-enquei= ng it anyway? It makes the scheduler far more complicated than it needs = to > be!

If an Awaitable object isn=E2=80=99= t being awaited, it can=E2=80=99t emit events, nor
can it inte= rfere with the scheduler.
Unless the programmer explicitly cre= ates new coroutines for every event by hand.
If an Awaitable h= as a lot of data and wakes up 1000 coroutines, then
the schedu= ler will ensure that all those coroutines get executed.
But un= til these 1000 coroutines finish processing the data, the
Awai= table cannot wake anyone else.
So there is a natural limit to = the number of subscribers. it can only
be exceeded through exp= licitly incorrect handling of coroutines.

Becau= se the scheduler=E2=80=99s algorithm is extremely simple, it has little<= /div>
to no starvation issues.
Starvation problems usually= arise in schedulers that use more
=E2=80=9Cintelligent=E2=80=9D= approaches.

You=E2=80=99re making= some assumptions:
  1. That the scheduler will always be cooper= ative.
  2. That the scheduler you implemented as a proof-of-concept = will be the one shipped.
There=E2=80=99ll come a day w= here someone will want to write a preemptive coroutine scheduler, or eve= n a multi-threaded scheduler. That might be in 20=E2=80=9330 years, or n= ext year; we don=E2=80=99t know. But if they do, having Awaitables multi= -shot will prevent that from being a realistic PR. The amoun= t of complexity required for such an implementation would be huge.
=


<= /div>
> A footgun is any reasonable looking code that subtly brea= ks because the contract is weak, not because the programmer didn't RTFM.=
The code cannot be considered =E2=80=9Creasonable,=E2=80=9D b= ecause in this case the
programmer is clearly violating the co= ntract.
And not because the contract is complex or confusing, = but because they
completely ignored it. This doesn=E2=80=99t l= ook like a footgun.


At this point, we=E2=80=99re debating philosophy, but according to the = RFC, coroutines will be entirely safe to handle this way, but not ALL aw= aitables. That=E2=80=99s what makes it a footgun. Sometimes,= it=E2=80=99s reasonable, and it works ... and in certain cases, it does= n=E2=80=99t work at all. Nobody can tell by simply reading the code in a= code review unless they had been shot in the foot before.
=E2=80=94 Rob
--237b15f60a91441d82a4c309023f4ea3--