Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:129238 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 CF90E1A00BC for ; Sat, 15 Nov 2025 21:13:14 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1763241199; bh=XSb9HtK9UZNlqj6FXBTDYtVG8hq/YCPN7bY/Pmvo3+Q=; h=Date:From:To:Cc:In-Reply-To:References:Subject:From; b=O9MDuV6xaRniwrrudq5NYeNmW9m2Jo5bYCyKS7zRO1AIA/8mbKBjAfYaJ1rrC7Z8Y HfA3eQFDWdp0x33AF5lpM7vi28YA/Vum4g/1UOKPgJ1gWUpKIFgSmFxCrU3Q/VFGz0 i0OidsrXUAejcHPBxlrVJ2C3YqOF1xRavkzQ0FT/CdSK2L4ilU0gYQG7ZM+Z6rZbB/ e33frd5zvK4zxXp+0dbACJRhsNX/fL2Bl3yBXLEPUYrRJpqg1V+8eUsJKe7+IGvZ0X GUutOFcSp8KP/h4yPqoTZbLc4rWIrnLWjblSawAFPC5R8jaSNFb1XamEgbrlzsRyuB 5sYbHPBnmb6bw== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id EE73C1801DB for ; Sat, 15 Nov 2025 21:13:05 +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.6 required=5.0 tests=BAYES_50,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_MISSING,HTML_MESSAGE, SPF_HELO_PASS,T_SPF_TEMPERROR 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 ; Sat, 15 Nov 2025 21:13:05 +0000 (UTC) Received: from phl-compute-12.internal (phl-compute-12.internal [10.202.2.52]) by mailfhigh.stl.internal (Postfix) with ESMTP id 10D877A00E4; Sat, 15 Nov 2025 16:13:00 -0500 (EST) Received: from phl-imap-05 ([10.202.2.95]) by phl-compute-12.internal (MEProxy); Sat, 15 Nov 2025 16:13:00 -0500 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=fm1; t=1763241179; x= 1763327579; bh=NXZ+YZPKzWCCczcvhHt3HjXIcnXQGpXY03sms60To2Q=; b=Z 6mxtOkJG7Cmgg50bXM5pqInIKgoNwAbNWYrIOPmFEzQnHlWu6uA2l8vbNpF0ALs6 7rB1NQ4pSiN/tk+7Tr4ZmIW3N0XK157p7/5kusqk6+d3W2RRZypOSNm4vmF6zAs/ /YeM269b1I4qUeksU6v11zIqE3hUHb1rPSzqT/glhIesOecLvxPB7UTzOoeRSFOT AVK8da9aKT0b0guush7q4r8gcUWPLS8lDBQf3e9dKbRBnvga4rmGZKL/RPEVzvdN 29zA0UZuNOKFdE56YrFYUb0ywmme54tc2imvVp/AmOSTFgUySIrAXxDiSk/oYxHe kBiBodvDdUEzGrXkcO0xw== 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=fm3; t= 1763241179; x=1763327579; bh=NXZ+YZPKzWCCczcvhHt3HjXIcnXQGpXY03s ms60To2Q=; b=QULot+RUpxJ/Gjlwz4CQxz+Gb2YuZgcardrTz+RxJS9a66VBJiA EfE9X/+/Pp479klwCGhESTpUGS5oKWmM/4E3dGVWBNeObClSy2UcpI3gq2F5bdx9 a6lCdYP0JF6jhhoYrvsb6K+XQmmq0EiQxN0Gpoka3SqAcAGXcZrZyHiGEFFHxILp k0H/04vZSQ626w5veIfjcpxucciRf0Pjjst54aqGrdBu1Fs0IZYamHFAsAjLNlB1 n2/jg0MJSZJ0exQj1VFkSt+nV/CIea9B6kc6jKqWmXnVgdSA9U7pESAAk+dVs6ka 8nzt1RNOE7hq+/BQ44ZDzLHxIAQwdlCtM0A== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeeffedrtdeggddvudefjeegucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfurfetoffkrfgpnffqhgenuceu rghilhhouhhtmecufedttdenucesvcftvggtihhpihgvnhhtshculddquddttddmnecujf gurhepofggfffhvfevkfgjfhfutgesrgdtreerredtjeenucfhrhhomhepfdftohgsucfn rghnuggvrhhsfdcuoehrohgssegsohhtthhlvggurdgtohguvghsqeenucggtffrrghtth gvrhhnpeeiueethedvvdefjefhgfeiheelheehtdfhfeekjefflefgvedvkeduteejjedt tdenucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmhepmhgrihhlfhhrohhmpehroh gssegsohhtthhlvggurdgtohguvghspdhnsggprhgtphhtthhopeehpdhmohguvgepshhm thhpohhuthdprhgtphhtthhopehlrghrrhihsehgrghrfhhivghlughtvggthhdrtghomh dprhgtphhtthhopegvughmohhnugdrhhhtsehgmhgrihhlrdgtohhmpdhrtghpthhtohep ihhnthgvrhhnrghlsheslhhishhtshdrphhhphdrnhgvthdprhgtphhtthhopegsuhhkkh grsehphhhprdhnvghtpdhrtghpthhtohepjhgsrghffhhorhguseiiohhrthdrnhgvth X-ME-Proxy: Feedback-ID: ifab94697:Fastmail Received: by mailuser.phl.internal (Postfix, from userid 501) id 2C3BD1820054; Sat, 15 Nov 2025 16:12:59 -0500 (EST) 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: A2k3WxiDp57Z Date: Sat, 15 Nov 2025 22:11:21 +0100 To: "John Bafford" , "Edmond Dantes" Cc: "php internals" , "Jakub Zelenka" , "Larry Garfield" Message-ID: <0f37fa44-dd56-4773-be33-f1ba57eddcd5@app.fastmail.com> In-Reply-To: <12D52EE4-62F4-42AC-A17D-A8F3A19D2433@zort.net> References: <6618a91c-5393-4f40-88b5-b5041ee09deb@app.fastmail.com> <12D52EE4-62F4-42AC-A17D-A8F3A19D2433@zort.net> Subject: Re: [PHP-DEV] PHP True Async RFC Stage 5 Content-Type: multipart/alternative; boundary=4f8127740d9849488015fbfe0f30ce5b From: rob@bottled.codes ("Rob Landers") --4f8127740d9849488015fbfe0f30ce5b Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable On Sat, Nov 15, 2025, at 17:20, John Bafford wrote: > Hi Rob, Edmond, >=20 > > On Nov 15, 2025, at 06:37, Rob Landers wrote: > >=20 > > I have concerns about the clarity of when suspension occurs in this = RFC. > >=20 > > The RFC states as a core goal: > >=20 > > "Code that was originally written and intended to run outside of a C= oroutine must work EXACTLY THE SAME inside a Coroutine without modificat= ions." > >=20 > > And: > >=20 > > "A PHP developer should not have to think about how Coroutine switch= and should not need to manage their switching=E2=80=94except in special= cases where they consciously choose to intervene in this logic." > >=20 > > [...] > >=20 > > With explicit async/await ("coloured functions"), developers know ex= actly where suspension can occur. This RFC=E2=80=99s implicit model seem= s convenient, but without clear rules about suspension points, I=E2=80=99= m unclear how developers can write correct concurrent code or reason abo= ut performance. > >=20 > > Could the RFC clarify the rules for when automatic suspension occurs= versus when manual suspend() calls are required? Is this RFC following = Go=E2=80=99s model where suspension timing is an implementation detail d= evelopers shouldn=E2=80=99t rely on? If so, that should be stated explic= itly. Keep in mind that Go didn=E2=80=99t start that way and took nearly= a decade to get there. Earlier versions of Go explicitly stated where s= uspensions were. > >=20 > > =E2=80=94 Rob >=20 > To provide an explicit example for this, code that fits this pattern i= s going to be problematic: >=20 > function writeData() { > $count =3D count($this->data); > for($x =3D 0; $x < $count; $x++) { > [$path, $content] =3D $this->data[$x]; > file_put_contents($path, $content); > } > $this->data =3D []; > } >=20 > While there are better ways to write this function, in normal PHP code= , there's no problem here. But if file_put_contents() can block and caus= e a different coroutine to run, $this->data can be changed out from unde= r writeData(), which leads to unexpected behavior. (e.g. $this->data cha= nges length, and now writeData() no longer covers all of it; or it runs = past the end of the array and errors; or doesn't see there's a change an= d loses it when it clears the data). >=20 > Now, yes, the programmer would have to do something to cause there to = be two coroutines running in the first place. But if _this_ code was cor= rect when "originally written and intended to run outside of a Coroutine= ", and with no changes is incorrect when run inside a coroutine, one can= only say that it is working "exactly the same" with coroutines by ignor= ing that it is now wrong. >=20 > Suspension points, whether explicit or hidden, allow for the entire re= st of the world to change out from under the caller. The only way for no= n-async-aware code to operate safely is for suspension to be explicit (w= hich, of course, means the code now must be async-aware). There is no wa= y in general for code written without coroutines or async suspensions in= mind to work correctly if it can be suspended. >=20 > -John I should have put all these emails combined into a single email ... but = here we are. John=E2=80=99s example captures the core issue, and I want to take a mom= ent and expand on it from a different angle. My concern with implicit su= spensions isn=E2=80=99t theoretical. It=E2=80=99s exactly why nearly eve= ry modern language abandoned this model. Transparent, implicit suspension means that *any* line of code can becom= e an interleaving point. That makes a large class of patterns, which are= perfectly safe in synchronous PHP today, unsafe the moment they run ins= ide a coroutine. A few concrete examples: With property hooks and implicit suspension, event this becomes unsafe: $this->counter++; A suspension can happen between the read and the write. Another coroutin= e can mutate the counter in between. The programmer did nothing wrong; i= t's just a hazard introduced by invisible suspension. And consider this can break invariants: $this->balance -=3D $amount; $this->ledger->writeEntry($this->id, -$amount); If the first line suspends, the balance can be changed somewhere else be= fore the ledger entry is written (which breaks an invariant that the bal= ance is a reflection of the ledger). With transparent async, it's sudden= ly a race condition. Then you can have time pass invisibly: if(!$cache->has($key)) { $cache->set($key, $value); } If has() suspends, anything can happen to that cache key before the set.= The invariant becomes incorrect. Implicit suspension allows any function to be re-entered before it retur= ns. That can lead to partially updated objects, state machines appearing= to skip states, "method called twice before return" bugs, double writes= , and re-entrant callbacks being invoked with inconsistent state. The bugs are extremely challenging to debug because the programmer never= actually wrote any async code. I=E2=80=99ve had the "pleasure" of working on Fiber frameworks that use = raw fibers (no async/await you get from React/Amp, though I=E2=80=99ve w= orked with those pretty extensively as well). These are the bugs you run= into all the time, where you sometimes have to literally put a suspensi= on in a seemingly random place to fix a bug. Implicit async blurs one of the most important boundaries in software de= sign: "this code cannot be interrupted" vs "this code can be interrupted= ". - JavaScript moved from implicit async -> promises -> async/await - Python moved from callbacks/greenlets -> async/await - Ruby moved from fibers -> explicit schedulers - Go eventually added true preemption Even the creators of Fibers eventually wrote async/await on top of them,= because implicit async is broken and coloured functions close off entir= e classes of bugs and make reasoning possible again. I understand the desire for "transparent async" but once a language allo= ws suspension at arbitrary points, the language can no longer promise in= variants, atomic sequences, non-reentrancy, predictable control flow, or= even correctness, in-general. =E2=80=94 Rob --4f8127740d9849488015fbfe0f30ce5b Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable
On Sat, Nov = 15, 2025, at 17:20, John Bafford wrote:
Hi Rob, Edmond,

> O= n Nov 15, 2025, at 06:37, Rob Landers <rob@bottled.codes> wrote:
&= gt; I have concerns about the clarity of when suspension occurs in this = RFC.
> The RFC states as a core goal:<= /div>
> "Code that was originally written an= d intended to run outside of a Coroutine must work EXACTLY THE SAME insi= de a Coroutine without modifications."
&g= t; And:
> "A PHP developer should not = have to think about how Coroutine switch and should not need to manage t= heir switching=E2=80=94except in special cases where they consciously ch= oose to intervene in this logic."
> [.= ..]
> With explicit async/await ("colo= ured functions"), developers know exactly where suspension can occur. Th= is RFC=E2=80=99s implicit model seems convenient, but without clear rule= s about suspension points, I=E2=80=99m unclear how developers can write = correct concurrent code or reason about performance.
> = ;
> Could the RFC clarify the rules for when automatic susp= ension occurs versus when manual suspend() calls are required? Is this R= FC following Go=E2=80=99s model where suspension timing is an implementa= tion detail developers shouldn=E2=80=99t rely on? If so, that should be = stated explicitly. Keep in mind that Go didn=E2=80=99t start that way an= d took nearly a decade to get there. Earlier versions of Go explicitly s= tated where suspensions were.
> =E2=80= =94 Rob

To provide an explicit example for this= , code that fits this pattern is going to be problematic:

=
function writeData() {
$count =3D count($this->d= ata);
for($x =3D 0; $x < $count; $x++) {
[$path, = $content] =3D $this->data[$x];
file_put_contents($path, $co= ntent);
}
$this->data =3D [];
}

While there are better ways to write this function, in= normal PHP code, there's no problem here. But if file_put_contents() ca= n block and cause a different coroutine to run, $this->data can be ch= anged out from under writeData(), which leads to unexpected behavior. (e= .g. $this->data changes length, and now writeData() no longer covers = all of it; or it runs past the end of the array and errors; or doesn't s= ee there's a change and loses it when it clears the data).
Now, yes, the programmer would have to do something to cause= there to be two coroutines running in the first place. But if _this_ co= de was correct when "originally written and intended to run outside of a= Coroutine", and with no changes is incorrect when run inside a coroutin= e, one can only say that it is working "exactly the same" with coroutine= s by ignoring that it is now wrong.

Suspension = points, whether explicit or hidden, allow for the entire rest of the wor= ld to change out from under the caller. The only way for non-async-aware= code to operate safely is for suspension to be explicit (which, of cour= se, means the code now must be async-aware). There is no way in general = for code written without coroutines or async suspensions in mind to work= correctly if it can be suspended.

-John
<= /blockquote>

I should have put all these emails combi= ned into a single email ... but here we are.

Jo= hn=E2=80=99s example captures the core issue, and I want to take a momen= t and expand on it from a different angle. My concern with implicit susp= ensions isn=E2=80=99t theoretical. It=E2=80=99s exactly why nearly every= modern language abandoned this model.

Transpar= ent, implicit suspension means that any line of code can bec= ome an interleaving point. That makes a large class of patterns, which a= re perfectly safe in synchronous PHP today, unsafe the moment they run i= nside a coroutine. A few concrete examples:

Wit= h property hooks and implicit suspension, event this becomes unsafe:

$this->counter++;

A su= spension can happen between the read and the write. Another coroutine ca= n mutate the counter in between. The programmer did nothing wrong; it's = just a hazard introduced by invisible suspension.

And consider this can break invariants:

$thi= s->balance -=3D $amount;
$this->ledger->writeEntry($t= his->id, -$amount);

If the first line suspen= ds, the balance can be changed somewhere else before the ledger entry is= written (which breaks an invariant that the balance is a reflection of = the ledger). With transparent async, it's suddenly a race condition.

Then you can have time pass invisibly:
<= br>
if(!$cache->has($key)) {
  $cache->se= t($key, $value);
}

If has() suspends,= anything can happen to that cache key before the set. The invariant bec= omes incorrect.

Implicit suspension allows any = function to be re-entered before it returns. That can lead to partially = updated objects, state machines appearing to skip states, "method called= twice before return" bugs, double writes, and re-entrant callbacks bein= g invoked with inconsistent state.

The bugs are= extremely challenging to debug because the programmer never actually wr= ote any async code.

I=E2=80=99ve had the "pleas= ure" of working on Fiber frameworks that use raw fibers (no async/await = you get from React/Amp, though I=E2=80=99ve worked with those pretty ext= ensively as well). These are the bugs you run into all the time, where y= ou sometimes have to literally put a suspension in a seemingly random pl= ace to fix a bug.

Implicit async blurs one of t= he most important boundaries in software design: "this code cannot be in= terrupted" vs "this code can be interrupted".

-= JavaScript moved from implicit async -> promises -> async/await
- Python moved from callbacks/greenlets -> async/await
=
- Ruby moved from fibers -> explicit schedulers
- Go e= ventually added true preemption

Even the creato= rs of Fibers eventually wrote async/await on top of them, because implic= it async is broken and coloured functions close off entire classes of bu= gs and make reasoning possible again.

I underst= and the desire for "transparent async" but once a language allows suspen= sion at arbitrary points, the language can no longer promise invariants,= atomic sequences, non-reentrancy, predictable control flow, or even cor= rectness, in-general.

=E2=80= =94 Rob
--4f8127740d9849488015fbfe0f30ce5b--