Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:126592 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 qa.php.net (Postfix) with ESMTPS id 6171E1A00BC for ; Thu, 6 Mar 2025 04:11:47 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1741234151; bh=FKHaBiMct8GpiDGf5CQbkQz2xKwripXlRTSJ6wqLNQY=; h=Date:From:To:In-Reply-To:References:Subject:From; b=SSVyroriLV2O7sUkiSWAYMqs7s9tGHsNleLF+tAFKwlzzeWPmZCOszA0Z7AMkXen0 n/UdVxTeJDiH2fVOT7OHsHdkpR3KQDu9DwOd5hBQMU8mBEvI+qrf31m6kTvJAboK5H XJ8XVb/ASNsJwwqDap0LbGPsG3uJ4fU28dBiggQHexLU8qAhGPb+ARMhCkPuBMWYtn nB8PA58M9lya7UNoagIyyIQY01VvPtKpFiF8FThmSwZ2e/UVQK8ZbkbPPXcqBLu8Tu yH9CFIml6tZBDJH7AFRlOJ84lhsx8K/ucJETcunRDaoQlxsbAtLFRHqp9/vFe7vmbp n9ce199GtwZxg== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 8485A180053 for ; Thu, 6 Mar 2025 04:09:09 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.0 (2022-12-13) 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,RCVD_IN_DNSWL_LOW, SPF_HELO_PASS,SPF_NONE autolearn=no autolearn_force=no version=4.0.0 X-Spam-Virus: No X-Envelope-From: Received: from fout-a2-smtp.messagingengine.com (fout-a2-smtp.messagingengine.com [103.168.172.145]) (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 ; Thu, 6 Mar 2025 04:09:09 +0000 (UTC) Received: from phl-compute-04.internal (phl-compute-04.phl.internal [10.202.2.44]) by mailfout.phl.internal (Postfix) with ESMTP id 7699D1381150 for ; Wed, 5 Mar 2025 23:11:44 -0500 (EST) Received: from phl-imap-06 ([10.202.2.83]) by phl-compute-04.internal (MEProxy); Wed, 05 Mar 2025 23:11:44 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= garfieldtech.com; h=cc:content-transfer-encoding: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=1741234304; x=1741320704; bh=hZvFzcMRY9lWocJMVPl3Z IPW0iM/YWaPzvf67XLARcY=; b=FLvL1XZOZWmDXMPVxQ9O0+QoL/5nOdWpJKvlM s9zkGNOR7drK0jJv4qJr5xyhBub0qcfP1E6hX47sfAwRTNEUqPYlHsJjDbKp+olc ssZkEaK9JsUFkFX54tbDpqXcVwrEevNYkyXqrKfguXq2CnFIm7HpoT5rdFsszmtU vGg8JcQrVIXmRL+EmWnV8TOKBi4N0xVC5mAjCyx7SY3cFTw+m8JZtI9H3Bfy5JEl /bWOgjfcW+PGUMbnXlAvHwGCzOrhI4+PWYOPmx6B0UOVopjQ2E4k4IFzWmI/lQQd PY6N5PXzM1lhW6c7U4vuSUnVD0exmwBaMSvKIfuU3pj6ndHqQ== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-transfer-encoding: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=fm1; t=1741234304; x=1741320704; bh=h ZvFzcMRY9lWocJMVPl3ZIPW0iM/YWaPzvf67XLARcY=; b=5RYx9Cdyr3ys0oW2S UQEWUwy978AzCPCMTEYrOe+xQs0lOpWRtz7zno2Mmndjntmllu3Ldi73yaPMPgpK Gyjhhz/9pIc2JGvhttzS/qDByXB3g+1p3fanvMBnidwCLJPBJj5YlnaBXRKgvSkA QXDY+zjHFn4dEyqehKUgrQ5pHhi6ZrLzkHMs/mNwHU/t+eVcIVZ8Pfv22TtfsTG2 aUaqDt/ifMXX9T9Th5t3gm4FDxef2SB7PHQsjEFLQF5+b8PY/1gfA4lEgd1NSevG MBHL4JLj7n8darKOtwtZ/FFhi8N7jRYDV8dXOiTw/tW9yCjx5ereAXZZpoZaw4iY +jZGg== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefvddrtddtgddutdeijeefucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdggtfgfnhhsuhgsshgtrhhisggv pdfurfetoffkrfgpnffqhgenuceurghilhhouhhtmecufedttdenucesvcftvggtihhpih gvnhhtshculddquddttddmnecujfgurhepofggfffhvffkjghfufgtgfesthhqredtredt jeenucfhrhhomhepfdfnrghrrhihucfirghrfhhivghlugdfuceolhgrrhhrhiesghgrrh hfihgvlhguthgvtghhrdgtohhmqeenucggtffrrghtthgvrhhnpeehudetudfhjeeftddt ieekuedvteelveeugfefhfekuddtleehffejffelkeffueenucffohhmrghinhepughomh grihhnuddrtghomhdpughomhgrihhnvddrtghomhdpghgrrhhfihgvlhguthgvtghhrdgt ohhmnecuvehluhhsthgvrhfuihiivgeptdenucfrrghrrghmpehmrghilhhfrhhomheplh grrhhrhiesghgrrhhfihgvlhguthgvtghhrdgtohhmpdhnsggprhgtphhtthhopedupdhm ohguvgepshhmthhpohhuthdprhgtphhtthhopehinhhtvghrnhgrlhhssehlihhsthhsrd hphhhprdhnvght X-ME-Proxy: Feedback-ID: i8414410d:Fastmail Received: by mailuser.phl.internal (Postfix, from userid 501) id 224F729C006F; Wed, 5 Mar 2025 23:11:44 -0500 (EST) X-Mailer: MessagingEngine.com Webmail Interface Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net x-ms-reactions: disallow MIME-Version: 1.0 Date: Wed, 05 Mar 2025 22:11:23 -0600 To: "php internals" Message-ID: In-Reply-To: References: <9964db8c-0ffe-43d5-8246-47fc76b07180@app.fastmail.com> <78a03dd0-fd4a-4f4a-ad8a-37e5704f06fc@app.fastmail.com> Subject: Re: [PHP-DEV] PHP True Async RFC Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable From: larry@garfieldtech.com ("Larry Garfield") On Wed, Mar 5, 2025, at 3:10 PM, Edmond Dantes wrote: >> No, just those functions/objects that necessarily involve running asy= nc control commands. Most wouldn't. =20 >> They would just silently context switch when they hit an IO operation= (which as noted above is transparency supported, which is what makes th= is=20 >> work) and otherwise behave the same.=20 > > So it's something more like Go or Python. > >> >> $val =3D async(function(AsyncContext $ctx) use ($stuff, $fn) { >> $result =3D []; >> foreach ($stuff as $item) { >> $result[] =3D $ctx->run($fn); >>} >> >> // We block/wait here until all subtasks are complete, then the asyn= c() call returns this value. >> return $result; >> }); > > Do I understand correctly that at the point `$val =3D=20 > async(function(AsyncContext $ctx) use ($stuff, $fn)` execution stops=20 > until everything inside is completed? Correct. By the time $val is populated, all fibers/coroutines/tasks sta= rted inside that block have completed and closed, guaranteed. If an exc= eption was thrown or something else went wrong, then by the time the exc= eption escapes the asnc{} block, all fibers inside it are done and close= d, guaranteed. (If there's another async {} block further up the stack = somewhere, there may still be other background fibers running, but anyth= ing created inside that block is guaranteed done.) > If so, let me introduce a second semantic option (for now, I'll remove=20 > the context and focus only on the function). =20 > > ```php > $url1 =3D 'https://domain1.com/'; > $url2 =3D 'https://domain2.com/'; > > $url_handle =3D fn(string $url) =3D> file_get_contents($url); > > $res =3D Async\start(function() use ($url1, $url2, $url_handle) { > $res1 =3D Async\run($url_handle, $url1); > $res2 =3D Async\run($url_handle, $url2); > > Async\run(fn() =3D> sleep(5)); > > // some logic here > > return $merged_result; > }); > ``` > > What's Happening Here: > > 1. After calling `$res =3D Async\start()`, the code waits until the=20 > entire block completes. > 2. Inside `Async\start`, the code waits for all nested coroutines to=20 > finish. > 3. If a coroutine has other nested coroutines, the same rule applies. > Rules Inside an Asynchronous Block: > > 1. I/O functions do not block coroutines within the block. > 2. Creating a new `Fiber` is not allowed =E2=80=94 an exception will = be=20 > thrown: you cannot use `Fiber`. > 3. Unhandled exceptions will be thrown at the point of `$res =3D=20 > Async\start()`. > Coroutine Cancellation Rules: > > Canceling a coroutine cancels it and all its child coroutines (this=20 > cannot be bypassed unless the coroutine is created in a different=20 > context). > > How does this option sound to you? We can quibble on the details and spelling, but I think the overall logi= c is sound. One key question, if we disallow explicitly creating Fibers= inside an async block, can a Fiber be created outside of it and not blo= ck async, or would that also be excluded? Viz, this is illegal: async { $f =3D new Fiber(some_func(...)); } But would this also be illegal? $f =3D new Fiber(some_func(...)); $f->start(); async { do_stuff(); } > Essentially, this is Kotlin, but it should also resemble Python.=20 > However, unlike Kotlin, there are no special language constructs=20 > here=E2=80=94code blocks naturally serve that role. Of course, syntact= ic sugar=20 > can be added later for better readability. =20 My brief foray into Kotlin in a previous job didn't get as far as corout= ines, so I will take your word from it. From a very cursory glance at t= he documentation, I think runBlocking {} is approximately what I am desc= ribing, yes. The various other block types I don't know are necessary. > And if you like this, I have good news: there are no implementation=20 > issues at this level. > > In terms of semantic elegance, the only thing that bothers me is that=20 > `return` behavior is slightly altered =E2=80=94 meaning the actual "re= turn"=20 > won=E2=80=99t happen until all child functions complete. This isn=E2=80= =99t very good,=20 > and Kotlin=E2=80=99s style would fit better here. I'm not sure I follow. The main guarantee we want is that "once you pas= s this }, all fibers/coroutines have ended, count on it." Do you mean s= omething like this? async $ctx { $ctx->run(foo(...)); $ctx->run(bar(...)); // This return statement blocks until foo() and bar() complete. return "all done"; } That doesn't seem any weirder than return and finally{} blocks. :-) (No= te that we can and should consider if async {} makes sense to have its o= wn catch and finally blocks built in.) > But on the other hand =E2=80=94 can we live with this? This seems far closer to something I'd support than the current RFC, yes. >> I cannot speak to JS Symbols as I haven't used them. =20 >> I am just vhemently opposed to globals, no matter how many layers the= y're wrapped in. :-) Most uses could be replaced by proper DI or partia= l application. > > You won=E2=80=99t be able to use DI because you have only *one service=20 > (instance of class)* for the entire application, not a separate servic= e=20 > for each coroutine. This service is shared across the application and=20 > can be called from any coroutine. As a result, the service needs memor= y=20 > slots to store or retrieve data. DI is a mechanism used once during=20 > service initialization, not every time a method is called. Not true. DI doesn't imply singleton objects. Most good DI *containers= * default to singleton objects, as they should, but for example Laravel'= s container does not. You have to opt-in to singleton behavior. (I thi= nk that's a terrible design, but it's still DI.) DI just means "a scope gets the stuff it needs given to it, it never ask= s for it." How that stuff is passed in is, deliberately, undefined. A = DI container is but one way. In Crell/Serde, I actually use "runner objects" a lot. I have an exampl= e here: https://presentations.garfieldtech.com/slides-serialization/longhornphp2= 023/#/7/4/3 That is still dependency injection, because ThingRunner is still taking = all of its dependencies via the constructor. And being readonly, it's s= till immutable-friendly. That's the sort of thing I'm thinking of here for the async context. To= spitball again: class ClientManager { public function __construct(string $base) {} public function client(AsyncContext $ctx) { return new HttpClient($this->base, $ctx); } } class HttpClient { public function __construct(private string $base, private AsyncContext= $ctx) {} public function get(string $path) { $this->ctx->defer(fn() =3D> print "Read $path\n"); return $this->ctx->run(fn() =3D> file_get_contents($this->base . $pa= th)); } } $manager =3D $container->get(ClientManager::class); async $ctx { $client =3D $manager->client($ctx); $client->get('/foo'); $client->get('/bar'); }=20 // We don't get here until all file_get_contents() calls are complete. // The deferred functions all get called right here. // There is no no async happening anymore. print "Done";=20 I'm pretty sure the return values are all messed up there, but hopefully= you get the idea. Now HttpClient has a fully injected context that con= trols what async scope it's working in. The same class can be used in a= bunch of different async blocks, each with their own context. You can = even mock AsyncContext for testing purposes just like any other construc= tor argument. And not a global function or variable in sight! :-) --Larry Garfield