Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:129358 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 E0A1C1A00BC for ; Fri, 21 Nov 2025 11:29:30 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1763724575; bh=QAdf2X+aT4ElpWhqjNj2NEWIvLcb+cQ2P0OvMmRHNMg=; h=References:In-Reply-To:From:Date:Subject:To:Cc:From; b=aNWEkIx0k4hYfemMS4AeF4QqwrI51ijrNUGQjstot7w8yDCcXRDH81KkeloAhj4jv fnKsyieNV37SMorqTJAUpLh+YcY4cLW2OVSmHwCjedR9lPeUMpM1hkuSGFaVJwMCYw 6SwPMG6EHynDr36ghn9ASUQtrvfqoqiJqjsHySn2MSA3NsrOyzWIeqiD52WdA2mBv5 881OCsJPJfHtZZT2UfGuRx7WfrLbl1AP07Qum1DtzVkJaHp3ONhuwIFy2z7Hcerl+L qfYbx1tAtD7hooU396yMdgkW7ajJMK9xIedr+Wy5ebtxIGJG8cgLixLQHP1lz8lc42 o1qL7lagvdwoQ== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 1AE321805CF for ; Fri, 21 Nov 2025 11:29:34 +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=1.8 required=5.0 tests=BAYES_50,DMARC_NONE, FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM,HEADER_FROM_DIFFERENT_DOMAINS, HTML_MESSAGE,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H3,RCVD_IN_MSPIKE_WL, SPF_HELO_NONE,SPF_PASS autolearn=no autolearn_force=no version=4.0.1 X-Spam-Virus: No X-Envelope-From: Received: from mail-oo1-f53.google.com (mail-oo1-f53.google.com [209.85.161.53]) (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 ; Fri, 21 Nov 2025 11:29:33 +0000 (UTC) Received: by mail-oo1-f53.google.com with SMTP id 006d021491bc7-656b32a0cc4so297420eaf.1 for ; Fri, 21 Nov 2025 03:29:28 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1763724568; x=1764329368; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=/WMq0zihRyZcN36EWOWMct6roMxjel8exPM0tfSxifs=; b=VzYm8Hq3SL76K797c7Zp8HC/jgeO31f9xhXj9v/HmKtYxmxjH9HFkFjuUS3fqJJV/8 2ch8Mqgq2gbGhOJtP1jJ5KAfjVkjm7xS36vlQpLLrdzMhM4ufTcdqpCnO8/sqwFQ/jRJ HbEs6SPBAjTB4ighR2lcyfdq5gEQZHGFP4BE6XW9Xm8YSTi1NVsxnGhf9/tniVxRp4y8 VpzZQUy6iByp4KJllIx4CGomiBbFr53niwbD/SwbTooQEEWC945owfD8/bLxfw3mcN36 EQjg1k2bEFU8rfWFG1gVaxegF9yEmQMHqHgX92o9D55zS9anOlQyVuMJV888fbOjGJX+ tPUg== X-Forwarded-Encrypted: i=1; AJvYcCUjozJsN01zWqwlP+xgDLXqqq3vEDBJso+aQpftkVdUx1bX62cfsDz51yiDkmCE8qsTt3FJSae5VCQ=@lists.php.net X-Gm-Message-State: AOJu0Ywr5/AsBXv8riYbeQ8jo2tPEB/Udyt5mav+A1ZFlnEChWqcDCdO 0N/8fPW8R7f31WOirjtwfq0KmNpwnMAOkz6600VeWTtef2N3zNZzM+ZCF99X3O4K8SWNaub36pB 4JKbafuaWvllckz3GtlvXEQ5lUJ+p2t8YMQ== X-Gm-Gg: ASbGnct8xs2v1mOqSv7WGNgdhxHHcYN8Tfdgl0OtAMK9eb3EnYCd/8SbULyjeBdmLdt j+zD3ZDhlB8VvwatLVzitBblOHYY2M21oLHp1zkpZ/W+wrMSEE8SogzpGon6Ta3b0i35G8deV01 WXJPhy3/8nqgm/udX8BdH6gud2nYaj6M54/LmNn+/uWPom7iNi3e4MxcEhcJt9GWe2f1+ggaJx0 f2G1tl5WL2lq3xjWoOxNey+wy+mf8v1cPikSrWFrkohzfuww/1agJaIf7H6psK8S+cUYw== X-Google-Smtp-Source: AGHT+IGdbLDCHtHVfTkKb6inOZ3HxAFtywC3a+BxJUb5GgisPTOTMAQX0hKd2tMV/2J9p+yRYsE7UaIlTFLHHM2auoo= X-Received: by 2002:a05:6820:229e:b0:651:c8cb:f662 with SMTP id 006d021491bc7-65790a7c79bmr663995eaf.1.1763724568097; Fri, 21 Nov 2025 03:29:28 -0800 (PST) Precedence: list list-help: list-unsubscribe: list-post: List-Id: x-ms-reactions: disallow MIME-Version: 1.0 References: In-Reply-To: Date: Fri, 21 Nov 2025 12:29:16 +0100 X-Gm-Features: AWmQ_bmvySevgnrIAMkywhfvG9DxOt0iST4f4IExYNj46OJcD6vAs2WzummQHeE Message-ID: Subject: Re: [PHP-DEV] [VOTE] True Async RFC 1.6 To: Edmond Dantes Cc: "Rowan Tommins [IMSoP]" , internals@lists.php.net Content-Type: multipart/alternative; boundary="0000000000005cf99b06441920bb" From: bukka@php.net (Jakub Zelenka) --0000000000005cf99b06441920bb Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Hi, On Fri, Nov 21, 2025 at 8:16=E2=80=AFAM Edmond Dantes = wrote: > Hello. > > Imagine that we have an application like this. > > ```php > class AuthService > { > private static ?self $instance =3D null; > > private PDO $db; > private ?string $sessionId =3D null; > > // Private constructor for singleton > private function __construct(PDO $db) > { > $this->db =3D $db; > } > > // Get singleton instance > public static function getInstance(PDO $db): self > { > if (self::$instance =3D=3D=3D null) { > self::$instance =3D new self($db); > } > return self::$instance; > } > > public function login(string $email, string $password): bool > { > // Find user by email > $stmt =3D $this->db->prepare('SELECT * FROM users WHERE email =3D= ?'); > $stmt->execute([$email]); > $user =3D $stmt->fetch(PDO::FETCH_ASSOC); > > // Invalid credentials > if (!$user || !password_verify($password, $user['password_hash'])= ) > { > return false; > } > > // Generate and save session ID > $this->sessionId =3D bin2hex(random_bytes(16)); > > $stmt =3D $this->db->prepare( > 'INSERT INTO sessions (user_id, session_id) VALUES (?, ?)' > ); > $stmt->execute([$user['id'], $this->sessionId]); > > return true; > } > > // Return current session ID > public function getSessionId(): ?string > { > return $this->sessionId; > } > } > ``` > > One day you decide you want more performance and make a single PHP > process handle multiple connections concurrently. You wrap each > request in a separate coroutine and try to use the old code. > > ```php > $server =3D new Swoole\Http\Server("127.0.0.1", 9501); > > $server->on("request", function ($req, $res) { > > // create DB connection (just for example) > $db =3D new PDO('mysql:host=3Dlocalhost;dbname=3Dtest', 'root', ''); > > // get singleton > $auth =3D AuthService::getInstance($db); > > // read request data > $data =3D json_decode($req->rawContent(), true); > > $email =3D $data['email'] ?? ''; > $password =3D $data['password'] ?? ''; > > // call old sync code > $ok =3D $auth->login($email, $password); > > if ($ok) { > $res->end("Logged in, session: " . $auth->getSessionId()); > } else { > $res->status(401); > $res->end("Invalid credentials"); > } > }); > > $server->start(); > ``` > > What is happening here? > Now, in PHP, inside a single process or thread, the same code is > literally handling multiple connections. > At the same time, there are constant switches between different > requests at the points where MySQL queries occur. > > That is, when the code executes > $stmt->execute([$email]); > control is passed to another coroutine with a different > $stmt->execute([$email]); > > What breaks in this code? > Correct, coroutines break the singleton because they alternate writing > different Session IDs! > > I think you seriously underestimate impact of this in the current PHP code bases where many applications depend on global state. Especially the legacy ones but even the most popular ones. Just look into WordPress which use global state extensively. Now imagine that some popular plugin decides to use async which change some of its globals (like $post or $wp_query) during the suspension of the main code. I would assume this could horribly break things. Don't forget that other code don't have control over the plugin and it might not even know that async is used there. So I'm not sure if this design is compatible with WordPress and similar applications where global state is used extensively. If that's the case, it's of course a problem because those applications (well WordPress on its own in fact) compose the majority of PHP users so introducing something that would have potential to break its code would limit usability of the whole feature and could even lead to loosing more users that we could gain from introducing this feature= . So I think it will need to find some solution that will prevent this from happening. I guess there might be few options 1. Disallow suspension of the main sync code which is effectively some sort of colouring. 2. Preventing access to globals from coroutine which I'm not sure is even fully doable from the engine PoV - it would mean some sort of different execution mode that could not use globals (e.g. global keyword and calling some functions that change global state). It would need channels for communications between coroutines. The advantage of such model would be possibility to combine it with threads in the future but I could imagine it could still lead to some subtle issue for years as there is internal global state as well that can lead to some surprises. But maybe that would be worth it. Just some thoughts... Cheers Jakub --0000000000005cf99b06441920bb Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
Hi,

On Fri, Nov 21, 2025 at 8:16= =E2=80=AFAM Edmond Dantes <edmond= .ht@gmail.com> wrote:
Hello.

Imagine that we have an application like this.

```php
class AuthService
{
=C2=A0 =C2=A0 private static ?self $instance =3D null;

=C2=A0 =C2=A0 private PDO $db;
=C2=A0 =C2=A0 private ?string $sessionId =3D null;

=C2=A0 =C2=A0 // Private constructor for singleton
=C2=A0 =C2=A0 private function __construct(PDO $db)
=C2=A0 =C2=A0 {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 $this->db =3D $db;
=C2=A0 =C2=A0 }

=C2=A0 =C2=A0 // Get singleton instance
=C2=A0 =C2=A0 public static function getInstance(PDO $db): self
=C2=A0 =C2=A0 {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (self::$instance =3D=3D=3D null) {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 self::$instance =3D new self($db)= ;
=C2=A0 =C2=A0 =C2=A0 =C2=A0 }
=C2=A0 =C2=A0 =C2=A0 =C2=A0 return self::$instance;
=C2=A0 =C2=A0 }

=C2=A0 =C2=A0 public function login(string $email, string $password): bool<= br> =C2=A0 =C2=A0 {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 // Find user by email
=C2=A0 =C2=A0 =C2=A0 =C2=A0 $stmt =3D $this->db->prepare('SELECT = * FROM users WHERE email =3D ?');
=C2=A0 =C2=A0 =C2=A0 =C2=A0 $stmt->execute([$email]);
=C2=A0 =C2=A0 =C2=A0 =C2=A0 $user =3D $stmt->fetch(PDO::FETCH_ASSOC);
=C2=A0 =C2=A0 =C2=A0 =C2=A0 // Invalid credentials
=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (!$user || !password_verify($password, $user= ['password_hash'])) {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return false;
=C2=A0 =C2=A0 =C2=A0 =C2=A0 }

=C2=A0 =C2=A0 =C2=A0 =C2=A0 // Generate and save session ID
=C2=A0 =C2=A0 =C2=A0 =C2=A0 $this->sessionId =3D bin2hex(random_bytes(16= ));

=C2=A0 =C2=A0 =C2=A0 =C2=A0 $stmt =3D $this->db->prepare(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 'INSERT INTO sessions (user_i= d, session_id) VALUES (?, ?)'
=C2=A0 =C2=A0 =C2=A0 =C2=A0 );
=C2=A0 =C2=A0 =C2=A0 =C2=A0 $stmt->execute([$user['id'], $this-&= gt;sessionId]);

=C2=A0 =C2=A0 =C2=A0 =C2=A0 return true;
=C2=A0 =C2=A0 }

=C2=A0 =C2=A0 // Return current session ID
=C2=A0 =C2=A0 public function getSessionId(): ?string
=C2=A0 =C2=A0 {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 return $this->sessionId;
=C2=A0 =C2=A0 }
}
```

One day you decide you want more performance and make a single PHP
process handle multiple connections concurrently. You wrap each
request in a separate coroutine and try to use the old code.

```php
$server =3D new Swoole\Http\Server("127.0.0.1", 9501);

$server->on("request", function ($req, $res) {

=C2=A0 =C2=A0 // create DB connection (just for example)
=C2=A0 =C2=A0 $db =3D new PDO('mysql:host=3Dlocalhost;dbname=3Dtest'= ;, 'root', '');

=C2=A0 =C2=A0 // get singleton
=C2=A0 =C2=A0 $auth =3D AuthService::getInstance($db);

=C2=A0 =C2=A0 // read request data
=C2=A0 =C2=A0 $data =3D json_decode($req->rawContent(), true);

=C2=A0 =C2=A0 $email =3D $data['email'] ?? '';
=C2=A0 =C2=A0 $password =3D $data['password'] ?? '';

=C2=A0 =C2=A0 // call old sync code
=C2=A0 =C2=A0 $ok =3D $auth->login($email, $password);

=C2=A0 =C2=A0 if ($ok) {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 $res->end("Logged in, session: " .= $auth->getSessionId());
=C2=A0 =C2=A0 } else {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 $res->status(401);
=C2=A0 =C2=A0 =C2=A0 =C2=A0 $res->end("Invalid credentials");<= br> =C2=A0 =C2=A0 }
});

$server->start();
```

What is happening here?
Now, in PHP, inside a single process or thread, the same code is
literally handling multiple connections.
At the same time, there are constant switches between different
requests at the points where MySQL queries occur.

That is, when the code executes
$stmt->execute([$email]);
control is passed to another coroutine with a different
$stmt->execute([$email]);

What breaks in this code?
Correct, coroutines break the singleton because they alternate writing
different Session IDs!


I think you = seriously underestimate impact of this in the current PHP code bases where = many applications depend on global state. Especially the legacy ones but ev= en the most popular ones. Just look into WordPress which use global state e= xtensively. Now imagine that some popular plugin decides to use async which= change some of its globals (like $post or $wp_query)=C2=A0during the suspe= nsion of the main code. I would assume this could horribly break things. Do= n't forget that other code don't have control over the plugin and i= t might not even know that async is used there. So I'm not sure if this= design is compatible with WordPress and similar applications where global = state is used extensively. If that's the case, it's of course a pro= blem because those applications (well WordPress on its own in fact) compose= the majority of PHP users so introducing something that would have potenti= al to break its code would limit usability of the whole feature and could e= ven lead to loosing more users that we could gain from introducing this fea= ture.

So I think it will need to find some solutio= n that will prevent this from happening. I guess there might be few options=

1. Disallow suspension of the main sync code whic= h is effectively some sort of colouring.
2. Preventing access to = globals from coroutine which I'm not sure is even fully doable from the= engine PoV - it would mean some sort of different execution mode that coul= d not use globals (e.g. global keyword and calling some functions that chan= ge global state). It would need channels for communications between corouti= nes. The advantage of such model would be possibility to combine it with th= reads in the future but I could imagine it could still lead to some subtle = issue for years as there is internal global state as well that can lead to = some surprises. But maybe that would be worth it.

= Just some thoughts...

Cheers

<= div>Jakub


--0000000000005cf99b06441920bb--