Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:129454 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 5965E1A00BC for ; Tue, 25 Nov 2025 12:37:52 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1764074276; bh=LN9OtVYUY54Syuwai9eqqDzFAnw+3eZkAp1fRLgfv+Q=; h=Date:Subject:To:References:From:In-Reply-To:From; b=DdMKrFkfWvkWrcLEYaEI/BfWQGKoiFbeB8khf8B2bJv0kjxY1yakJ+F7rpFAsACX3 GXPDCQV1eD4uCaGOhMIATlAnojy+7oLz7M/pCltbEyruDvMx3oPAlZeTKbATdmN6Iw BbMJ3CNa/OB5LmAotPAd5r8ENk1IDi/68yuozBBcSAkBYbQbxrfBn+l5XAPpIl8HyI 6f2EAvEbwsx5ntyuaEL/cTTsQ/ogIykRnVZYKrOhgbcCVzXBZBtEx9HYWYtCc9vnEU 14cWuG0hO0/6H/dEh52keZrn+IHbsp1539YVyT0yPZIClX74f1jnl4xWHk+5LZq7Lq d70Vo47BD3pkQ== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id B6FA0180041 for ; Tue, 25 Nov 2025 12:37: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.6 required=5.0 tests=BAYES_50,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_PASS,FREEMAIL_FROM, 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-ej1-f51.google.com (mail-ej1-f51.google.com [209.85.218.51]) (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, 25 Nov 2025 12:37:55 +0000 (UTC) Received: by mail-ej1-f51.google.com with SMTP id a640c23a62f3a-b7355f6ef12so1144703866b.3 for ; Tue, 25 Nov 2025 04:37:50 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1764074269; x=1764679069; darn=lists.php.net; h=content-transfer-encoding:in-reply-to:from:content-language :references:to:subject:user-agent:mime-version:date:message-id:from :to:cc:subject:date:message-id:reply-to; bh=uS2WFKm+xhqZ7CYE5EPIIoOUWEjI5uyj33NS334aBZ8=; b=MGplTEzi6ufzDF5U+vHQ6fC3qA2S9F9+UXVjRUiP15s4A/IDL4x7Fjpogml4T+dNCr sWUBsNUM1qu9Yj6A6vfL/czXw855AgdhVkD5EA1N8uB9qqIKDL4IsUwv+/rLN1DApdeF uca8SFgAgxqqOy8QFPCtyGNae4BzYxZUOPsk90+h+IJzqje3hQLzoqUUV9LaoBUL6Ryj BYGyqAiqFdnFk7a/ccMeb/JcfLRibyn0kssRcu4u848vIbjSKITR6aiK/uSE7R1O6NEc IW3xCfrJlQkJ52sWsdlF7hWH3Csev3ZNRcrnM8XJheT6UgQlmb22a2hOwAlgERQ4YZgG /0iw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1764074269; x=1764679069; h=content-transfer-encoding:in-reply-to:from:content-language :references:to:subject:user-agent:mime-version:date:message-id :x-gm-gg:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=uS2WFKm+xhqZ7CYE5EPIIoOUWEjI5uyj33NS334aBZ8=; b=e2PtMeZ/cHrKi3vKK4n4ZTjbIjRuQmXZZ97nzriVS33gh2EHpHpuUeeA3KvcqZP8HM GmgDYn/WhMnVEJJZKrHs/09kU+ry8HQMxmcWHmwcvAcC1w7CZN/0/AwEpJZ1HccviSDx tqRq2lZQA9owRh5l2N6lwNV53yJNhxVPnWvHytoPwKQ4aWVw+vf8qd698ruA1sDFRONd vQIWrBQFwpUnRGp/FTK/Ipd+ieXTZdc6wej8lbVOtTBVUMH8Yy8hz+yCJvrckMYUY6Xr pQwcfxeIg//0fgc8O3HhbQGyHC2BcVjJDlObIN47Ps/LeWpVMzvgxdVd23v/U6yU51ex XHCg== X-Gm-Message-State: AOJu0YyiP7PaPmny+OD9WiyyFFM38GQ7TRZF1KPeo861e3OnBmLZOYtE DH6Sn9t05DsXSkzZ9RnjTnjvYPGyq29dMBx998bGl1+F2d8FKLuE3EtVDYZPRA== X-Gm-Gg: ASbGncvrYGpOuCm/uSus3CnbbwF/UpRmYYEujLTCFXbmMqvWum44extS3GIbpvOA1Vi AU54htZkKp8K5XPCce52qPCI/cozolHmnWQVxDKl5IreAVv0jDrSrjcRWDxA+7AJHYa9tAt835K 0FouPcrjlF9iKkETNDvFctlkdFBhKESd69HGuv32/GW4vYFIMZHwHuIxFmAE1+m72c+wFXCU5A3 tPg3SsLrBUPzWeu5XJHXOmWTlAlHm8Y8J7NqSBy8h2DUpJGXtJkOl/KySiOy9H68eZydWftboq/ cI9oUQI+FKbuQCrw1NwDjG8BYkbo7C3mhLV0F+eZZePwZjs6bSDTh3/jqPWlBb7E88WlGpCJHFt Xfxg7V781C99+rX1S2KdkaTuL+/55sM5sCwtSQUSXy8xrgP6G/BZid69PXRyeT4aFN5Ca0dhvP+ nzh3C75Fu3yJKMGcxhhqIxlSs/sgBFRULLPRYbQuabJk3fhlXnKL2vtFMgkTS1Y8nxSlJHXN4Yk 4SmBJ1rVOnBmi2a/l4UidxinEQTA/nPYZFPWoqqZwGrdf8YSM5v X-Google-Smtp-Source: AGHT+IECnkbXTTxM44O13vxOeJ92cCcEWBgiKzXqOGBcEKdr74MoJ1CNlHviSGbUF2Ko351fszG2ZQ== X-Received: by 2002:a17:907:a0d:b0:b70:be0b:6ba8 with SMTP id a640c23a62f3a-b76719d9598mr1578700066b.61.1764074268763; Tue, 25 Nov 2025 04:37:48 -0800 (PST) Received: from ?IPV6:2a0e:97c0:38f:0:e6a8:dfff:fee8:b020? (luna-020b8eeffffd8a6e0000f.net.as198747.daniil.it. [2a0e:97c0:38f:0:e6a8:dfff:fee8:b020]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-b7654d55d7fsm1573455366b.21.2025.11.25.04.37.48 for (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Tue, 25 Nov 2025 04:37:48 -0800 (PST) Message-ID: <4a8fadd7-beca-4f0e-952d-5ce9b3a8f502@gmail.com> Date: Tue, 25 Nov 2025 13:37:40 +0100 Precedence: list list-help: list-unsubscribe: list-post: List-Id: x-ms-reactions: disallow MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: [PHP-DEV] [VOTE] True Async RFC 1.6 To: internals@lists.php.net References: Content-Language: en-US In-Reply-To: Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 8bit From: daniil.gentili@gmail.com (Daniil Gentili) Hi all. Reposting here what I already posted (https://github.com/true-async/php-true-async-rfc/discussions/8#discussioncomment-15074303) in the discussion on php-true-async-rfc (the discussion should really move there from this list IMO). Senior software artisan here with 15 years of PHP experience, 8 years of async PHP experience (amphp v2, then v3), 5 years of go experience, 3 years of Rust experience, and much more. My recommendation for async PHP is: - Stackful coroutines - Colorless functions - Existing IO functions should be async - Some memory isolation: shared statics maybe with a separate attribute (i.e. allow fiber-local statics which have uses, but also allow normal statics as is already the case to allow for caching); while getting rid of globals (not statics) in general would be nice, I feel like it would be out of scope for this RFC.   Race conditions are **not** an issue, like they are not an issue in the vast majority of languages, as there is a variety of tools and models to work with them: channels (go), mutexes (go, amphp, rust, C, C++, etc), actor model (any language), etc. This model matches the status quo in golang and amphp v3: from my 8 years of experience in writing async both **business logic** and **abstraction logic** in multiple languages, this is the best concurrency model. Some evidence: I maintain [MadelineProto](https://github.com/danog/MadelineProto/), the biggest PHP MTProto client. MTProto is an async binary protocol over TCP (no HTTP involved), requires heavy caching in order for clients to function correctly, and a lot race-heavy abstractions. MadelineProto is a framework which contains a fully async MTProto client. I migrated MadelineProto to async PHP with amphp v2 (colored, stackless async) in 2018, and then to amphp v3 (colorless, stackful async) in 2022. - The biggest problem while using v2 was the colored approach, requiring the use of await (yield, using await for clarity from here on) every time a function may become async: this is the status quo of some languages like JS, but in reality **it becomes a giant pain when writing a lot of business logic**: not only that, there were constant **breaking changes** every time a method that previously wasn't async (i.e. had no network/IO logic) suddenly becomes async (thus requiring the use of await).   In MadelineProto, I worked around this with a custom coroutine runtime (replacing that of amphp v2), which allowed users to `await` even non-async functions: by then forcing users to always await all methods of the framework, I managed to avoid breaking changes every time an abstraction became async.   Clearly, this was a crutch, made to workaround a big usability issue for end users, caused by colored functions.   Another issue was with the stackless approach, which essentially forced to use of `call` to spawn a new coroutine (creating a new generator) every time when invoking an async function: I again worked around this in my custom coroutine runtime, allowing to spawn a new coroutine simply by using `await` (`yield`).   This in turn led to the creation of a new generator object every time `await` was used: I was also able to work around this, transforming every `yield` to a `yield from`: this reduced the overhead, but it was clearly yet another crutch, made to transform a stackless approach into a more usable stackful approach. - The switch to amphp v3 finally got rid of the colored approach, switching to a stackful, colorless approach.   This was a huge improvement in terms of developer UX: no more worrying about when to use `await`, no more custom coroutine runtimes to implement stackfulness to avoid using `call()` *and* `await` every time when calling an async function, all **without impacting safety**.   Amphp v3's model is pretty close to golang's model, and mirrors go's ease of use. - When migrating the MadelineProto framework to amphp, right away, it was clear to me that with concurrency, race conditions would be an issue.  Amphp v2 already offered synchronization primitives in the form of mutexes, which are more than enough to guarantee safety.    However, I wanted an easier approach for me as the library developer, which is why I, right away, chose to communicate instead of sharing memory, by adopting the [actor model](https://en.wikipedia.org/wiki/Actor_model) for MadelineProto's internal core modules (IO, update handling, any moderately large logic requiring synchronization).    Mutexes are still used for isolated, smaller logic, as they provide equivalent safety with fewer boilerplate compared to channels/actors.    **A shared-nothing approach only adds needless complexity and overhead, when the alternative is simply to isolate logic needing synchronization in a separate actor, or adding a few mutexes in key places, like is already done in many languages like Go, Rust (multithreaded with inner mutability), Java, C, C++, etc** - An issue that is still present to this day in amphp is the requirement to switch to their own IO API in order to write async logic (admittedly a lot better than PHP's stdlib): this is a **huge** issue especially for PHP developers that have only ever used PHP's own stdlib (curl, etc), or common libraries like guzzle.   For example: MadelineProto exposes MTProto events through an event handler, which is a class with appropriately decorated user-defined methods which handle chosen events concurrently (each event is handled a new coroutine).   If users use native PHP functions within those methods, they block execution of:   - All other events   - Most importantly, the library itself, which **requires** the periodic execution of time-sensitive operations in order to maintain connection with the MTProto servers (if MTProto updates aren't acked within a specific time frame, they are either resent or the connection is terminated by the server, plus `ping_delay_disconnect` must be emitted periodically to signal liveliness of the connection itself)   To work around this, I run static analysis on code users write, warning them if they use non-async stdlib functions and libraries in the event handler, suggesting async alternatives (and also offering simple to use synchronization primitives).   To this day, warnings emitted by the static analysis are the single biggest hurdle to thousands of new users of my framework, which simply do not understand why can't they just use `file_get_contents` or `curl` instead of amphp/http-client, amphp/file, etc.   The same thing will happen to PHP, if a split approach is chosen (the old stdlib remains blocking, and a new async stdlib is made)   To those who think making all of the PHP stdlib async will break stuff, I say: **it is a misconception that making all PHP IO functions async will break existing applications**.   Existing legacy frameworks like wordpress will simply keep working as before, using existing application servers like php-fpm or apache.   New application servers based on `spawn` will only be able to use modern, maintained frameworks which use proper synchronization primitives (mutexes, channels, etc), and currently-legacy frameworks can also be adapted with a little bit of effort, just like I adapted the large codebase of MadelineProto over the course of a few months (including both v2 and v3 migrations).   There are also ways to signal the async-safety of libraries: just like in other languages (go, java, C, C++, etc):   - Via a note in the documentation (this class is safe to use concurrently), an example from the go prometheus library: https://pkg.go.dev/github.com/prometheus/client_golang/prometheus, `All exported functions and methods are safe to be used concurrently unless specified otherwise. `   - Via a class attribute like `Concurrent` (closer to Rust's `Sync` attribute, though Rust's Sync [doesn't directly indicate thread-safety](https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=4ddb9fe86e126ee0e9fa76a006d648e2), as race conditions on interior mutability are still possible on Sync structs; in fact, Rust's Sync attribute is more of a **negative** attribute, where its absence signal non-thread-safety, but its presence does not guarantee thread safety, and non-thread-safe structs should be marked `!Sync`, whereas a PHP `Concurrent` attribute could be a **positive** attribute, explicitly guaranteeing thread safety) Outside in PHP: in 2020, I started writing heavily async business logic in Go, and was very positively surprised by the superior developer experience, especially when using async: I was also very pleased to learn that Go *encourages* (not forces) the use of the actor model through channels. In my professional Go experience, I wrote heavily concurrent and massively parallel, high-load services, and golang's stackful, colorless approach. I also used Rust to write async business logic, and suffered from the same issues I suffered with amphp v2: large amounts of boilerplate await keywords, heavily impacting developer experience. To this day, I consider Go's approach superior to that of any other language, confirmed by extensive development experience. Regards, Daniil Gentili.