Hello all
According to all previous discussions, version 1.6 of this RFC has
been prepared and is now being submitted for a vote:
Voting Page: https://wiki.php.net/rfc/true_async/voting
RFC https://wiki.php.net/rfc/true_async
The vote officially starts tomorrow, as previously announced.
For version 1.6 the following important change was made:
All input/output functions are now bound by the shared requirement of
being non-blocking with respect to the process. However, the specific
behavior of each function may (optionally) be defined in separate
RFCs.
Thus, I/O functions themselves are not part of this RFC, but the main
RFC defines the general way in which they must operate. Thus (as I see
it), the RFC achieves a balance between cohesion and separation of
concerns.
Since the discussion period has ended, I will not be engaging in
further debate (except regarding the voting process itself). If you
have any questions for me of any kind, you may ask them either in a
separate thread or privately. (This also means that I will not be
answering RFC-related questions in this thread). I will be glad to
hear your opinions and feedback. I wish all participants the best of
luck.
Best Regards, Ed
Hello all
According to all previous discussions, version 1.6 of this RFC has
been prepared and is now being submitted for a vote:Voting Page: https://wiki.php.net/rfc/true_async/voting
RFC https://wiki.php.net/rfc/true_asyncThe vote officially starts tomorrow, as previously announced.
Why is the voting widget not on the RFC page where it belongs?
cheers
Derick
Hi,
Il 19/11/2025 14:24, Derick Rethans ha scritto:
Hello all
According to all previous discussions, version 1.6 of this RFC has
been prepared and is now being submitted for a vote:Voting Page: https://wiki.php.net/rfc/true_async/voting
RFC https://wiki.php.net/rfc/true_asyncThe vote officially starts tomorrow, as previously announced.
Why is the voting widget not on the RFC page where it belongs?
Also it's weird to get a [VOTE] email when voting is not open yet.
Cheers
Matteo Beccati
Hello, Matteo
Why is the voting widget not on the RFC page where it belongs?
Unfortunately, I wasn’t able to understand exactly how the voting
process works, so I created a separate page. But if the vote is
supposed to be included in the RFC itself, please ignore it.
Also it's weird to get a [VOTE] email when voting is not open yet.
I’m sorry, this really is strange.
Unfortunately, I wasn’t able to understand exactly how the voting
process works, so I created a separate page. But if the vote is
supposed to be included in the RFC itself, please ignore it.
I guess you didn't see my reply when you asked this before. Here again
is the link to the instructions: https://wiki.php.net/rfc/howto
Can you tell us where you got the idea that there needed to be a
separate page? Maybe there is some text that we can make clearer.
Meanwhile, please cancel this vote. There is no rush.
If we get it right, this could be the most significant feature added to
PHP for 10 years or more. If we get it wrong, we might regret it for 20
years.
On Monday, you replied to a message from Bart Vanhoutte, saying:
Hello Bart.
I am ready to agree with every word.Participation from a working group, framework representatives, and the
ability to adapt libraries in advance would remove the concerns that
are currently causing fear. This is probably the only effective
process for developing and introducing such a feature.
Then two days later, you decided that no more discussion was necessary,
and opened a vote.
This feels like a complete contradiction.
Let's find a way to get that working group set up, and get people from
other projects involved.
I am extremely grateful and frankly amazed for all the work you've put
into this. Please don't throw that away by ending up with a "No" vote
over small details.
--
Rowan Tommins
[IMSoP]
Hi,
Hello Bart.
I am ready to agree with every word.Participation from a working group, framework representatives, and the
ability to adapt libraries in advance would remove the concerns that
are currently causing fear. This is probably the only effective
process for developing and introducing such a feature.Then two days later, you decided that no more discussion was
necessary, and opened a vote.This feels like a complete contradiction.
Let's find a way to get that working group set up, and get people from
other projects involved.
My key takeaway from Bart's message is:
Moreover, even though there are quite a few people in the community
who have the knowledge required because they either develop or work with
aforementioned libraries or extensions, (almost) none of them seem to be
involved in discussing this RFC.
For an RFC that can drastically change the way we develop
applications I would expect more experts on this matter to be involved.
Ideally, PHP core developers, library developers & maintainers, IDE
developers, ..., would develop software using this branch to at least
get some feel for the paradigm and this RFC in general.
I absolutely agree with this take, however, so far, the discussion
around this RFC has been, in my opinion, mostly bikeshedding, with
theoretical correctness proposals that are an absolute nightmare in
practice (like structured concurrency), proposed by people that
admittedly have never written extensive amounts of async code in
languages using multiple paradigms, and thus haven't:
- Experienced the pain of writing async with colored functions
- Experienced the footguns of structured concurrency
- And on the other hand, haven't experienced the pleasure and simplicity
of safely writing async code in languages like Go, or in PHP using AMPHP
(which use uncolored and unstructured concurrency, the kind proposed and
championed by edmond)
While a working group can steer the conversation away from
theoretically correct but practically unusable approaches, that can
happen only if
- The correct people (i.e. async library maintainers, or people that
write async logic every day in multiple languages like myself) are present - They are given more weight than the average PHP developer who hasn't
used async much if at all, and can only make theoretical proposals not
based on practice and experience
I'm afraid that given the current state of the PHP community, which is
largely new to async, the quality of the conversation in a working group
would not be much higher than the one I'm seeing in this RFC, and would
just protract even longer the agony of design by committee, where in
reality what's needed is a single, clear and correct vision (which
Edmond has), without influences from unexperienced people making
proposals based purely on abstract/theoretical PoVs.
Regards,
Daniil Gentili.
On Thu, 20 Nov 2025 at 07:21 Daniil Gentili daniil.gentili@gmail.com
wrote:
Hi,
Hello Bart.
I am ready to agree with every word.Participation from a working group, framework representatives, and the
ability to adapt libraries in advance would remove the concerns that
are currently causing fear. This is probably the only effective
process for developing and introducing such a feature.Then two days later, you decided that no more discussion was
necessary, and opened a vote.This feels like a complete contradiction.
Let's find a way to get that working group set up, and get people from
other projects involved.My key takeaway from Bart's message is:
Moreover, even though there are quite a few people in the community
who have the knowledge required because they either develop or work with
aforementioned libraries or extensions, (almost) none of them seem to be
involved in discussing this RFC.
For an RFC that can drastically change the way we develop
applications I would expect more experts on this matter to be involved.
Ideally, PHP core developers, library developers & maintainers, IDE
developers, ..., would develop software using this branch to at least
get some feel for the paradigm and this RFC in general.I absolutely agree with this take, however, so far, the discussion
around this RFC has been, in my opinion, mostly bikeshedding, with
theoretical correctness proposals that are an absolute nightmare in
practice (like structured concurrency), proposed by people that
admittedly have never written extensive amounts of async code in
languages using multiple paradigms, and thus haven't:
- Experienced the pain of writing async with colored functions
- Experienced the footguns of structured concurrency
- And on the other hand, haven't experienced the pleasure and simplicity
of safely writing async code in languages like Go, or in PHP using AMPHP
(which use uncolored and unstructured concurrency, the kind proposed and
championed by edmond)While a working group can steer the conversation away from
theoretically correct but practically unusable approaches, that can
happen only if
- The correct people (i.e. async library maintainers, or people that
write async logic every day in multiple languages like myself) are present- They are given more weight than the average PHP developer who hasn't
used async much if at all, and can only make theoretical proposals not
based on practice and experienceI'm afraid that given the current state of the PHP community, which is
largely new to async, the quality of the conversation in a working group
would not be much higher than the one I'm seeing in this RFC, and would
just protract even longer the agony of design by committee, where in
reality what's needed is a single, clear and correct vision (which
Edmond has), without influences from unexperienced people making
proposals based purely on abstract/theoretical PoVs.Regards,
Daniil Gentili.
While I certainly can sympathize with the painful, dreadful, unpleasant,
unbearable agony of debating a subject with “non-experts”, it’s important
to have some perspective in the opposite direction.
As it has been mentioned before, Async PHP in general is practically a
rounding error in terms of user base and there are reasons for that. It’s
important to remember that the benefits of async doesn’t always justify the
burden that it brings. For PHP as a language to adopt an async solution
natively it’s very important that sync code continues to function while
also allowing developers to opt into async without having to feel like they
changed languages and must re-learn how to manage their projects. If this
is not possible then perhaps the current state is as good as we can ever
get: let expert matter install their extension (opt-in) on a per-project
basis.
It's going to be up to the subject experts to come up with a path that
allows PHP to stay coherent while offering both approaches. To put this in
another way: RFC Voters are above average PHP developers. If they're unable
to digest the changes being proposed, even if said changes are being
proposed by the single most subject-expert human on the planet, then how do
we expect average PHP developers to make good use of it?
On Thu, 20 Nov 2025 at 07:21 Daniil Gentili daniil.gentili@gmail.com
wrote:Hi,
Hello Bart.
I am ready to agree with every word.Participation from a working group, framework representatives, and the
ability to adapt libraries in advance would remove the concerns that
are currently causing fear. This is probably the only effective
process for developing and introducing such a feature.Then two days later, you decided that no more discussion was
necessary, and opened a vote.This feels like a complete contradiction.
Let's find a way to get that working group set up, and get people from
other projects involved.My key takeaway from Bart's message is:
Moreover, even though there are quite a few people in the community
who have the knowledge required because they either develop or work with
aforementioned libraries or extensions, (almost) none of them seem to be
involved in discussing this RFC.
For an RFC that can drastically change the way we develop
applications I would expect more experts on this matter to be involved.
Ideally, PHP core developers, library developers & maintainers, IDE
developers, ..., would develop software using this branch to at least
get some feel for the paradigm and this RFC in general.I absolutely agree with this take, however, so far, the discussion
around this RFC has been, in my opinion, mostly bikeshedding, with
theoretical correctness proposals that are an absolute nightmare in
practice (like structured concurrency), proposed by people that
admittedly have never written extensive amounts of async code in
languages using multiple paradigms, and thus haven't:
- Experienced the pain of writing async with colored functions
- Experienced the footguns of structured concurrency
- And on the other hand, haven't experienced the pleasure and simplicity
of safely writing async code in languages like Go, or in PHP using AMPHP
(which use uncolored and unstructured concurrency, the kind proposed and
championed by edmond)While a working group can steer the conversation away from
theoretically correct but practically unusable approaches, that can
happen only if
- The correct people (i.e. async library maintainers, or people that
write async logic every day in multiple languages like myself) are present- They are given more weight than the average PHP developer who hasn't
used async much if at all, and can only make theoretical proposals not
based on practice and experienceI'm afraid that given the current state of the PHP community, which is
largely new to async, the quality of the conversation in a working group
would not be much higher than the one I'm seeing in this RFC, and would
just protract even longer the agony of design by committee, where in
reality what's needed is a single, clear and correct vision (which
Edmond has), without influences from unexperienced people making
proposals based purely on abstract/theoretical PoVs.Regards,
Daniil Gentili.
While I certainly can sympathize with the painful, dreadful, unpleasant,
unbearable agony of debating a subject with “non-experts”, it’s important
to have some perspective in the opposite direction.As it has been mentioned before, Async PHP in general is practically a
rounding error in terms of user base and there are reasons for that. It’s
important to remember that the benefits of async doesn’t always justify the
burden that it brings. For PHP as a language to adopt an async solution
natively it’s very important that sync code continues to function while
also allowing developers to opt into async without having to feel like they
changed languages and must re-learn how to manage their projects. If this
is not possible then perhaps the current state is as good as we can ever
get: let expert matter install their extension (opt-in) on a per-project
basis.It's going to be up to the subject experts to come up with a path that
allows PHP to stay coherent while offering both approaches. To put this in
another way: RFC Voters are above average PHP developers. If they're unable
to digest the changes being proposed, even if said changes are being
proposed by the single most subject-expert human on the planet, then how do
we expect average PHP developers to make good use of it?
Yeah I think this is one of the reasons why the RFC failed. It couldn't
properly explain the topic even though it was reduced to minimum. One of
the factor is certainly that Edmond is new to the RFC process but more
importantly it's quite contentious topic that can bring even more bike
shedding. I think there were some important points raised in the
discussions about safety of the existing sync code which I think might be
the real killer here. So even if we omit the mix up with the
pre-announcement and sudden voting (that were sure path to rejection), I
think the bigger problem is the whole size of the feature and the fact that
it will be extremely hard to find any solution that will please majority of
voters. I'm honestly not sure if this is possible to get to any form that
can pass. I would like to be wrong but we can see the reality here.
I think the way forward for PHP is to do what we have been doing and it is
to provide the building blocks for user space to enable async there because
that's something that can be reasonably introduced using the RFC through
smaller chunks. It means improving the non blocking setup, exposing IO
hooks, better polling and other primitives. That was actually the plan in
past and that's why it is also contained in my STF stream work where the
scope was created way before the TrueAsync.
Kind regards,
Jakub
Hi
What I understand from this is that the voters are not willing to make any
concessions to include a feature that is important to at least 10% of the
developers in the PHP ecosystem (someone mentioned 10% in an earlier email;
I would guess the number is even higher, considering China). It’s important
for us, who are part of that 10% that apparently isn’t important enough to
be heard, to know what the voters’ stance actually is regarding the
introduction of tooling for Async in PHP. That way we can understand what
the next step needs to be: whether we take on the difficult job of
migrating our software from PHP to another platform, or the impossible job
of trying to convince people who are not willing to be convinced.
Regards,
Luís Vinícius
Em qui., 20 de nov. de 2025, 12:44, Jakub Zelenka bukka@php.net escreveu:
On Thu, 20 Nov 2025 at 07:21 Daniil Gentili daniil.gentili@gmail.com
wrote:Hi,
Hello Bart.
I am ready to agree with every word.Participation from a working group, framework representatives, and the
ability to adapt libraries in advance would remove the concerns that
are currently causing fear. This is probably the only effective
process for developing and introducing such a feature.Then two days later, you decided that no more discussion was
necessary, and opened a vote.This feels like a complete contradiction.
Let's find a way to get that working group set up, and get people from
other projects involved.My key takeaway from Bart's message is:
Moreover, even though there are quite a few people in the community
who have the knowledge required because they either develop or work with
aforementioned libraries or extensions, (almost) none of them seem to be
involved in discussing this RFC.
For an RFC that can drastically change the way we develop
applications I would expect more experts on this matter to be involved.
Ideally, PHP core developers, library developers & maintainers, IDE
developers, ..., would develop software using this branch to at least
get some feel for the paradigm and this RFC in general.I absolutely agree with this take, however, so far, the discussion
around this RFC has been, in my opinion, mostly bikeshedding, with
theoretical correctness proposals that are an absolute nightmare in
practice (like structured concurrency), proposed by people that
admittedly have never written extensive amounts of async code in
languages using multiple paradigms, and thus haven't:
- Experienced the pain of writing async with colored functions
- Experienced the footguns of structured concurrency
- And on the other hand, haven't experienced the pleasure and simplicity
of safely writing async code in languages like Go, or in PHP using AMPHP
(which use uncolored and unstructured concurrency, the kind proposed and
championed by edmond)While a working group can steer the conversation away from
theoretically correct but practically unusable approaches, that can
happen only if
- The correct people (i.e. async library maintainers, or people that
write async logic every day in multiple languages like myself) are
present- They are given more weight than the average PHP developer who hasn't
used async much if at all, and can only make theoretical proposals not
based on practice and experienceI'm afraid that given the current state of the PHP community, which is
largely new to async, the quality of the conversation in a working group
would not be much higher than the one I'm seeing in this RFC, and would
just protract even longer the agony of design by committee, where in
reality what's needed is a single, clear and correct vision (which
Edmond has), without influences from unexperienced people making
proposals based purely on abstract/theoretical PoVs.Regards,
Daniil Gentili.
While I certainly can sympathize with the painful, dreadful, unpleasant,
unbearable agony of debating a subject with “non-experts”, it’s important
to have some perspective in the opposite direction.As it has been mentioned before, Async PHP in general is practically a
rounding error in terms of user base and there are reasons for that. It’s
important to remember that the benefits of async doesn’t always justify the
burden that it brings. For PHP as a language to adopt an async solution
natively it’s very important that sync code continues to function while
also allowing developers to opt into async without having to feel like they
changed languages and must re-learn how to manage their projects. If this
is not possible then perhaps the current state is as good as we can ever
get: let expert matter install their extension (opt-in) on a per-project
basis.It's going to be up to the subject experts to come up with a path that
allows PHP to stay coherent while offering both approaches. To put this in
another way: RFC Voters are above average PHP developers. If they're unable
to digest the changes being proposed, even if said changes are being
proposed by the single most subject-expert human on the planet, then how do
we expect average PHP developers to make good use of it?Yeah I think this is one of the reasons why the RFC failed. It couldn't
properly explain the topic even though it was reduced to minimum. One of
the factor is certainly that Edmond is new to the RFC process but more
importantly it's quite contentious topic that can bring even more bike
shedding. I think there were some important points raised in the
discussions about safety of the existing sync code which I think might be
the real killer here. So even if we omit the mix up with the
pre-announcement and sudden voting (that were sure path to rejection), I
think the bigger problem is the whole size of the feature and the fact that
it will be extremely hard to find any solution that will please majority of
voters. I'm honestly not sure if this is possible to get to any form that
can pass. I would like to be wrong but we can see the reality here.I think the way forward for PHP is to do what we have been doing and it is
to provide the building blocks for user space to enable async there because
that's something that can be reasonably introduced using the RFC through
smaller chunks. It means improving the non blocking setup, exposing IO
hooks, better polling and other primitives. That was actually the plan in
past and that's why it is also contained in my STF stream work where the
scope was created way before the TrueAsync.Kind regards,
Jakub
Hi,
Hello Bart.
I am ready to agree with every word.Participation from a working group, framework representatives, and the
ability to adapt libraries in advance would remove the concerns that
are currently causing fear. This is probably the only effective
process for developing and introducing such a feature.Then two days later, you decided that no more discussion was
necessary, and opened a vote.This feels like a complete contradiction.
Let's find a way to get that working group set up, and get people from
other projects involved.My key takeaway from Bart's message is:
Moreover, even though there are quite a few people in the community
who have the knowledge required because they either develop or work with
aforementioned libraries or extensions, (almost) none of them seem to be
involved in discussing this RFC.
For an RFC that can drastically change the way we develop
applications I would expect more experts on this matter to be involved.
Ideally, PHP core developers, library developers & maintainers, IDE
developers, ..., would develop software using this branch to at least
get some feel for the paradigm and this RFC in general.I absolutely agree with this take, however, so far, the discussion
around this RFC has been, in my opinion, mostly bikeshedding, with
theoretical correctness proposals that are an absolute nightmare in
practice (like structured concurrency), proposed by people that
admittedly have never written extensive amounts of async code in
languages using multiple paradigms, and thus haven't:
- Experienced the pain of writing async with colored functions
- Experienced the footguns of structured concurrency
- And on the other hand, haven't experienced the pleasure and simplicity
of safely writing async code in languages like Go, or in PHP using AMPHP
(which use uncolored and unstructured concurrency, the kind proposed and
championed by edmond)While a working group can steer the conversation away from
theoretically correct but practically unusable approaches, that can
happen only if
- The correct people (i.e. async library maintainers, or people that
write async logic every day in multiple languages like myself) are present- They are given more weight than the average PHP developer who hasn't
used async much if at all, and can only make theoretical proposals not
based on practice and experienceI'm afraid that given the current state of the PHP community, which is
largely new to async, the quality of the conversation in a working group
would not be much higher than the one I'm seeing in this RFC, and would
just protract even longer the agony of design by committee, where in
reality what's needed is a single, clear and correct vision (which
Edmond has), without influences from unexperienced people making
proposals based purely on abstract/theoretical PoVs.Regards,
Daniil Gentili.
I kind of take offence to this statement. First of all, I work almost exclusively in Go these days, and on FrankenPHP. I only brought up colored functions twice, because literally every language that has attempted the proposed solution here have all reversed course and implemented coloured functions. I've hand-written schedulers in C# (which is also cooperatively scheduled).
Second, Go has a tremendous amount of primitives for dealing with concurrency: wait groups, locks, atomics, etc. This proposal has none of that, hence my concern with suspension points.
Further, I have worked with AMPHP extensively since it's generator-based days on multiple projects, and with Fibers quite extensively as well. So saying "proposed by people that admittedly have never written extensive amounts of async code in languages using multiple paradigms" is factually untrue.
— Rob
Hello
I'm afraid that given the current state of the PHP community, which is
largely new to async, the quality of the conversation in a working group
would not be much higher than the one I'm seeing in this RFC
I’m afraid I have to agree with every word said here, even though we
don’t know each other and have never spoken before.
And this post is the most honest explanation of the current situation.
Ed
I absolutely agree with this take, however, so far, the discussion
around this RFC has been, in my opinion, mostly bikeshedding, with
theoretical correctness proposals that are an absolute nightmare in
practice (like structured concurrency), proposed by people that
admittedly have never written extensive amounts of async code in
languages using multiple paradigms
That is exactly why I think some other process is needed, and why an RFC
vote right now doesn't tell us anything useful.
I really want this feature. But I really want us to get it right.
As someone who is completely new to the topic, the things that will make
me vote "Yes" on an async proposal, are:
- Confidence that experts have reviewed the design, and ironed out
crucial details.
From what I've seen, Edmond has done an extremely thorough job. Once
again, thank you, I tip my hat to your effort and dedication. But I'm
not qualified to judge the result, and I want to see opinions from
people who are.
It may be that after discussing it with a bunch of other experts,
exactly the same technical design would come out; I just want a bit of
reassurance that the right discussions have happened.
Has there been an attempt to invite specific people into a discussion,
e.g. people who've worked on ReactPHP, Swoole, etc?
- A cast-iron promise that existing code would run unchanged; or a
clear explanation of what modifications it would need.
The "Goals" section says code will not need changes "or changes should
be minimal"; and will run without modification "provided that..." Those
caveats worry me.
I work on a code base with a million lines of PHP code developed in half
a dozen frameworks over twenty years, maintained by a small team. When I
hear "it took us 2-3 days to update a library", I don't hear a small
number; I multiply it in my head, and wonder how I would justify weeks
of development and testing. Right now, I don't even understand what we
would be spending those weeks doing.
If it really is a case of "it will be fine unless you're doing some
weird tricks to force PHP to do things it doesn't normally do", then great!
If it ends up as "set this global setting, and you won't get the
benefit, but your legacy code will run fine", I can live with that.
If the only advice is "your code will be fine as long it's already
well-architected and completely covered by automated testing", it's no
use to me - that's just not the reality I live in.
I sincerely hope that this is solvable.
- A set of "idiot-proof" high-level features that a non-expert PHP
developer can use without understanding the full implementation.
The current RFC is a lot more focused than previous versions, and that
is great; but it still gets very quickly into the mechanics and
edge-cases that require expertise to use.
What I think is missing is the pitch to users. Something that we can put
on the php.net homepage, and say "look how easy concurrency is in PHP 9!"
To paraphrase the Perl slogan, I want the language to make the common
things easy, and the complex things possible. I want a "pit of
success", where the easiest thing to write is also the most likely to be
safe and useful.
I think that mostly just means defining some syntax sugar on top of some
of the functions and objects. Something that we can say is PHP's
equivalent of async/await, or goroutines and channels.
But ... I realise this can't all fit into one RFC without discussion
going on forever.
Which is why we need to find some new process to make it possible.
I think the way forward for PHP is to do what we have been doing and
it is to provide the building blocks for user space to enable async
there because that's something that can be reasonably introduced using
the RFC through smaller chunks.
I think it would be a shame to throw away the progress that's been made.
I wonder if a way forward could be something like this:
-
Agree a Project Charter, some Goals and Non-Goals. Hold a vote on
this, to agree that we're going ahead with the project -
Set up some kind of project tracker, where we can list open questions
and design tasks; maybe a separate mailing list / forum / chatroom for
those involved in the details -
Start with Edmond's amazing work, and iteratively work on those
individual questions -
Keep bikeshedding questions (e.g. "what is the best name for this
class, and should it extend Error or Exception?") separate from
architecture questions (e.g. "is this class necessary, or should it be
hidden from the user?") -
Converge on a solution where we've already agreed everything in
bite-sized pieces -
Release PHP 9.0 and celebrate
All of that requires that enough people who actually understand the
problem space deeply are willing to be involved and collaborate. I
really hope that's the case, but I am not in a position to volunteer myself.
--
Rowan Tommins
[IMSoP]
Hello.
Imagine that we have an application like this.
class AuthService
{
private static ?self $instance = null;
private PDO $db;
private ?string $sessionId = null;
// Private constructor for singleton
private function __construct(PDO $db)
{
$this->db = $db;
}
// Get singleton instance
public static function getInstance(PDO $db): self
{
if (self::$instance === null) {
self::$instance = new self($db);
}
return self::$instance;
}
public function login(string $email, string $password): bool
{
// Find user by email
$stmt = $this->db->prepare('SELECT * FROM users WHERE email = ?');
$stmt->execute([$email]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
// Invalid credentials
if (!$user || !password_verify($password, $user['password_hash'])) {
return false;
}
// Generate and save session ID
$this->sessionId = bin2hex(random_bytes(16));
$stmt = $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.
$server = new Swoole\Http\Server("127.0.0.1", 9501);
$server->on("request", function ($req, $res) {
// create DB connection (just for example)
$db = new PDO('mysql:host=localhost;dbname=test', 'root', '');
// get singleton
$auth = AuthService::getInstance($db);
// read request data
$data = json_decode($req->rawContent(), true);
$email = $data['email'] ?? '';
$password = $data['password'] ?? '';
// call old sync code
$ok = $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!
And what does not change in this code?
The SQL queries can remain unchanged.
The first problem with shared memory between coroutines can ONLY be
solved by the programmer. Only the programmer. There is no solution
that would make this happen automatically.
Yesterday we talked about how we can help the programmer detect such
situations during debugging. But in any case, only the programmer
CAN and MUST solve this problem.
The difference is that you don’t need to rewrite everything else.
The focus is only on the issue of concurrent access to memory.
The essence of the choice is how much code needs to be rewritten.
Almost everything, or only the code with global state.
My choice is: it’s better to rewrite only the code with global state —
or switch to Go and avoid the pain :)
As for the rest, I will write a separate message so as not to clutter things up.
Edmond
My thanks to Edmond for his work on PHP async I/O. Regardless of how the vote turns out, I believe everything is moving in a positive direction. This will undoubtedly leave a bold mark in the history of PHP’s evolution.
I’ll share some information and thoughts to help everyone understand async. These may include views on PHP Fiber, amphp, reactphp, and FrankenPHP, but please remember they are purely technical reflections, with no praise or criticism implied.
- What lies at the core of Swoole’s async design
Using Boost.Context assembly to implement C/C++ stackful coroutines is no longer esoteric; PHP Fiber and Swoole are almost identical in their low-level principles. The only difference is that Swoole suspends and resumes coroutines entirely in C/C++, whereas PHP Fiber does the opposite—suspension happens in PHP code. While PHP also exposes relevant APIs, they are rarely used in Swoole.
Because both the C stack and the PHP stack are fully preserved, this approach is actually very safe and won’t cause memory errors—unless static or global memory is misused. Swoole runs over 1,700 tests on GitHub Actions, many of which involve multiple coroutines issuing concurrent requests. Before testing, containers spin up mysql, pgsql, oracle, redis, firebirdsql, httpbin, tinyproxy, pure-ftpd, and many other databases and servers to interact with code in phpt files. The breadth of these tests speaks to its reliability.
Unlike amphp/reactphp, Swoole does not invent new APIs; it reuses PHP’s existing functions. Swoole hooks into PHP streams, the standard library, and other extension functions—such as sleep, stream_socket_client, stream_socket_server, file_get_contents, fsockopen, curl_*, mysqli, pdo_mysql. Inside a Swoole coroutine, these calls are no longer synchronous blocking I/O; they become non-blocking. When I/O isn’t ready, the runtime suspends the current coroutine and uses epoll to watch for readable events, resuming the coroutine only when the operation completes.
An example:
Co\run(function() {
Co\go(function() {
while(1) {
sleep(1);
$fp = stream_socket_client("tcp://127.0.0.1:8000", $errno, $errstr, 30);
echo fread($fp, 8192), PHP_EOL;
}
});
Co\go(function() {
$fp = stream_socket_server("tcp://0.0.0.0:8000", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN);
while(1) {
$conn = stream_socket_accept($fp);
fwrite($conn, 'The local time is ' . date('n/j/Y g:i a'));
}
});
Co\go(function() {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
while(true) {
$redis->subscribe(['test'], function ($instance, $channelName, $message) {
echo 'New redis message: '.$channelName, "==>", $message, PHP_EOL;
});
}
});
Co\go(function() {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$count = 0;
while(true) {
sleep(2);
$redis->publish('test','hello, world, count='.$count++);
}
});
});
By conventional understanding, this code shouldn’t run: every function that performs network I/O would block the entire process. But in the Swoole environment, the program runs smoothly. We can even modify the code to increase the number of clients by several thousand, and it still runs stably.
Co\run(function() {
Co\go(function() {
while(1) {
sleep(1);
$fp = stream_socket_client("tcp://127.0.0.1:8000", $errno, $errstr, 30);
echo fread($fp, 8192), PHP_EOL;
}
});
$n = 2000;
while($n--) {
Co\go(function() {
$fp = stream_socket_server("tcp://0.0.0.0:8000", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN);
while(1) {
$conn = stream_socket_accept($fp);
fwrite($conn, 'The local time is ' . date('n/j/Y g:i a'));
}
});
}
});
Swoole’s aim is to leverage PHP’s existing ecosystem rather than build a new one. If we were starting from scratch—discarding PHP’s commonly used functions and learning an entirely new async API—why wouldn’t developers simply switch languages?
Now that true-async has adopted Swoole’s approach, I think that’s an excellent choice.
- Where PHP-FPM falls short
If all you do is read/write MySQL and generate HTML, PHP-FPM is already superb. If I’m building a web project that only depends on MySQL, I wouldn’t use Swoole; PHP-FPM is the best choice. But many modern web projects need to call external HTTP APIs, and slow requests often render PHP-FPM unavailable, which is frustrating. Async exists precisely to address this. With the rise of ChatGPT, streaming responses such as SSE and full-duplex communication via WebSocket will become increasingly common—technologies that PHP-FPM doesn’t support well. Many developers choose Node.js or Go instead. The influence of Swoole or amphp remains limited; only a small subset of developers opt to stay with PHP for async programming using these solutions.
If PHP can adopt true-async or other AsyncIO solutions and provide support for async I/O at the language level, it would be tremendous news for PHP users. In essence, async I/O is a runtime matter—much like Node.js in relation to V8. New PHP syntax isn’t required; Swoole, for instance, adds no new syntax—just some functions and classes—just as fastcgi_finish_request and fpm_get_status are php-fpm–only functions.
- FrankenPHP
FrankenPHP is a wonderful project that uses Go to give PHP additional capabilities, with great room for exploration.
In an RFC for a Polling API, author Jakub Zelenka—also a FrankenPHP maintainer—shared a technical idea: consider implementing a goroutine version of the TSRM thread isolation scheme. Each goroutine would have its own Zend VM environment—essentially a goroutine-based php-fpm.
I believe this approach may pose significant challenges, especially regarding memory resources.
Today, when running Symfony or Laravel under PHP-FPM with 100–200 worker processes, memory pressure is already heavy. If each process consumes tens to over a hundred megabytes, the group can easily use up to 20 GB. With goroutines, if you launch thousands or tens of thousands to handle requests concurrently, memory usage could become enormous.
By contrast, coroutines are designed to be very lightweight: a suspended coroutine should retain only the call stack and a small amount of request/session-related memory, while other resources can be shared and reused across requests. This drastically reduces memory usage while still allowing a large number of simultaneous requests. When a request is slow, suspension incurs little cost.
- Fiber
If Fiber and coroutines coexist as execution units, I agree it can be confusing. But the current Fiber simply can’t be used in a Swoole-like runtime with extensive low-level switching.
Although Fiber landed in PHP 8.1, Swoole cannot use any Fiber APIs.
In addition, Fiber doesn’t fully switch all global memory state—for example OG(handlers), BG(serialize), BG(unserialize)—so it’s unclear whether issues exist there.
- Golang’s abundance of synchronization primitives
Go’s goroutine isn’t purely a coroutine; it’s a combination of thread and coroutine, which necessitates many locks, mutexes, semaphores, and atomics to resolve data races. PHP does not support multithreading. Whether it’s Fiber, Swoole, or any other coroutine implementation in PHP, execution is single-threaded: only one coroutine runs at a time, and until it yields, no other coroutine runs.
Therefore, PHP coroutines are not a complex concept but a clear and straightforward one.
If the true-async RFC vote doesn’t pass this time, I think we can split the work into several parts, aiming for each RFC to accomplish just one thing.
I think the most important task now is to allow coroutine switching in low-level C code, not just in PHP code. Whether we call it coroutines or Fiber 2.0 is fine. On top of that, other work can be introduced via future RFCs to progressively strengthen the design.
Lastly, I sincerely hope PHP keeps getting better. Thanks all for your contributions. Open discussion is always beneficial.
Tianfeng Han
------------------ Original ------------------
From: "Edmond Dantes"<edmond.ht@gmail.com>;
Date: Fri, Nov 21, 2025 03:17 PM
To: "Rowan Tommins [IMSoP]"<imsop.php@rwec.co.uk>;
Cc: "php internals"<internals@lists.php.net>;
Subject: Re: [PHP-DEV] [VOTE] True Async RFC 1.6
Hello.
Imagine that we have an application like this.
class AuthService
{
private static ?self $instance = null;
private PDO $db;
private ?string $sessionId = null;
// Private constructor for singleton
private function __construct(PDO $db)
{
$this->db = $db;
}
// Get singleton instance
public static function getInstance(PDO $db): self
{
if (self::$instance === null) {
self::$instance = new self($db);
}
return self::$instance;
}
public function login(string $email, string $password): bool
{
// Find user by email
$stmt = $this->db->prepare('SELECT * FROM users WHERE email = ?');
$stmt->execute([$email]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
// Invalid credentials
if (!$user || !password_verify($password, $user['password_hash'])) {
return false;
}
// Generate and save session ID
$this->sessionId = bin2hex(random_bytes(16));
$stmt = $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.
$server = new Swoole\Http\Server("127.0.0.1", 9501);
$server->on("request", function ($req, $res) {
// create DB connection (just for example)
$db = new PDO('mysql:host=localhost;dbname=test', 'root', '');
// get singleton
$auth = AuthService::getInstance($db);
// read request data
$data = json_decode($req->rawContent(), true);
$email = $data['email'] ?? '';
$password = $data['password'] ?? '';
// call old sync code
$ok = $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!
And what does not change in this code?
The SQL queries can remain unchanged.
The first problem with shared memory between coroutines can ONLY be
solved by the programmer. Only the programmer. There is no solution
that would make this happen automatically.
Yesterday we talked about how we can help the programmer detect such
situations during debugging. But in any case, only the programmer
CAN and MUST solve this problem.
The difference is that you don’t need to rewrite everything else.
The focus is only on the issue of concurrent access to memory.
The essence of the choice is how much code needs to be rewritten.
Almost everything, or only the code with global state.
My choice is: it’s better to rewrite only the code with global state —
or switch to Go and avoid the pain :)
As for the rest, I will write a separate message so as not to clutter things up.
Edmond
To correct a mistake in previous email, there was an error in the second code snippet of my last email. The correct code is as follows:
<?php
Co\run(function() {
Co\go(function() {
$fp = stream_socket_server("tcp://0.0.0.0:8000", $errno, $errstr, `STREAM_SERVER_BIND` | STREAM_SERVER_LISTEN);
while(1) {
$conn = stream_socket_accept($fp);
Co\go(function() use ($conn) {
while(1) {
fwrite($conn, 'The local time is ' . date('n/j/Y g:i a'));
sleep(1);
}
});
}
});
$n = 2000;
while($n--) {
Co\go(function() {
$fp = stream_socket_client("tcp://127.0.0.1:8000", $errno, $errstr, 30);
while(1) {
echo fread($fp, 8192), PHP_EOL;
}
});
}
});
The logic here is to start a server coroutine, which then accepts client connections. Each new connection spawns a coroutine that sends the current time string to the client every second.
The code below directly creates 2,000 clients in the current process, connects them to the server, and reads data. Note: If you want to test this code, I recommend lowering the number of concurrent connections, otherwise your terminal may freeze or become unresponsive.
This program can accommodate virtually any PHP function, such as mysqli, pdo, redis, curl, and more—all of which can be executed concurrently with ease.
Notice that only two new APIs have been introduced in the code: Co\run and Co\go; the rest are standard or commonly used PHP extension functions.
This is precisely the strength of Swoole, and the focus of the True-Async team’s ongoing work.
Hopefully, the PHP language will one day include such powerful features natively.
Tianfeng Han
------------------ Original ------------------
From: "韩天峰"<rango@swoole.com>;
Date: Fri, Nov 21, 2025 07:03 PM
To: "Edmond Dantes"<edmond.ht@gmail.com>; "Rowan Tommins [IMSoP]"<imsop.php@rwec.co.uk>;
Cc: "php internals"<internals@lists.php.net>;
Subject: Re: [PHP-DEV] [VOTE] True Async RFC 1.6
My thanks to Edmond for his work on PHP async I/O. Regardless of how the vote turns out, I believe everything is moving in a positive direction. This will undoubtedly leave a bold mark in the history of PHP’s evolution.
I’ll share some information and thoughts to help everyone understand async. These may include views on PHP Fiber, amphp, reactphp, and FrankenPHP, but please remember they are purely technical reflections, with no praise or criticism implied.
- What lies at the core of Swoole’s async design
Using Boost.Context assembly to implement C/C++ stackful coroutines is no longer esoteric; PHP Fiber and Swoole are almost identical in their low-level principles. The only difference is that Swoole suspends and resumes coroutines entirely in C/C++, whereas PHP Fiber does the opposite—suspension happens in PHP code. While PHP also exposes relevant APIs, they are rarely used in Swoole.
Because both the C stack and the PHP stack are fully preserved, this approach is actually very safe and won’t cause memory errors—unless static or global memory is misused. Swoole runs over 1,700 tests on GitHub Actions, many of which involve multiple coroutines issuing concurrent requests. Before testing, containers spin up mysql, pgsql, oracle, redis, firebirdsql, httpbin, tinyproxy, pure-ftpd, and many other databases and servers to interact with code in phpt files. The breadth of these tests speaks to its reliability.
Unlike amphp/reactphp, Swoole does not invent new APIs; it reuses PHP’s existing functions. Swoole hooks into PHP streams, the standard library, and other extension functions—such as sleep, stream_socket_client, stream_socket_server, file_get_contents, fsockopen, curl_*, mysqli, pdo_mysql. Inside a Swoole coroutine, these calls are no longer synchronous blocking I/O; they become non-blocking. When I/O isn’t ready, the runtime suspends the current coroutine and uses epoll to watch for readable events, resuming the coroutine only when the operation completes.
An example:
Co\run(function() {
Co\go(function() {
while(1) {
sleep(1);
$fp = stream_socket_client("tcp://127.0.0.1:8000", $errno, $errstr, 30);
echo fread($fp, 8192), PHP_EOL;
}
});
Co\go(function() {
$fp = stream_socket_server("tcp://0.0.0.0:8000", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN);
while(1) {
$conn = stream_socket_accept($fp);
fwrite($conn, 'The local time is ' . date('n/j/Y g:i a'));
}
});
Co\go(function() {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
while(true) {
$redis->subscribe(['test'], function ($instance, $channelName, $message) {
echo 'New redis message: '.$channelName, "==>", $message, PHP_EOL;
});
}
});
Co\go(function() {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$count = 0;
while(true) {
sleep(2);
$redis->publish('test','hello, world, count='.$count++);
}
});
});
By conventional understanding, this code shouldn’t run: every function that performs network I/O would block the entire process. But in the Swoole environment, the program runs smoothly. We can even modify the code to increase the number of clients by several thousand, and it still runs stably.
Co\run(function() {
Co\go(function() {
while(1) {
sleep(1);
$fp = stream_socket_client("tcp://127.0.0.1:8000", $errno, $errstr, 30);
echo fread($fp, 8192), PHP_EOL;
}
});
$n = 2000;
while($n--) {
Co\go(function() {
$fp = stream_socket_server("tcp://0.0.0.0:8000", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN);
while(1) {
$conn = stream_socket_accept($fp);
fwrite($conn, 'The local time is ' . date('n/j/Y g:i a'));
}
});
}
});
Swoole’s aim is to leverage PHP’s existing ecosystem rather than build a new one. If we were starting from scratch—discarding PHP’s commonly used functions and learning an entirely new async API—why wouldn’t developers simply switch languages?
Now that true-async has adopted Swoole’s approach, I think that’s an excellent choice.
- Where PHP-FPM falls short
If all you do is read/write MySQL and generate HTML, PHP-FPM is already superb. If I’m building a web project that only depends on MySQL, I wouldn’t use Swoole; PHP-FPM is the best choice. But many modern web projects need to call external HTTP APIs, and slow requests often render PHP-FPM unavailable, which is frustrating. Async exists precisely to address this. With the rise of ChatGPT, streaming responses such as SSE and full-duplex communication via WebSocket will become increasingly common—technologies that PHP-FPM doesn’t support well. Many developers choose Node.js or Go instead. The influence of Swoole or amphp remains limited; only a small subset of developers opt to stay with PHP for async programming using these solutions.
If PHP can adopt true-async or other AsyncIO solutions and provide support for async I/O at the language level, it would be tremendous news for PHP users. In essence, async I/O is a runtime matter—much like Node.js in relation to V8. New PHP syntax isn’t required; Swoole, for instance, adds no new syntax—just some functions and classes—just as fastcgi_finish_request and fpm_get_status are php-fpm–only functions.
- FrankenPHP
FrankenPHP is a wonderful project that uses Go to give PHP additional capabilities, with great room for exploration.
In an RFC for a Polling API, author Jakub Zelenka—also a FrankenPHP maintainer—shared a technical idea: consider implementing a goroutine version of the TSRM thread isolation scheme. Each goroutine would have its own Zend VM environment—essentially a goroutine-based php-fpm.
I believe this approach may pose significant challenges, especially regarding memory resources.
Today, when running Symfony or Laravel under PHP-FPM with 100–200 worker processes, memory pressure is already heavy. If each process consumes tens to over a hundred megabytes, the group can easily use up to 20 GB. With goroutines, if you launch thousands or tens of thousands to handle requests concurrently, memory usage could become enormous.
By contrast, coroutines are designed to be very lightweight: a suspended coroutine should retain only the call stack and a small amount of request/session-related memory, while other resources can be shared and reused across requests. This drastically reduces memory usage while still allowing a large number of simultaneous requests. When a request is slow, suspension incurs little cost.
- Fiber
If Fiber and coroutines coexist as execution units, I agree it can be confusing. But the current Fiber simply can’t be used in a Swoole-like runtime with extensive low-level switching.
Although Fiber landed in PHP 8.1, Swoole cannot use any Fiber APIs.
In addition, Fiber doesn’t fully switch all global memory state—for example OG(handlers), BG(serialize), BG(unserialize)—so it’s unclear whether issues exist there.
- Golang’s abundance of synchronization primitives
Go’s goroutine isn’t purely a coroutine; it’s a combination of thread and coroutine, which necessitates many locks, mutexes, semaphores, and atomics to resolve data races. PHP does not support multithreading. Whether it’s Fiber, Swoole, or any other coroutine implementation in PHP, execution is single-threaded: only one coroutine runs at a time, and until it yields, no other coroutine runs.
Therefore, PHP coroutines are not a complex concept but a clear and straightforward one.
If the true-async RFC vote doesn’t pass this time, I think we can split the work into several parts, aiming for each RFC to accomplish just one thing.
I think the most important task now is to allow coroutine switching in low-level C code, not just in PHP code. Whether we call it coroutines or Fiber 2.0 is fine. On top of that, other work can be introduced via future RFCs to progressively strengthen the design.
Lastly, I sincerely hope PHP keeps getting better. Thanks all for your contributions. Open discussion is always beneficial.
Tianfeng Han
------------------ Original ------------------
From: "Edmond Dantes"<edmond.ht@gmail.com>;
Date: Fri, Nov 21, 2025 03:17 PM
To: "Rowan Tommins [IMSoP]"<imsop.php@rwec.co.uk>;
Cc: "php internals"<internals@lists.php.net>;
Subject: Re: [PHP-DEV] [VOTE] True Async RFC 1.6
Hello.
Imagine that we have an application like this.
class AuthService
{
private static ?self $instance = null;
private PDO $db;
private ?string $sessionId = null;
// Private constructor for singleton
private function __construct(PDO $db)
{
$this->db = $db;
}
// Get singleton instance
public static function getInstance(PDO $db): self
{
if (self::$instance === null) {
self::$instance = new self($db);
}
return self::$instance;
}
public function login(string $email, string $password): bool
{
// Find user by email
$stmt = $this->db->prepare('SELECT * FROM users WHERE email = ?');
$stmt->execute([$email]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
// Invalid credentials
if (!$user || !password_verify($password, $user['password_hash'])) {
return false;
}
// Generate and save session ID
$this->sessionId = bin2hex(random_bytes(16));
$stmt = $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.
$server = new Swoole\Http\Server("127.0.0.1", 9501);
$server->on("request", function ($req, $res) {
// create DB connection (just for example)
$db = new PDO('mysql:host=localhost;dbname=test', 'root', '');
// get singleton
$auth = AuthService::getInstance($db);
// read request data
$data = json_decode($req->rawContent(), true);
$email = $data['email'] ?? '';
$password = $data['password'] ?? '';
// call old sync code
$ok = $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!
And what does not change in this code?
The SQL queries can remain unchanged.
The first problem with shared memory between coroutines can ONLY be
solved by the programmer. Only the programmer. There is no solution
that would make this happen automatically.
Yesterday we talked about how we can help the programmer detect such
situations during debugging. But in any case, only the programmer
CAN and MUST solve this problem.
The difference is that you don’t need to rewrite everything else.
The focus is only on the issue of concurrent access to memory.
The essence of the choice is how much code needs to be rewritten.
Almost everything, or only the code with global state.
My choice is: it’s better to rewrite only the code with global state —
or switch to Go and avoid the pain :)
As for the rest, I will write a separate message so as not to clutter things up.
Edmond
Hello,
Thank you a lot.
Swoole’s aim is to leverage PHP’s existing ecosystem rather than build a new one. If we were
starting from scratch—discarding PHP’s commonly used functions and learning an entirely new
async API—why wouldn’t developers simply switch languages?
And it turned out to be a very successful solution, which has been
proven many times in practice.
The first question developers ask when they want to migrate to async
is: how much code do I need to rewrite?
If the cost of migration is comparable to rewriting the entire project
from scratch, then it becomes economically more sensible to hire Go
developers and rebuild the project.
And PHP’s influence is strong primarily because of the companies that
run PHP-based projects.
So when a company decides to rewrite a project in another language
because PHP no longer meets its technical needs, PHP loses that
client.
That’s why supporting a single runtime and a single API,
standardization, is crucial for the technology’s survival.
Those were the economic reasons. There are technical reasons as well.
In a server that handles multiple requests within a single thread, it
is very important that coroutines do not occupy the CPU for too long.
If they do, all coroutines in that process are affected.
This means that having blocking operations inside a coroutine
nullifies the advantages of concurrency.
Therefore, intentionally making functions blocking so that code works
correctly with shared memory makes the idea of using asynchronous code
pointless.
This does not mean that one solution or another is 100 percent
perfect. Each has its drawbacks. The question is whether the drawbacks
outweigh the benefits.
Swoole made it possible to see that, in practice, using a unified API
provides a significant advantage that makes migrating legacy projects
feasible.
This means that by voting for an RFC built on these principles, we are
not venturing into a dark unknown, but walking along a well-trodden
path.
Ed
Hi,
Hello.
Imagine that we have an application like this.
class AuthService { private static ?self $instance = null; private PDO $db; private ?string $sessionId = null; // Private constructor for singleton private function __construct(PDO $db) { $this->db = $db; } // Get singleton instance public static function getInstance(PDO $db): self { if (self::$instance === null) { self::$instance = new self($db); } return self::$instance; } public function login(string $email, string $password): bool { // Find user by email $stmt = $this->db->prepare('SELECT * FROM users WHERE email = ?'); $stmt->execute([$email]); $user = $stmt->fetch(PDO::FETCH_ASSOC); // Invalid credentials if (!$user || !password_verify($password, $user['password_hash'])) { return false; } // Generate and save session ID $this->sessionId = bin2hex(random_bytes(16)); $stmt = $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.$server = new Swoole\Http\Server("127.0.0.1", 9501); $server->on("request", function ($req, $res) { // create DB connection (just for example) $db = new PDO('mysql:host=localhost;dbname=test', 'root', ''); // get singleton $auth = AuthService::getInstance($db); // read request data $data = json_decode($req->rawContent(), true); $email = $data['email'] ?? ''; $password = $data['password'] ?? ''; // call old sync code $ok = $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
- Disallow suspension of the main sync code which is effectively some sort
of colouring. - 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
Hello
I think you seriously underestimate impact of this in the current PHP code bases where many applications depend on global state.
Do I really look like someone who could underestimate memory-related issues? :)
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.
Or for example, you run an old WordPress on the new PHP 8.5 and
everything breaks. Is that a reason not to release new PHP versions?
No, I’m not joking. That’s literally the essence of the argument. If
something might break, then we shouldn’t do it?
This is a common story. There is a framework that is adapted to a
technology, and there is a framework or library that is not adapted to
it.
If a framework is not adapted, you simply won’t be able to use the
technology. So why is this considered a problem?
However, inside WordPress you will still be able to use coroutines as
long as you don’t call WP functions that aren’t adapted. You can. So
why is this a problem?
I can use AMPHP inside WordPress and break WP.
So does that mean we must urgently remove Fiber from the language?
Because AMPHP uses Fiber, and AMPHP implements coroutines. And
coroutines break WordPress.
Asynchrony already exists in PHP. You can write async code today.
Which means you can already do all the “horrors” you’re talking about.
TrueAsync cannot change that. And no one else can change it either.
So why is this being treated as an argument against it?
Don't forget that other code don't have control over the plugin and it might not even know that async is used there.
Exactly. This means that right now I can use Fiber plus select() to
write async code and break WordPress.
And I can also write a plugin that divides by zero and crashes
WordPress, and WordPress won’t know anything about it.
So I'm not sure if this design is compatible with WordPress
Yes, WordPress is not compatible with asynchrony. I’ll emphasize
again. This is not about TrueAsync specifically. This is about
asynchrony itself. Yes, WordPress and Laravel are not compatible with
concurrent execution. That’s true.
But are you really suggesting that because of this, all other
applications should be denied the ability to use it?
Moreover, would you deny WordPress itself the possibility of
supporting asynchrony in future? After all, WordPress can be
refactored. It’s not carved in stone.
Wouldn’t WordPress benefit from the performance improvements that
async provides?
Wouldn’t WordPress plugins benefit from being able to actively
communicate with microservices and deliver the fastest possible
responses to JavaScript?
Is this feature really something nobody needs? If yes, then I have no
further questions.
Async is needed to increase throughput. That’s the purpose. If a PHP
project doesn’t need it, it doesn’t have to use it. That’s fine. But
there are PHP projects that do need it.
Good morning from a rather cold but sunny Belgium,
About Backwards compatibility & colored functions.
Right now, I think there's a problem with the promise of backward
compatibility. Unless your application handles shared state perfectly,
switching on async PHP is going to cause a lot of problems. Ignoring
the problem and asking users to rewrite shared state in their
applications is an absolute no-go from me. On the other hand, we could
go down the path of colored functions, which is also an absolute no-go
from me. We've had this in the past before Fibers were a thing and it
was an absolute pain in the ass. Every function only accepted
Promise<x> and only returned Promise<x>. Bear in mind this coincided
with the rise of static typing and FIG-PSRs where you could get away
with it until Interfaces accepted and returned actual types. Having
colored functions could mean having to duplicate all interfaces where
you would have sync and async ones. This would split the ecosystem in
two and kill adoption, no thanks.
I would much rather have some solution where we can enable (opt-in)
non-blocking/async behavior on a per-project basis. At the very least
we'll be able to use it in greenfields projects. Developers who want
to add support for non-blocking/async I/O in their existing
applications can then rewrite their global state and turn it on
whenever they feel ready for it. It's not ideal but I think it's a
nice compromise between fast adoption and backward compatibility. I
envision a time where you'd look at the documentation of a library and
see "ok, this is async-ready" (shared state is dealt with) through
some badge or something.
For people who are questioning whether a feature like this should be
added to PHP I'd like to point out a couple of things.
-
Current usage of async PHP is not a good argument. Like I've
previously said elsewhere, this is a classical chicken-or-egg thing.
Some people think it's not worth adding support for this because
nobody is using it but also very few people are using async PHP
because it's not a first class citizen in the language. Having a
couple of libraries and frameworks supporting whatever async model PHP
ends up choosing would increase these numbers drastically. Adding
support for this new paradigm in popular frameworks/CMSes and thereby
pushing the language and ecosystem forward seems like something the
PHP Foundation was founded for (wrong mailing list, I know). -
About handing off the problem to Userland: you can kinda do
things asynchronously in PHP through Userland (I'm talking Fibers)
these days, but having async as a first class citizen in PHP core
would make things much easier. Currently, the ecosystem is fragmented
and you need a lot of libraries and specific knowledge in order to get
started. We've been doing this for a couple of years now and only
recently we got a decent ORM to work async (one unit of work per
Fiber, transactions, ...). -
I think PHP is especially suited for this paradigm. Web
applications require a lot of I/O, whether it is talking to the
client, database, filesystem, API's, ... Having the ability to either
serve multiple requests at the same time through a long running
process or - for example - upload a file to S3 and concurrently write
a record registering that file in the database can lower the TTFB of
web applications drastically. Moreover, as shared hosting is a thing,
spawning more processes or threads isn't always going to be available.
I feel like not adding async to PHP in the (near?) future would only
be postponing it and risk developers looking to squeeze that extra bit
of performance from their applications to Go to other languages.
On a final note, it's nice to see this discussion is gaining some
traction! I've just checked with core ReactPHP developers and they're
checking up on the RFC, however, their main concern seems to be the
same as others on this list: time. Everything is changing very fast
and more time is needed to digest this all and to get used to the idea
of this mentally.
I agree with others, this is something that we have to do right, so
let's take the time to do it right. There's already been an awesome
effort by Edmund, let's keep working on this.
Best Regards,
Bart Vanhoutte
Op vr 21 nov 2025 om 13:08 schreef Edmond Dantes edmond.ht@gmail.com:
Hello
I think you seriously underestimate impact of this in the current PHP code bases where many applications depend on global state.
Do I really look like someone who could underestimate memory-related issues? :)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.
Or for example, you run an old WordPress on the new PHP 8.5 and
everything breaks. Is that a reason not to release new PHP versions?
No, I’m not joking. That’s literally the essence of the argument. If
something might break, then we shouldn’t do it?This is a common story. There is a framework that is adapted to a
technology, and there is a framework or library that is not adapted to
it.
If a framework is not adapted, you simply won’t be able to use the
technology. So why is this considered a problem?
However, inside WordPress you will still be able to use coroutines as
long as you don’t call WP functions that aren’t adapted. You can. So
why is this a problem?I can use AMPHP inside WordPress and break WP.
So does that mean we must urgently remove Fiber from the language?
Because AMPHP uses Fiber, and AMPHP implements coroutines. And
coroutines break WordPress.
Asynchrony already exists in PHP. You can write async code today.
Which means you can already do all the “horrors” you’re talking about.
TrueAsync cannot change that. And no one else can change it either.
So why is this being treated as an argument against it?Don't forget that other code don't have control over the plugin and it might not even know that async is used there.
Exactly. This means that right now I can use Fiber plus select() to
write async code and break WordPress.
And I can also write a plugin that divides by zero and crashes
WordPress, and WordPress won’t know anything about it.So I'm not sure if this design is compatible with WordPress
Yes, WordPress is not compatible with asynchrony. I’ll emphasize
again. This is not about TrueAsync specifically. This is about
asynchrony itself. Yes, WordPress and Laravel are not compatible with
concurrent execution. That’s true.
But are you really suggesting that because of this, all other
applications should be denied the ability to use it?
Moreover, would you deny WordPress itself the possibility of
supporting asynchrony in future? After all, WordPress can be
refactored. It’s not carved in stone.Wouldn’t WordPress benefit from the performance improvements that
async provides?
Wouldn’t WordPress plugins benefit from being able to actively
communicate with microservices and deliver the fastest possible
responses to JavaScript?Is this feature really something nobody needs? If yes, then I have no
further questions.Async is needed to increase throughput. That’s the purpose. If a PHP
project doesn’t need it, it doesn’t have to use it. That’s fine. But
there are PHP projects that do need it.
Hello Bart.
Good morning from a rather cold but sunny Belgium,
Cool, it’s +12° here today and sunny as well :)
I would much rather have some solution where we can enable (opt-in) non-blocking/async behavior on a per-project basis.
Agreed, this will make it possible to disable asynchrony in projects
that are not ready for it yet.
And we can come up with several ways to do that.
Ed
Hello
I think you seriously underestimate impact of this in the current PHP
code bases where many applications depend on global state.
Do I really look like someone who could underestimate memory-related
issues? :)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.
Or for example, you run an old WordPress on the new PHP 8.5 and
everything breaks. Is that a reason not to release new PHP versions?
No, I’m not joking. That’s literally the essence of the argument. If
something might break, then we shouldn’t do it?This is a common story. There is a framework that is adapted to a
technology, and there is a framework or library that is not adapted to
it.
If a framework is not adapted, you simply won’t be able to use the
technology. So why is this considered a problem?
However, inside WordPress you will still be able to use coroutines as
long as you don’t call WP functions that aren’t adapted. You can. So
why is this a problem?I can use AMPHP inside WordPress and break WP.
So does that mean we must urgently remove Fiber from the language?
Because AMPHP uses Fiber, and AMPHP implements coroutines. And
coroutines break WordPress.
Asynchrony already exists in PHP. You can write async code today.
Which means you can already do all the “horrors” you’re talking about.
TrueAsync cannot change that. And no one else can change it either.
So why is this being treated as an argument against it?Don't forget that other code don't have control over the plugin and it
might not even know that async is used there.
Exactly. This means that right now I can use Fiber plus select() to
write async code and break WordPress.
And I can also write a plugin that divides by zero and crashes
WordPress, and WordPress won’t know anything about it.So I'm not sure if this design is compatible with WordPress
Yes, WordPress is not compatible with asynchrony. I’ll emphasize
again. This is not about TrueAsync specifically. This is about
asynchrony itself. Yes, WordPress and Laravel are not compatible with
concurrent execution. That’s true.
But are you really suggesting that because of this, all other
applications should be denied the ability to use it?
Moreover, would you deny WordPress itself the possibility of
supporting asynchrony in future? After all, WordPress can be
refactored. It’s not carved in stone.Wouldn’t WordPress benefit from the performance improvements that
async provides?
Wouldn’t WordPress plugins benefit from being able to actively
communicate with microservices and deliver the fastest possible
responses to JavaScript?Is this feature really something nobody needs? If yes, then I have no
further questions.Async is needed to increase throughput. That’s the purpose. If a PHP
project doesn’t need it, it doesn’t have to use it. That’s fine. But
there are PHP projects that do need it.
The point you’re trying to make here is very clouded for me. Although you
can use AMPHP to break Wordpress, there is an extensive process of “opt-in”
for you to do that and it doesn’t happen naturally, accidentally or
unintentionally. You’ll have to setup a plugin that requires AMPHP and
write some code that will inadvertently break and never really see the
light of day.
Now there’s 2 possibilities for us to go from here: my understanding of the
most basic concept of TrueAsync is still wrong OR the comparison you’re
trying to make isn’t relevant.
My understanding is that if this RFC were to be implemented as-is, you
would be able to open any PHP file and write \Async\spawn() and: nothing
would blow up but any code inside the project that touches global state
could silently misbehave. Is this not true?
My understanding of AMPHP is that things only become async if you
explicitly run AMPHP as your web server. It either has no effect if your
application is running Apache + mod_php OR your application 100% breaks
with nothing getting executed at all. Is this not true?
I know you're tired of debating very basic things, but from the
conversation on the list, I think I'm not the only one confused by this. We
did talk about our desire to have Async on PHP-FPM and as far as I remember
you mentioned it not being very useful, but that it would be possible
nonetheless. This is the a core principle that matters a lot.
Here is a very simple hypothetical:
Suppose I run a website and I have very little understanding of PHP, but I
know the basics. Every Monday I run composer update, do a little testing
and deploy the website. Suppose next Monday when I run composer update
one of my dependencies (Package A) starts to pull in AMPHP as its nested
dependency. When I try to open my website locally it will either have
everything broken or it will just work regularly and nothing will be
running async. There is no other option.
Now suppose the same example but with PHP 9 + TrueAsync. I do composer update one day and test my home page and it works. But deeply nested
somewhere there is a feature that will use Package A which then spawns a
coroutine and leads to my global state code behaving weirdly. It's not a
fatal error. It's not very clear at all to me that something changed, but
now my array indexes are triggering DB updates out of order.
If my assumptions about TrueAsync are completely wrong and that's not
possible at all, I think that's not clear for everyone. But if my
assumptions are right, then your comparison with AMPHP is not the same
thing.
Hello.
To use AMPHP you don’t need to run its server. But let’s simplify the situation.
You have Fiber.
With Fiber you can build an EventLoop + Scheduler, create two
coroutines, and then run code inside them that, for example, renders a
template.
It will require a fair amount of code, but it’s possible.
At the same time, ob_start/ob_end will immediately break, because they
don’t support Fiber switching. This is a known bug.
And if you pass global state into closures, and that state changes
unpredictably, the application logic will essentially break.
What’s different with TrueAsync
- You don’t need to write a lot of code. Yes, just spawn();
-
ob_startandob_endwill work correctly. - If closures share a variable that multiple coroutines write to
unpredictably, the application will still break.
In other words, no matter how asynchrony is implemented in PHP (and
other langs), it will always require correct handling of shared
memory.
And I want to clarify a bit. This is not about a global variable. It’s
about shared memory. These are not the same things. A local variable
can be passed into a closure by reference.
But if you don’t call WP() inside a coroutine, nothing prevents you
from using asynchrony.
In TrueAsync there is a Dockerfile for FPM, you can install it locally
and try running WordPress. I’m sure something will break, but not
everything at once :)
My understanding of AMPHP is that things only become async if you explicitly run AMPHP as your web server. It either has no effect
if your application is running Apache + mod_php OR your application 100% breaks with nothing getting executed at all. Is this not true?
You can use it without a server. The only difference is that the
coroutines won’t live longer than the HTTP request. The same applies
to TrueAsync when running under FPM.
When I try to open my website locally it will either have everything broken or it will just work regularly and nothing will be running async. There is no other option.
It won’t break on its own.
It will only break if that “something” shares a variable or resource
with your code. If you write code according to SOLID principles, the
chance of breakage is very low.
If someone writes coroutine code and completely ignores memory
handling, it’s the same as writing an infinite loop. Can they do that?
Yes. Can you download that code? Yes. Can it break everything? It can.
What’s the conclusion?
I do
composer updateone day and test my home page and it works. But deeply nested somewhere there is a feature that will use Package A which
then spawns a coroutine and leads to my global state code behaving weirdly. It's not a fatal error. It's not very clear at all to me that something changed,
but now my array indexes are triggering DB updates out of order.
The exact same thing will happen in code without coroutines if, for
example, you have a class, you pass an array to it by reference,
and that class silently corrupts your values. You don’t need
asynchrony for that to happen.
<?php
class Storage
{
private array &$data; // holds a reference to the external array
public function __construct(array &$data)
{
// store the reference
$this->data = &$data;
}
public function loadUser()
{
// looks like a harmless read
return $this->data['user'] ?? null;
}
public function updateInternally()
{
// silently mutates the external array
$this->data['user'] = 'hacked';
$this->data['counter'] = ($this->data['counter'] ?? 0) + 1;
}
}
// ---------------------
$shared = [
'user' => 'admin',
'counter' => 0,
];
// create the class, passing the array by reference
$storage = new Storage($shared);
echo $storage->loadUser() . PHP_EOL; // admin
// somewhere else this method is called, modifying the shared array
$storage->updateInternally();
print_r($shared);
// the array is now corrupted unexpectedly
There is no asynchrony in the example above. It’s just normal code. It
breaks application logic because the class secretly holds the array
and modifies it without permission.
And a coroutine is just a regular object. If you pass a referenced
array into a coroutine and then say something “accidentally” broke, it
wasn’t accidental. Did the array get there by accident? No. Did some
developer write the code by accident? No. It’s a bit different from
synchronous code, but the meaning is exactly the same.
I have a mental habit: when I write asynchronous code, I always assume
that any object shared between coroutines can change at any moment.
Similarly, if you hand off memory ownership to another class or share
memory, you have to keep in mind that there is a chance some developer
might accidentally do something wrong.
The only difference between synchronous and asynchronous code is that
you don’t have a precise moment in time when the change happens. And
yes, that makes debugging harder. It makes finding bugs harder. But
it’s not something fundamentally new.
Yes, making the same mistakes with AMPHP is harder, because you need
to install a whole package and so on. But that’s not a guarantee :)
I have a mental habit: when I write asynchronous code, I always assume
that any object shared between coroutines can change at any moment.
Similarly, if you hand off memory ownership to another class or share
memory, you have to keep in mind that there is a chance some developer
might accidentally do something wrong.The only difference between synchronous and asynchronous code is that
you don’t have a precise moment in time when the change happens. And
yes, that makes debugging harder. It makes finding bugs harder. But
it’s not something fundamentally new.
I don't know how else to describe it, but I decided to give a last attempt
here since you mentioned your mental habit: having a mental habit about
async code makes perfect sense. What I'm trying to say is that most PHP
developers don't have that mental habit and that's not a problem because
Async code in PHP doesn't come into your project out of nowhere. With
spawn() being native, anytime you do "composer update" your project may
start running async code without your consent and without warning you that
you should go through a mental habit that you don't even know exist because
you never had to worry about async code before in your life.
Having spawn() be an extremely easy way to start running async code is a
great feature (not a bug). But the very first thing that seems to be
missing is: I'm a dumb PHP developer that don't know and don't care about
async php and when I upgrade to PHP X.Y, how do I keep my project
always-sync without risking one of my composer packages suddenly calling
spawn() and causing bugs I have no idea how to even begin to understand?
ini settings are very frowned upon, but for the sake of a conversation, we
can think of it like that. if I do async.enabled = 0, then nothing changes
for me. Having this opt-in/opt-out control would mean that a broken website
caused by spawn() only happens if they decide to enable async and they can
start learning and developing their mental habit about how to work with
async code.
--
Marco Deleu
Hello
how do I keep my project always-sync without risking one of my composer packages suddenly calling spawn() and causing bugs I have no idea how to even begin to understand?
This idea was mentioned a bit earlier in the discussion. You can use a
special setting to disable asynchrony. There are many different
possibilities here. For example, you can tell Composer not to use
async packages. And so on. You can come up with your own protection
mechanisms. There is no technical problem here in terms of
implementation.
Why not enable asynchronous mode in PHP explicitly through php.ini?
I would follow this logic:
- Since this is a new feature, it should be disabled by default.
- Therefore, to use it, you must explicitly enable it.
For example, you can tell Composer not to use async packages.
This is a good idea that could save people from headaches about
backward compatibility.
It could be specified as a special dependency, like ext-async.
Hello
how do I keep my project always-sync without risking one of my composer
packages suddenly calling spawn() and causing bugs I have no idea how to
even begin to understand?This idea was mentioned a bit earlier in the discussion. You can use a
special setting to disable asynchrony. There are many different
possibilities here. For example, you can tell Composer not to use
async packages. And so on. You can come up with your own protection
mechanisms. There is no technical problem here in terms of
implementation.
Why not enable asynchronous mode in PHP explicitly through php.ini?
I'm not sure if INI is going to be successful. I would bet that it is going
to get rejected as PHP has been moving against introducing INI for language
/ extension behavior changes.
It reminds to some extend the scalar type hints with strict / non strict
discussion that ended up using declare keyword. So maybe async could use it
too and it could be enabled per file in a similar way as strict types. I
haven't thought if it would always make sense for async but for projects
that want to be fully async, it would need to be declared in each file.
Cheers
Jakub
Hello, from a Brazil that is (as always) very, very, very hot.
I believe that using a php.ini flag would be the approach that makes the
most sense, especially in an experimental stage. Since that isn’t possible,
I think having a function inside the Async namespace is the best
alternative, something like Async\enable(), without a corresponding
Async\disable(), of course.
Sincerely,
Luís Vinícius.
Em sex., 21 de nov. de 2025, 16:45, Jakub Zelenka bukka@php.net escreveu:
Hello
how do I keep my project always-sync without risking one of my composer
packages suddenly calling spawn() and causing bugs I have no idea how to
even begin to understand?This idea was mentioned a bit earlier in the discussion. You can use a
special setting to disable asynchrony. There are many different
possibilities here. For example, you can tell Composer not to use
async packages. And so on. You can come up with your own protection
mechanisms. There is no technical problem here in terms of
implementation.
Why not enable asynchronous mode in PHP explicitly through php.ini?I'm not sure if INI is going to be successful. I would bet that it is
going to get rejected as PHP has been moving against introducing INI for
language / extension behavior changes.It reminds to some extend the scalar type hints with strict / non strict
discussion that ended up using declare keyword. So maybe async could use it
too and it could be enabled per file in a similar way as strict types. I
haven't thought if it would always make sense for async but for projects
that want to be fully async, it would need to be declared in each file.Cheers
Jakub
I'm not sure if INI is going to be successful. I would bet that it is going
to get rejected as PHP has been moving against introducing INI for language
/ extension behavior changes.
I think Edmond is right that exactly how the "off switch" should look is a ticket for later in the project. A ticket that blocks the final release, but doesn't have a deadline other than that.
The important thing for now is to have a general model of how code behaves when the switch in the "off" position, and whether or not sync and async code can run in the same thread, because that has an impact on how other parts of the design progress.
Rowan Tommins
[IMSoP]
Rowan Tommins [IMSoP] imsop.php@rwec.co.uk hat am 21.11.2025 21:58 CET geschrieben:
I'm not sure if INI is going to be successful. I would bet that it is going
to get rejected as PHP has been moving against introducing INI for language
/ extension behavior changes.I think Edmond is right that exactly how the "off switch" should look is a ticket for later in the project. A ticket that blocks the final release, but doesn't have a deadline other than that.
The important thing for now is to have a general model of how code behaves when the switch in the "off" position, and whether or not sync and async code can run in the same thread, because that has an impact on how other parts of the design progress.
Rowan Tommins
[IMSoP]
Hello,
from userland perspective I would prefer explicit declaration on each usage of async over declare(), hooks and INI. This would make reviews, static code analysis, etc. easier. RFCs can be written in small parts for each new function, e.g.
$content = \file_get_contents(); // sync
$promise = \file_get_contents_async(); // async
$promise = \Async\file_get_contents(); // async
$contentOrFalse = curl_exec($ch); // sync
$promise = \curl_exec_async($ch); // async
$promise = \Async\curl_exec($ch); // async
$pdoStatement = $pdo->prepare(); // sync
$pdoStatementAsync = $pdo->prepare_async(); // async
$promise = $pdoStatementAsync->execute()
Best Regards
Thomas
Hello
from userland perspective I would prefer explicit declaration on each usage of async over declare(), hooks and INI. This would make reviews, static code analysis, etc. easier.
RFCs can be written in small parts for each new function, e.g.
This solution has the following consequences.
We have a REST API project that is 10 years old. Recently we learned
that PHP 9 has been released, and we can improve performance. To do
this, we need to:
- Migrate to another web server.
- Wrap the request-handling code in a coroutine.
However, it turns out this doesn’t work. If the code inside the
coroutine doesn’t yield control, then there is no improvement at all.
And we’re like… this technology is useless.
And then one of the developers says: “Guys, we just need to rename all
functions to _async and it will work.”
(And he’s this young optimistic perfectionist programmer who says:
“Then the code will be beautiful, amazing, and blaaaaaazing!”)
Everyone happily writes a script to rename 30-50,000 lines of a
many-years-old project. New bugs appear. But we fix them, hoping for a
better future.
And then it turns out that after renaming the functions nothing works,
because shared memory has to be removed.
In other words, the team spent time on refactoring, and in the end
they still had to do the refactoring that was unavoidable.
What benefits did the developers gain?
- Did the code become clearer? Yes, the code became a bit clearer.
It’s visible that the functions will yield control. - Did the number of bugs decrease?
- Did the amount of code changes become smaller?
- Did API segmentation appear?
People won’t rewrite code unless the business is at risk. And even
then, very often no one does anything. Therefore, the more
complicated the transition to asynchrony is, the less likely it is
to ever happen.
There is a chance that asynchrony could give the language a push
toward greater memory safety and make it more functional in style.
Because features in programming evolve according to the law of
accessibility. The more accessible a feature is, the more likely it is
to be used, and the more widespread it becomes. Go made parallel
programming more common because it made it simpler.
Ed
Edmond Dantes edmond.ht@gmail.com hat am 22.11.2025 05:17 CET geschrieben:
Hello
from userland perspective I would prefer explicit declaration on each usage of async over declare(), hooks and INI. This would make reviews, static code analysis, etc. easier.
RFCs can be written in small parts for each new function, e.g.This solution has the following consequences.
We have a REST API project that is 10 years old. Recently we learned
that PHP 9 has been released, and we can improve performance. To do
this, we need to:
- Migrate to another web server.
- Wrap the request-handling code in a coroutine.
However, it turns out this doesn’t work. If the code inside the
coroutine doesn’t yield control, then there is no improvement at all.
And we’re like… this technology is useless.And then one of the developers says: “Guys, we just need to rename all
functions to _async and it will work.”
(And he’s this young optimistic perfectionist programmer who says:
“Then the code will be beautiful, amazing, and blaaaaaazing!”)Everyone happily writes a script to rename 30-50,000 lines of a
many-years-old project. New bugs appear. But we fix them, hoping for a
better future.
And then it turns out that after renaming the functions nothing works,
because shared memory has to be removed.In other words, the team spent time on refactoring, and in the end
they still had to do the refactoring that was unavoidable.What benefits did the developers gain?
- Did the code become clearer? Yes, the code became a bit clearer.
It’s visible that the functions will yield control.- Did the number of bugs decrease?
- Did the amount of code changes become smaller?
- Did API segmentation appear?
People won’t rewrite code unless the business is at risk. And even
then, very often no one does anything. Therefore, the more
complicated the transition to asynchrony is, the less likely it is
to ever happen.
There is a chance that asynchrony could give the language a push
toward greater memory safety and make it more functional in style.
Because features in programming evolve according to the law of
accessibility. The more accessible a feature is, the more likely it is
to be used, and the more widespread it becomes. Go made parallel
programming more common because it made it simpler.
Ed
Maybe misunderstanding, I'm not proposing coroutines, I'm proposing to add new functions for async io. e.g.
// currently we have
$content = \file_get_contents(); // old function remains: sync, returns string|false
// new function file_get_contents_async
$promise = \file_get_contents_async(); // new function: async, returns a promise object
(similar to node.js using fs.readFile and fs.readFileSync)
Regarding 10 year old projects: I would only add new functions and don't change existing functions for async io. So new async functions can be used, but don't need to be used.
I expect that's already a good improvement for many use cases to read multiple files in parallel, run multiple queries in parallel, etc.
Best Regards
Thomas
Hello
I expect that's already a good improvement for many use cases to read multiple files in parallel, run multiple queries in parallel, etc.
I see. You want to get several promises to wait for?
Then there is a good way to do it without additional functions:
$promise1 = spawn(file_get_content(...), "file1.txt");
$promise2 = spawn(file_get_content(...), "file2.txt");
$promise3 = spawn(file_get_content(...), "file3.txt");
This is equivalent because a coroutine is a Future. In the same way,
you can turn any other function into a Promise without creating a
separate API.
There is a nuance regarding resources. And in certain scenarios,
creating an array of Promises in another way can save memory. For
example, a scenario where you need to handle a large array of sockets.
Such cases require a special API.
Ed
Edmond Dantes edmond.ht@gmail.com hat am 22.11.2025 11:37 CET geschrieben:
Hello
I expect that's already a good improvement for many use cases to read multiple files in parallel, run multiple queries in parallel, etc.
I see. You want to get several promises to wait for?
Then there is a good way to do it without additional functions:$promise1 = spawn(file_get_content(...), "file1.txt"); $promise2 = spawn(file_get_content(...), "file2.txt"); $promise3 = spawn(file_get_content(...), "file3.txt");This is equivalent because a coroutine is a Future. In the same way,
you can turn any other function into a Promise without creating a
separate API.There is a nuance regarding resources. And in certain scenarios,
creating an array of Promises in another way can save memory. For
example, a scenario where you need to handle a large array of sockets.
Such cases require a special API.
Ed
Hello,
function return types should not depend on the outside context (spawn, hook, ini, etc.) because when the code gets more complex, it's very hard to find the outside context.
So file_get_contents() should always return string|false, file_get_contents_async() should always return a promise object.
From the example I would expect it like this:
$promise1 = file_get_content_async("file1.txt");
$promise2 = file_get_content_async("file2.txt");
$promise3 = file_get_content_async("file3.txt");
// do sth else
$content1 = $promise->await();
$content2 = $promise->await();
$content3 = $promise->await();
PHP got very successful by making things easier than in C, this should be the path to continue.
Best Regards
Thomas
Thomas Bley mails@thomasbley.de hat am 22.11.2025 12:42 CET geschrieben:
Edmond Dantes edmond.ht@gmail.com hat am 22.11.2025 11:37 CET geschrieben:
Hello
I expect that's already a good improvement for many use cases to read multiple files in parallel, run multiple queries in parallel, etc.
I see. You want to get several promises to wait for?
Then there is a good way to do it without additional functions:$promise1 = spawn(file_get_content(...), "file1.txt"); $promise2 = spawn(file_get_content(...), "file2.txt"); $promise3 = spawn(file_get_content(...), "file3.txt");This is equivalent because a coroutine is a Future. In the same way,
you can turn any other function into a Promise without creating a
separate API.There is a nuance regarding resources. And in certain scenarios,
creating an array of Promises in another way can save memory. For
example, a scenario where you need to handle a large array of sockets.
Such cases require a special API.
Ed
Hello,
function return types should not depend on the outside context (spawn, hook, ini, etc.) because when the code gets more complex, it's very hard to find the outside context.
So
file_get_contents()should always return string|false, file_get_contents_async() should always return a promise object.From the example I would expect it like this:
$promise1 = file_get_content_async("file1.txt");
$promise2 = file_get_content_async("file2.txt");
$promise3 = file_get_content_async("file3.txt");// do sth else
$content1 = $promise->await();
$content2 = $promise->await();
$content3 = $promise->await();PHP got very successful by making things easier than in C, this should be the path to continue.
Best Regards
Thomas
I'm sorry, the correct example should be:
$promise1 = file_get_content_async("file1.txt");
$promise2 = file_get_content_async("file2.txt");
$promise3 = file_get_content_async("file3.txt");
// do sth else
$content1 = $promise1->await();
$content2 = $promise2->await();
$content3 = $promise3->await();
Best Regards
Thomas
Hello
function return types should not depend on the outside context (spawn, hook, ini, etc.) because when the code gets more complex, it's very hard to find the outside context.
What does “outside context” mean?
I just want to understand the practical use of functions with Promise.
The code above makes sense only if there is awaitAll.
$promise1 = file_get_content_async("file1.txt");
$promise2 = file_get_content_async("file2.txt");
$promise3 = file_get_content_async("file3.txt");
awaitAll($promise1, ....);
But you can achieve exactly the same effect without special functions.
The only difference is that the _async function inside might be
optimized in some way.
Or is there something else?
Ed
Edmond Dantes edmond.ht@gmail.com hat am 22.11.2025 13:01 CET geschrieben:
Hello
function return types should not depend on the outside context (spawn, hook, ini, etc.) because when the code gets more complex, it's very hard to find the outside context.
What does “outside context” mean?
I just want to understand the practical use of functions with Promise.
The code above makes sense only if there is awaitAll.$promise1 = file_get_content_async("file1.txt");
$promise2 = file_get_content_async("file2.txt");
$promise3 = file_get_content_async("file3.txt");awaitAll($promise1, ....);
But you can achieve exactly the same effect without special functions.
The only difference is that the _async function inside might be
optimized in some way.
Or is there something else?
Ed
Hello,
basically in $result = foo(spawn(bar(baz(file_get_contents())))); file_get_contents() receives outside context from spawn() to turn into async mode. Also foo(), bar(), baz() can be in different namespaces, different classes, so by looking at the code calling file_get_contents(), it's not clear if the result is sync or async.
Another example:
$foo = file_get_contents('foo.txt');
$result = foo($foo);
should be equal to:
$result = foo(file_get_contents('foo.txt'));
but having:
$foo = file_get_contents('foo.txt'); // sync
$result = spawn($foo); // error because $foo is string
would not be eqaul to:
$result = spawn(file_get_contents('foo.txt')); // async
Best Regards
Thomas
basically in $result = foo(spawn(bar(baz(file_get_contents()))));
file_get_contents()receives outside context from spawn() to turn into async mode.
Also foo(), bar(), baz() can be in different namespaces, different classes, so by looking at the code callingfile_get_contents(), it's not clear if the result is sync or async.
Ok, then let’s look in detail at what is happening.
foo(spawn(bar(baz(file_get_contents()))))
i.e.
$result = foo(spawn(bar(...), fn () => baz(file_get_contents())));
Did I understand the code correctly?
(Assume there was also some parameter there, like a file name.)
- We call
barin a separate coroutine, which - First calls
file_get_contents - Then passes the result to the function
baz()
Is that correct?
Also foo(), bar(), baz() can be in different namespaces, different classes, so by looking at the code calling
file_get_contents(), it's not clear if the result is sync or async.
If we are discussing the code above, then it returns a Promise not of
the file-reading result.
This is a completely different logic, and here the programmer clearly
intended to do something else.
What if the function baz replaces every a character with baz? It's ok.
$foo = file_get_contents('foo.txt'); // sync
$result = spawn($foo); // error because $foo is string
Here I don’t understand why someone would intentionally write incorrect code.
Code:
$result = foo(file_get_contents('foo.txt'));
// equivalent to
// the code below has no practical purpose
$result = foo(await(spawn(file_get_contents(...), 'foo.txt')));
Is that correct?
Edmond Dantes edmond.ht@gmail.com hat am 22.11.2025 13:37 CET geschrieben:
basically in $result = foo(spawn(bar(baz(file_get_contents()))));
file_get_contents()receives outside context from spawn() to turn into async mode.
Also foo(), bar(), baz() can be in different namespaces, different classes, so by looking at the code callingfile_get_contents(), it's not clear if the result is sync or async.Ok, then let’s look in detail at what is happening.
foo(spawn(bar(baz(file_get_contents()))))
i.e.
$result = foo(spawn(bar(...), fn () => baz(file_get_contents())));Did I understand the code correctly?
(Assume there was also some parameter there, like a file name.)
- We call
barin a separate coroutine, which- First calls
file_get_contents- Then passes the result to the function
baz()Is that correct?
Also foo(), bar(), baz() can be in different namespaces, different classes, so by looking at the code calling
file_get_contents(), it's not clear if the result is sync or async.
If we are discussing the code above, then it returns a Promise not of
the file-reading result.
This is a completely different logic, and here the programmer clearly
intended to do something else.
What if the function baz replaces every a character with baz? It's ok.$foo = file_get_contents('foo.txt'); // sync $result = spawn($foo); // error because $foo is stringHere I don’t understand why someone would intentionally write incorrect code.
Code:
$result = foo(file_get_contents('foo.txt')); // equivalent to // the code below has no practical purpose $result = foo(await(spawn(file_get_contents(...), 'foo.txt')));Is that correct?
So I guess you want to use spawn() in a similar way as call_user_func() works.
This changes the behavior of file_get_contents() from the outside, so it gives file_get_contents() async context from the outside to behave differently.
Assuming every io operation inside spawn() runs async, I guess it's not possible to mix sync and async io inside of one function.
As mentioned before I'm not suggesting to use coroutines for async io. My recommendation would be implementing new functions for async io and a promise object. Having everything that's executed async explicitly named "_async" helps a lot when reading and understanding userland code.
Regarding Coroutines in Go from userland perspective: the idea is nice but often fails with deadlocks, hanging processes and having to run a "Data Race Detector" all the time. Sure it's always the developers fault, but it happens too often.
Best Regards
Thomas
So I guess you want to use spawn() in a similar way as
call_user_func()works.
yes
This changes the behavior of
file_get_contents()from the outside
No.
function file_get_contents(string $filename): string
{
$fh = `fopen()`;
// It creates an EPOLL event so it can wake us when the data
becomes available.
$event = ReactorAPI.create_event_from($fh);
$waker = Scheduler.getCurrentWaker();
// Event Driven logic inside.
$waker.add_event($event, function() use($waker) {
// Wakeup this coroutine
$waker.wake();
});
// suspend current coroutine
// zz..... z.....
Scheduler.suspend();
// Continue here after the IO event
// Now we have date, return
return fread($fh, ....);
}
This is pseudocode. You can assume it always works.
If you call file_get_contents directly, it behaves the same way.
So it does not matter where file_get_contents is called.
Since all PHP code together with TrueAsync runs inside coroutines,
file_get_contents will suspend the coroutine in which it was invoked.
When you call spawn, you simply run the function in another
coroutine, not in your own. But spawn has no effect on
file_get_contents.
We’re not at risk of DataRace yet :) We don’t have multithreading.
And most likely it won’t appear anytime soon.
So I guess you want to use spawn() in a similar way as
call_user_func()works.
yesThis changes the behavior of
file_get_contents()from the outside
No.function file_get_contents(string $filename): string { $fh = `fopen()`; // It creates an EPOLL event so it can wake us when the data becomes available. $event = ReactorAPI.create_event_from($fh); $waker = Scheduler.getCurrentWaker(); // Event Driven logic inside. $waker.add_event($event, function() use($waker) { // Wakeup this coroutine $waker.wake(); }); // suspend current coroutine // zz..... z..... Scheduler.suspend(); // Continue here after the IO event // Now we have date, return return fread($fh, ....); }This is pseudocode. You can assume it always works.
If you callfile_get_contentsdirectly, it behaves the same way.
So it does not matter wherefile_get_contentsis called.
Since all PHP code together with TrueAsync runs inside coroutines,
file_get_contentswill suspend the coroutine in which it was invoked.When you call
spawn, you simply run the function in another
coroutine, not in your own. Butspawnhas no effect on
file_get_contents.We’re not at risk of DataRace yet :) We don’t have multithreading.
And most likely it won’t appear anytime soon.
We are in data-race territory though:
spawn(fn() => file_put_contents('file', 'long string'))
spawn(fn() => file_get_contents('file'))
I’m on a phone, so I’m not sure I got all the syntax right, but hopefully my intent is clear. But if these run “concurrently” the scheduler will theoretically batch the reading/writing of bytes in an interleaved way, causing absolute chaos and corruption.
The same thing would happen with db drivers that typically use a token to keep track of which response goes to which request (I maintain an async db driver for amphp). There will be a need to ensure the stream cannot be interleaved with other coroutines. I can do this with amphp locks, but there isn’t even a semaphore implementation to build a lock around.
— Rob
But if these run “concurrently” the scheduler will theoretically batch the reading/writing of bytes in an interleaved way, causing absolute chaos and corruption.
Yes of course, that’s exactly what will happen.
Edmond Dantes edmond.ht@gmail.com hat am 22.11.2025 14:55 CET geschrieben:
So I guess you want to use spawn() in a similar way as
call_user_func()works.
yesThis changes the behavior of
file_get_contents()from the outside
No.function file_get_contents(string $filename): string { $fh = `fopen()`; // It creates an EPOLL event so it can wake us when the data becomes available. $event = ReactorAPI.create_event_from($fh); $waker = Scheduler.getCurrentWaker(); // Event Driven logic inside. $waker.add_event($event, function() use($waker) { // Wakeup this coroutine $waker.wake(); }); // suspend current coroutine // zz..... z..... Scheduler.suspend(); // Continue here after the IO event // Now we have date, return return fread($fh, ....); }This is pseudocode. You can assume it always works.
If you callfile_get_contentsdirectly, it behaves the same way.
So it does not matter wherefile_get_contentsis called.
Since all PHP code together with TrueAsync runs inside coroutines,
file_get_contentswill suspend the coroutine in which it was invoked.When you call
spawn, you simply run the function in another
coroutine, not in your own. Butspawnhas no effect on
file_get_contents.We’re not at risk of DataRace yet :) We don’t have multithreading.
And most likely it won’t appear anytime soon.
// Continue here after the IO event
From my understanding, the code does not continue if there is no io event? Will it use default_socket_timeout from php.ini and/or use the timeout specified in the stream context?
Can I mix sync IO and async IO in one function? e.g. if the server uses a mixed storage of SSDs and HDDs and I only want async io for the SSDs?
Best Regards
Thomas
From my understanding, the code does not continue if there is no io event?
Yes
Will it use default_socket_timeout from php.ini and/or use the timeout specified in the stream context?
There are many more different cases in the real C code.
For sockets, I remember it uses a timeout.
For filesystem files, I think there is no such thing.
Can I mix sync IO and async IO in one function?
In essence, there is no more sync I/O.
Any input/output is potentially considered asynchronous.
Launching multiple coroutines lets the programmer create several I/O
operations that run asynchronously.
Thus, the degree of asynchrony is equal to the number of coroutines.
The code inside a coroutine creates the illusion of step-by-step
sequential execution with no extra effort.
Therefore, in this model the programmer writes less code and uses
Promises less often.
if the server uses a mixed storage of SSDs and HDDs and I only want async io for the SSDs?
How will PHP understand which type of storage it is dealing with?
Can I mix sync IO and async IO in one function?
To be more precise, the idea is that ideally all input and output must
be non blocking and must work through an EventLoop. In that case an
application that performs I O often receives the maximum benefit from
coroutines. From the PHP process point of view the application is
always asynchronous. From the coroutine point of view it is
synchronous.
The thinking of a programmer who works with such code is different
from JavaScript. Here I do not think about which code will be
asynchronous, I think about how many functions I can run in the
background independently of each other. Because all code is already
asynchronous. The only question is how many virtual logical threads I
want to divide it into. This philosophy is better for business logic
code but worse for systems programming. That is why I chose it.
use Async\spawn;
use Async\Channel;
function worker(Channel $tasks) {
// Start a process for this coroutine
$proc = proc_open("php worker.php", [
["pipe","r"],
["pipe","w"],
["pipe","w"]
], $pipes);
$stdin = $pipes[0];
$stdout = $pipes[1];
while (true) {
// Receive a task (this suspends the coroutine)
$task = $tasks->recv();
// Write task to process (suspends while writing)
fwrite($stdin, $task . "\n");
// Read result (suspends while waiting for data)
$result = fgets($stdout);
... make some else
}
}
$tasks = new Channel();
// Start N workers
for ($i = 0; $i < 10; $i++) {
spawn(worker(...), $tasks);
}
// Producer...
$tasks->push(...);
$tasks->push(...);
$tasks->push(...);
// Close Channel auto after leave function
Please look at this code.
There are three or five other ways to implement it. And most of them
would likely be worse in terms of reliability. Because in this example
there is almost no asynchronous code at all. Everything is sequential.
There is no synchronization. There is nothing. Just a channel and just
coroutines. That is all. And inside each coroutine the code is
synchronous.
And you do not even need any structural concurrency. The task is
solved because when the channel is destroyed, an exception is thrown
and the coroutines will terminate together with the rest of the code.
Of course, if you add automatic process restart, it becomes a bit more
complex, but the code is still synchronous. Which is very good.
Imagine a person who has never worked with async before. How hard
would it be for them to write this code?
Ed
I've been thinking about this quite a bit and I'm still not quite sure
we need a feature flag to enable async behavior.
@Ed Unless something calls spawn all I/O is going to be blocking &
non-concurrent, correct?
@Deleu Since you currently can't use spawn; no libraries have it and
therefore if you're a library developer and you implement async
behavior after this feature has been released you'll probably release
a new major version of your library signalling the new version is not
backwards compatible. Currently, if you require a new library you need
to read the documentation to see how it works as well...
Also, I'm not quite sure what we would want to happen if async is
disabled but you're requiring a library that spawns coroutines. Surely
the library won't work as designed?
Best Regards,
Bart Vanhoutte
Op za 22 nov 2025 om 17:01 schreef Edmond Dantes edmond.ht@gmail.com:
use Async\spawn; use Async\Channel; function worker(Channel $tasks) { // Start a process for this coroutine $proc = proc_open("php worker.php", [ ["pipe","r"], ["pipe","w"], ["pipe","w"] ], $pipes); $stdin = $pipes[0]; $stdout = $pipes[1]; while (true) { // Receive a task (this suspends the coroutine) $task = $tasks->recv(); // Write task to process (suspends while writing) fwrite($stdin, $task . "\n"); // Read result (suspends while waiting for data) $result = fgets($stdout); ... make some else } } $tasks = new Channel(); // Start N workers for ($i = 0; $i < 10; $i++) { spawn(worker(...), $tasks); } // Producer... $tasks->push(...); $tasks->push(...); $tasks->push(...); // Close Channel auto after leave functionPlease look at this code.
There are three or five other ways to implement it. And most of them
would likely be worse in terms of reliability. Because in this example
there is almost no asynchronous code at all. Everything is sequential.
There is no synchronization. There is nothing. Just a channel and just
coroutines. That is all. And inside each coroutine the code is
synchronous.And you do not even need any structural concurrency. The task is
solved because when the channel is destroyed, an exception is thrown
and the coroutines will terminate together with the rest of the code.Of course, if you add automatic process restart, it becomes a bit more
complex, but the code is still synchronous. Which is very good.Imagine a person who has never worked with async before. How hard
would it be for them to write this code?
Ed
Hello.
@Ed Unless something calls
spawnall I/O is going to be blocking &
non-concurrent, correct?
Yes.
If no one calls spawn, this is equivalent to the code running inside a
single coroutine.
At the moment, TrueAsync has an internal flag that allows it to be
enabled or disabled. If Async is disabled, an exception will be
thrown.
So essentially this capability already exists in the code, and the
global flag that turns the feature on or off was inherited from the
early versions of the library.
@Ed Unless something calls
spawnall I/O is going to be blocking &
non-concurrent, correct?
Yes.
If no one calls spawn, this is equivalent to the code running inside a
single coroutine.At the moment, TrueAsync has an internal flag that allows it to be
enabled or disabled. If Async is disabled, an exception will be
thrown.
I think there's a lot of confusion in this thread because different
people are talking about different scenarios. Perhaps it would be useful
to introduce some User Stories...
Async Alice is working on a brand new application written in PHP 9, and
is designing it from the ground up to make use of async capabilities
wherever possible. She wants third-party libraries to use async I/O so
that she can use them in her design.
Beginner Bob has a recently built application, and thinks there's an
opportunity to improve it with async I/O, but doesn't know anything
about it. He wants a simple-to-use API that lets him get the benefits,
and clear instructions on what pitfalls to look out for.
Legacy Les is maintaining a 20-year-old business back office system,
which makes extensive use of global state and does not have good
automated testing. He wants to run it under PHP 9, and to use up-to-date
third-party libraries for new functionality, without an expensive and
risky rewrite of existing code.
Finally, SDK Susie is publishing the official PHP library for a popular
cloud API. She wants to serve the best version she can for Alice, Bob,
and Les, but doesn't want to maintain separate "sync" and "async"
branches of the library or its methods.
Feel free to create more personas if you want to talk about additional
scenarios.
The first thing I want to clarify is that SDK Susie doesn't necessarily
need to change the public methods of her library; she can still use
async I/O internally. For instance, if a method already returns an
Iterator to silently fetch a page of results at a time, that can be
changed to store Promises internally, and await them when the data is
needed.
However, she might want to mark it as a breaking change anyway, so
that Async Alice and Beginner Bob know they are opting into it.
Legacy Les won't get it until he opts in, but at some point he will need
a new version of the library for other reasons (e.g. because the cloud
API becomes incompatible with the old library version); so he still
needs a way to run it safely.
If he runs PHP in a mode where any attempt to use async I/O throws an
error, he still can't use the new version of the library, so this
doesn't help him.
However, if Legacy Les can run PHP in a mode where any attempt to use
async I/O is automatically run synchronously, then he will be happy:
he can run his legacy application under PHP 9, and use the updated
library, without worrying about async code.
Beginner Bob doesn't want to run his whole application in "sync only"
mode, but might want to switch parts of it, so that he doesn't have to
think about them yet. So a scoped, rather than global, switch might be
useful for him.
This is how I picture that mode working: when SDK Susie's library code
calls "spawn", a Coroutine is created as normal. However, when it
suspends, the Scheduler immediately resumes it, rather than switching to
a different Coroutine. The library code will see the Coroutine object it
expects, but passing it to "await" will immediately produce its result.
However, I might well be misunderstanding something, and this is either
impossible or difficult to implement. If so, I think some other solution
to Legacy Les's requirements is needed.
I hope this description is useful.
--
Rowan Tommins
[IMSoP]
Hello,
I have serious concerns about any approach that may cause the event loop to
block. The correct ways to handle this should not involve blocking the loop
— whether globally or in a scoped manner — while attempting to let
synchronous and asynchronous code coexist in the same environment. More
appropriate alternatives would be offloading execution to another thread
(which is far too complex for the current VM and ecosystem) or relying on
preemption (which is not feasible to implement here).
In addition, the cascading effects of such an approach are significant:
libraries would be forced to implement internal mechanisms just to deal
with unexpected loop stalls and similar issues.
It would be much healthier for new libraries to be designed specifically
for the asynchronous environment, in the same way Laravel created Octane to
run on top of Swoole.
Kind regards,
Luís Vinícius.
Rowan Tommins [IMSoP] imsop.php@rwec.co.uk escreveu (domingo, 23/11/2025
à(s) 18:58):
@Ed Unless something calls
spawnall I/O is going to be blocking &
non-concurrent, correct?Yes.
If no one calls spawn, this is equivalent to the code running inside a
single coroutine.At the moment, TrueAsync has an internal flag that allows it to be
enabled or disabled. If Async is disabled, an exception will be
thrown.I think there's a lot of confusion in this thread because different people
are talking about different scenarios. Perhaps it would be useful to
introduce some User Stories...Async Alice is working on a brand new application written in PHP 9, and is
designing it from the ground up to make use of async capabilities wherever
possible. She wants third-party libraries to use async I/O so that she can
use them in her design.Beginner Bob has a recently built application, and thinks there's an
opportunity to improve it with async I/O, but doesn't know anything about
it. He wants a simple-to-use API that lets him get the benefits, and clear
instructions on what pitfalls to look out for.Legacy Les is maintaining a 20-year-old business back office system, which
makes extensive use of global state and does not have good automated
testing. He wants to run it under PHP 9, and to use up-to-date third-party
libraries for new functionality, without an expensive and risky rewrite of
existing code.Finally, SDK Susie is publishing the official PHP library for a popular
cloud API. She wants to serve the best version she can for Alice, Bob, and
Les, but doesn't want to maintain separate "sync" and "async" branches of
the library or its methods.Feel free to create more personas if you want to talk about additional
scenarios.The first thing I want to clarify is that SDK Susie doesn't necessarily
need to change the public methods of her library; she can still use async
I/O internally. For instance, if a method already returns an Iterator to
silently fetch a page of results at a time, that can be changed to store
Promises internally, and await them when the data is needed.However, she might want to mark it as a breaking change anyway, so
that Async Alice and Beginner Bob know they are opting into it.Legacy Les won't get it until he opts in, but at some point he will need a
new version of the library for other reasons (e.g. because the cloud API
becomes incompatible with the old library version); so he still needs a way
to run it safely.If he runs PHP in a mode where any attempt to use async I/O throws an
error, he still can't use the new version of the library, so this doesn't
help him.However, if Legacy Les can run PHP in a mode where any attempt to use
async I/O is automatically run synchronously, then he will be happy: he
can run his legacy application under PHP 9, and use the updated library,
without worrying about async code.Beginner Bob doesn't want to run his whole application in "sync only"
mode, but might want to switch parts of it, so that he doesn't have to
think about them yet. So a scoped, rather than global, switch might be
useful for him.This is how I picture that mode working: when SDK Susie's library code
calls "spawn", a Coroutine is created as normal. However, when it suspends,
the Scheduler immediately resumes it, rather than switching to a different
Coroutine. The library code will see the Coroutine object it expects, but
passing it to "await" will immediately produce its result.However, I might well be misunderstanding something, and this is either
impossible or difficult to implement. If so, I think some other solution to
Legacy Les's requirements is needed.I hope this description is useful.
--
Rowan Tommins
[IMSoP]
On Sun, 23 Nov 2025 at 21:29 Luís Vinícius Santos da Costa Barros <
luisvscbarros@gmail.com> wrote:
Hello,
I have serious concerns about any approach that may cause the event loop
to block. The correct ways to handle this should not involve blocking the
loop — whether globally or in a scoped manner — while attempting to let
synchronous and asynchronous code coexist in the same environment. More
appropriate alternatives would be offloading execution to another thread
(which is far too complex for the current VM and ecosystem) or relying on
preemption (which is not feasible to implement here).In addition, the cascading effects of such an approach are significant:
libraries would be forced to implement internal mechanisms just to deal
with unexpected loop stalls and similar issues.It would be much healthier for new libraries to be designed specifically
for the asynchronous environment, in the same way Laravel created Octane to
run on top of Swoole.
If I could summarize months of discussion on this RFC, the primary topic
would be the consensus that we do not want to have a split in the
community. If every PHP package needs an adapter to be async compatible
that essentially means the efforts to not break the community in half has
failed.
A different way to look at it may be that it’s not so much about blocking
the event loop, but rather not even having an event loop in the first place
(in case you’re running sync-PHP). And if you’re partially running async at
an isolated area, then once that returns and all coroutines are finished
the event loop may also finish and we’re back to sync.
If I could summarize months of discussion on this RFC, the primary topic
would be the consensus that we do not want to have a split in the
community. If every PHP package needs an adapter to be async compatible
that essentially means the efforts to not break the community in half has
failed.
Yes, this was precisely the point of my "SDK Susie" user story: Susie is not interested in maintaining multiple libraries for different parts of the community, she just wants her API to be usable by as many PHP developers as possible.
If she can't use async I/O without breaking Legacy Les's application, she will simply not use it. Async Alice might write her own code for the API calls she needs, but won't want to maintain a fork of the entire library. Beginner Bob will use the official library and not get any async benefits.
This is basically the situation we already have, with Async Alice using Swoole or ReactPHP.
Rowan Tommins
[IMSoP]
Will Legacy Les see a side effect when he upgrades to SDK Susie's new
async library?
For clarity, Susie's code has turned from something sync like:
function getFoo(): Foo
{
$resultOne = sendHttpRequest('one');
$resultTwo = sendHttpRequest('two');
return new Foo($resultOne, $resultTwo);
}
into something async like:
function getFoo(): Foo
{
$results = await Async\all([
spawn sendHttpRequest('one'),
spawn sendHttpRequest('two')
]);
return new Foo($results[0], $results[1]);
}
Is that correct Rowan?
Op ma 24 nov 2025 om 12:56 schreef Rowan Tommins [IMSoP] <
imsop.php@rwec.co.uk>:
If I could summarize months of discussion on this RFC, the primary topic
would be the consensus that we do not want to have a split in the
community. If every PHP package needs an adapter to be async compatible
that essentially means the efforts to not break the community in half has
failed.Yes, this was precisely the point of my "SDK Susie" user story: Susie is
not interested in maintaining multiple libraries for different parts of the
community, she just wants her API to be usable by as many PHP developers as
possible.If she can't use async I/O without breaking Legacy Les's application, she
will simply not use it. Async Alice might write her own code for the API
calls she needs, but won't want to maintain a fork of the entire library.
Beginner Bob will use the official library and not get any async benefits.This is basically the situation we already have, with Async Alice using
Swoole or ReactPHP.Rowan Tommins
[IMSoP]
Will Legacy Les see a side effect when he upgrades to SDK Susie's new async library?For clarity, Susie's code has turned from something sync like:
function getFoo(): Foo { $resultOne = sendHttpRequest('one'); $resultTwo = sendHttpRequest('two'); return new Foo($resultOne, $resultTwo); }into something async like:
function getFoo(): Foo { $results = await Async\all([ spawn sendHttpRequest('one'), spawn sendHttpRequest('two') ]); return new Foo($results[0], $results[1]); }Is that correct Rowan?
It could be as simple as that, but it might also have Coroutine / Future
objects held internally rather than immediately awaited, e.g. she might
have changed this:
function getNextResult(): Result
{
if ( $this->needNextPage() ) {
$this->currentPageOfResults = $this->fetchNextPage();
}
return array_shift($this->currentPageOfResults);
}
To this:
function getNextResult(): Result
{
if ( $this->needNextPage() ) {
$this->currentPageOfResults = await $this->nextPageFuture;
$this->nextPageFuture = spawn $this->fetchNextPage();
}
return array_shift($this->currentPageOfResults);
}
There might also be interleaved code which is passed in from the
application - i.e. code written by Beginner Bob or Legacy Les. For
instance, an implementation of the PSR-3 LoggerInterface, which might be
referenced inside the fetchNextPage() method which is being run
asynchronously.
[P.S. Reminder: place your reply below an edited quote, not above.]
--
Rowan Tommins
[IMSoP]
Hello.
I think there's a lot of confusion in this thread because different people are talking about different scenarios. Perhaps it would be useful to introduce some User Stories
Yesterday I briefly reviewed the WordPress code to estimate the
refactoring effort.
The amount of global state accessed through global and static is
quite large.
However, it seems technically possible to switch global and static
variables per coroutine.
I’m not sure how this will affect performance (maybe 1–2% on
switches?), but the task looks realistic.
At first I thought this was a bad approach, because if such a feature
were added to PHP,
everyone would start using global as a way to exchange data inside a
coroutine. However, this situation can be viewed from a completely
different perspective:
function myFunction(): void {
global $x;
}
// This code is equivalent to:
function myFunction(): void {
effect ?string $x = null; // <-- function effect, external dependency
}
// This code is equivalent to:
function myFunction(): void in ?string $x {
}
// Then calling the function:
with ($x = "string") {
myFunction();
}
In other words, this is a function effect that turns previously
unmanaged and unsafe code into a function with an explicit
external-dependency contract.
And in the future, it could be improved by adding proper effect
support to the language. It turns out that a feature previously seen
as an antipattern can actually be framed as a modern concept from
functional programming.
Of course, these statements need to be validated with code and testing,
and they also require a separate RFC.
I forgot to add an important point.
If a coroutine can be started with its own separate Globals state,
this means there is a technical possibility to safely run coroutines
in applications with global state, guaranteeing they won’t break
anything.
If the rule of having zero Globals becomes the default rule for
starting coroutines, it makes PHP potentially less dangerous.
It means that data can be passed into a coroutine only explicitly at
creation time, and in no other way.
Of course, we need to consider the potential performance impact of
this logic. But if the performance drop is minimal, this feature could
potentially make the language better and essentially turn PHP into a
language where global state doesn’t exist at all.
After all, any code is code running inside a coroutine.
That means $GLOBALS effectively no longer exists. Memory is always
localized to the execution context.
This property potentially allows coroutines to be moved between
threads in the future, because the memory model begins to look like an
isolated region that belongs to the coroutine.
At the same time, this property does not break existing code,
because everything remains 100% functional inside a coroutine.
But now we gain the ability to run multiple instances of WordPress in
a single PHP thread, if we adjust the startup code.
All of this resembles Erlang and sounds a bit crazy for a PHP
developer, but so far I haven’t been able to come up with a
counterexample that would make this property negative.
Let me repeat the conclusions:
- Each coroutine has its own GLOBALS/Static, and a developer cannot
pass global state into a coroutine. - This forces the developer to pass objects explicitly, because there
are simply no other mechanisms available. -
staticcaching breaks for coroutines. But since coroutines don’t
currently exist, this is not a problem. Developers will just need to
account for it.
staticcaching breaks for coroutines. But since coroutines don’t
currently exist, this is not a problem. Developers will just need to
account for it.
This is exactly the kind of ambiguous statement I was trying to avoid by coming up with persona names and user stories - which developers will need to account for it?
Will Legacy Les see a side effect when he upgrades to SDK Susie's new async library?
Rowan Tommins
[IMSoP]
Hello.
Will Legacy Les see a side effect when he upgrades to SDK Susie's new async library?
That’s the advantage of the idea. Legacy code doesn’t need to do anything.
Let’s assume all PHP code written before PHP9 is code that ran inside
a single coroutine that was implicitly defined.
Let’s say we have a function myFun() with a static variable:
<?php
function myFun(): void {
static $counter = 0;
$counter++;
echo "Counter = {$counter}\n";
}
myFun(); // Counter = 1
myFun(); // Counter = 2
myFun(); // Counter = 3
But if you run this same function in another coroutine…:
```php
function myFun(): void {
static $counter = 0;
$counter++;
echo "counter = {$counter}\n";
}
awaitAll([
spawn(myFun(...)), // Counter = 1
spawn(myFun(...)), // Counter = 1
spawn(myFun(...)), // Counter = 1
]);
A programmer working with coroutines will not be able to use static or
global variables to pass state.
They have only two ways to do it:
- Function parameters
- use to capture variables in a closure
This makes it impossible to accidentally shoot yourself in the foot. A
developer can still do something silly by explicitly passing objects
between coroutines, but now they are doing it consciously.
Although even here we can go further and create something similar to
ownership transfer of an object. In other words, an explicit semantics
that clearly specifies what to do with the object:
- Should the object be moved between coroutines?
- Should the object be cloned?
- Is the object a special shared object that can be safely passed around?
Such semantics makes coroutines thread-safe.
In other words, by designing a special parameter-passing semantics for
coroutines, we can create a perfect specification for coroutines that
can be run both in another thread and in the current one.
At the same time, the memory model becomes equivalent to Erlang’s
model, where there are no shared objects except for specific special
ones.
Such a change requires adding special Shared objects to the
language, which can be safely used across different threads. And
developers will no longer be able to use reference-based variables,
except within something like SharedBox<T>.
At the same time, implementing multi-threading support is not required
immediately. But once this capability is added, the language semantics
will already be fully prepared for it.
So props/cons:
- Coroutines become safe execution containers that cannot
accidentally damage shared memory. - Old code requires no changes.
- New coroutines cannot harm old code. And if parameter-passing
semantics are introduced, they won’t be able to harm it at all. PHP
will forbid a programmer from even trying to pass memory to another
coroutine just like that. - The language semantics make it possible to describe fully
thread-safe code, which can be added in the future at any time without
major changes.
The cost:
- a developer must write a bit more code to work with shared objects
between coroutines. - In such a memory model, you cannot obtain the result of a
coroutine’s execution twice. - And this changes the philosophy of awaiting a coroutine: only one
coroutine can wait for another. However, this limitation has many
positive sides, because it greatly simplifies debugging.
Such a memory model is quite modern and yet not new. It is essentially
supported by Go, Erlang, and other next-generation languages.
Therefore, if PHP’s strategy is to eventually become a language with
parallelism while guaranteeing coroutine safety with respect to shared
memory, then this is the right path.
But I want to warn once again about the price that must be paid from
the developer’s point of view. A developer will no longer be able to
use reference variables or pass objects between coroutines.
Ed
This makes it impossible to accidentally shoot yourself in the foot. A
developer can still do something silly by explicitly passing objects
between coroutines, but now they are doing it consciously.
Again, you completely avoided my question, and went back to ambiguous
statements about "a developer".
To stick to my personas:
-
SDK Susie does not know what implementation of LoggerInterface will be
passed to her library. How does she know if it is safe to use in her
asynchronous code? -
Legacy Les is using a LoggerInterface implementation written years
ago. How does he know whether it is acceptable for use with Susie's library?
--
Rowan Tommins
[IMSoP]
Op ma 24 nov 2025 om 15:03 schreef Rowan Tommins [IMSoP] <
imsop.php@rwec.co.uk>:
- SDK Susie does not know what implementation of LoggerInterface will be
passed to her library. How does she know if it is safe to use in her
asynchronous code?
As the LoggerInterface is injected into her SDK client and there is no
distinction between an async and a sync Interface, Susie can't and doesn't
really need to know this. She just uses the contract that is implied by the
interface.
- Legacy Les is using a LoggerInterface implementation written years
ago. How does he know whether it is acceptable for use with Susie's
library?
As I see it, there's two kinds of "acceptable" in this case: blocking vs
non-blocking and possible side effects.
Blocking vs non-blocking:
If Les does not care about blocking the event loop he can do whatever he
wants. If he passes a Logger with a blocking I/O call (for example through
some extension) the event loop would block for the duration of the I/O
call. Acceptable in his case, as he does not seem to care about async.
If Les wants to take advantage of non-blocking I/O he has to use a Logger
which implements one of the non-blocking I/O functions (file_put_contents,
...). In this case I would expect the Scheduler to await that function call
in the current coroutine. Is this correct Edmond?
Possible side-effects:
It is however possible that the expected output of the logs may have a
different order than before Susie migrated her library from sync to async.
Depending on how you look at it this might be a problem. I would expect
Susie to release a new major version warning users of a possible break in
backward compatibility. If Susie does not do that, Susie is at fault. If
Susie does release a new major version I would expect Les to fix his
dependency on shared state before upgrading to a new major version instead
of blindly upgrading to a new version*.
- which he shouldn't do anyways, async or not.
Is this helpful?
Best Regards,
Bart Vanhoutte
If Susie does release a new major version I would expect Les to fix
his dependency on shared state before upgrading to a new major version
instead of blindly upgrading to a new version*.
- which he shouldn't do anyways, async or not.
I already covered that point:
Legacy Les won't get it until he opts in, but at some point he will
need a new version of the library for other reasons (e.g. because the
cloud API becomes incompatible with the old library version); so he
still needs a way to run it safely.
It is not about Les "blindly upgrading". It is about what he needs to do
to consciously, safely upgrade.
Remember, he has no interest in running his application asynchronously.
He just wants his code to continue running on PHP 9, and to have access
to Susie's cloud API.
Forcing Susie's code to run more slowly is not a problem for him. Having
to rewrite chunks of his application in case they break something is a
big problem.
--
Rowan Tommins
[IMSoP]
Yes, let’s consider scenarios for all three possible memory models:
- Coroutines share the same global and static variables.
- Coroutines have separate global and static variables, but the
programmer can explicitly pass the same object. - Coroutines have separate variables, and the programmer cannot pass
regular objects as shared at all.
Let’s say SDK Susie publishes her library, which depends on LoggerInterface.
Let LoggerInterface internally just use plain fwrite calls and a
shared message array. But Susie’s SDK doesn’t know that.
function `flush()`: void {
file_put_contents(implode("\n", $this->logs), FILE_APPEND);
$this->logs = []; // clean after save error
}
Such code, when called from two different coroutines, may write the
same data to the log file twice.
Then, if SDK Susie’s code creates a coroutine and uses LoggerInterface
inside it…
- …an error will occur.
- …an error will occur.
- PHP will not allow the same LoggerInterface object to be used in
different coroutines without changes to the code.
- Legacy Les is using a LoggerInterface implementation written years
ago. How does he know whether it is acceptable for use with Susie's library?
For the third memory model, the entire burden of guarantees falls on
SDK Susie, who must think through exactly how to use LoggerInterface.
If SDK Susie has no ability to modify the LoggerInterface, then she is
required to use it outside of coroutines.
For the first and second memory models, the responsibility for correct
behavior lies with SDK Susie, but the difference from the third model
is that SDK Susie can simply forget about it.
Ed
Then, if SDK Susie’s code creates a coroutine and uses LoggerInterface
inside it…
- …an error will occur.
- …an error will occur.
- PHP will not allow the same LoggerInterface object to be used in
different coroutines without changes to the code.
An error will occur for whom? SDK Susie when unit testing her library?
Or Legacy Les when he tries to run his working code with the new library?
If it is an error for SDK Susie, what is her solution to allow users to
pass in a logger to her library?
If it is an error for Legacy Les, how does he fix it? Does he have to
audit his entire 20-year-old application for cases which might cause an
error he doesn't understand?
--
Rowan Tommins
[IMSoP]
An error will occur for whom? SDK Susie when unit testing her library?
If SDK Susie used a new coroutine in which she calls the
LoggerInterface::flush()method, then with some probability the user
may notice that log entries are sometimes duplicated because the
LoggerInterface::logsproperty is not being cleared.
You could say that SDK Susie is responsible for this, because she ran
code that was never intended to be executed in a concurrent
environment.
If it is an error for SDK Susie, what is her solution to allow users to pass in a logger to her library?
To avoid the error, SDK Susie must be sure that the
LoggerInterface implementation supports concurrent execution.
If SDK Susie knows that LoggerInterface does not support asynchronous
code, then she should not attempt to call its methods in a separate
coroutine and should use LoggerInterface in the usual synchronous way.
The shared-nothing memory model doesn’t even give SDK Susie a chance
to make this mistake, because it simply forbids the existence of an
object with two references.
If it is an error for Legacy Les, how does he fix it?
In this case, we should assume that the responsibility for correctness
lies with the one who provides the new functionality. Therefore,
Legacy Les should not have to do anything.
However, if Legacy Les wants to use the library, he needs to do the following:
function `flush()`: void {
$data = $this->logs;
$this->logs = []; // It is guaranteed to execute without interruptions.
file_put_contents(implode("\n", $data), FILE_APPEND);
}
I want to point out right away that the code above is not suitable for
multithreaded execution.
An error will occur for whom? SDK Susie when unit testing her library?
If SDK Susie used a new coroutine in which she calls the
LoggerInterface::flush()method, then with some probability the user
may notice that log entries are sometimes duplicated because the
LoggerInterface::logsproperty is not being cleared.
You could say that SDK Susie is responsible for this, because she ran
code that was never intended to be executed in a concurrent
environment.
Just to be clear, LoggerInterface is not an imaginary example, it's
right here: https://www.php-fig.org/psr/psr-3/#3-psrlogloggerinterface
Packagist lists 9539 packages which depend on it, and has logged more
than 1 billion installs: https://packagist.org/packages/psr/log
It is an absolute certainty that a PHP library will want to both use
async I/O and accept a LoggerInterface as a dependency.
If it is an error for SDK Susie, what is her solution to allow users to pass in a logger to her library?
To avoid the error, SDK Susie must be sure that the
LoggerInterface implementation supports concurrent execution.
Susie doesn't know anything about the implementation, other than what is
defined in the interface. That is literally the purpose of accepting an
interface.
So it sounds like her only safe option is to restrict her usage of async
coroutines to small self-contained pieces of code, and only use injected
dependencies and callbacks in the "main" coroutine where they were
passed in.
Regards,
--
Rowan Tommins
[IMSoP]
Hello
Susie doesn't know anything about the implementation, other than what is defined in the interface. That is literally the purpose of accepting an interface.
So it sounds like her only safe option is to restrict her usage of async coroutines to small self-contained pieces of code, and only use injected dependencies and callbacks in the "main" coroutine where they were passed in.
Exactly. This means that for asynchronous logging, a separate
interface must be introduced in order to guarantee correct behavior.
Op di 25 nov 2025 om 10:43 schreef Edmond Dantes edmond.ht@gmail.com:
Hello
Susie doesn't know anything about the implementation, other than what is
defined in the interface. That is literally the purpose of accepting an
interface.
So it sounds like her only safe option is to restrict her usage of async
coroutines to small self-contained pieces of code, and only use injected
dependencies and callbacks in the "main" coroutine where they were passed
in.Exactly. This means that for asynchronous logging, a separate
interface must be introduced in order to guarantee correct behavior.
In my opinion that depends on what Susie thinks of as correct behavior, but
LoggerInterface might not be the best example to discuss shared state.
There's plenty of other examples to think of though that would not
guarantee backwards compatibility (order of lines in a file written with
file_put_contents etc).
Anyway, it seems like we're coming to the conclusion that mixing sync and
async behavior is going to cause quite some problems and is something that
would not pass a vote if I had to guess. Even if you know what you're doing
YMMV.
Am I reading the room correctly if I say that we're converging at the
sync/async toggle (ini settings, function call, whatever, ...)?
Best Regards,
Bart Vanhoutte
Hello
Anyway, it seems like we're coming to the conclusion that mixing sync and async behavior is going to cause quite some problems and is something that would not pass a vote if I had to guess. Even if you know what you're doing YMMV.
The main question is how much code needs to be changed.
The example I gave with file_put_contents is a typical source of errors.
It is easy to fix, but not always easy to detect.
Maybe we can improve this situation.
This requires discussion.
Again, I'm of the opinion that this vote is premature and needs to be
halted.
Edmond had done extraordinary effort and proved that an async
implementation is possible. And his solution may indeed be the route to go.
But perhaps settle back and target this for 9.0 instead of 8.6, which will
give whatever implementation is chosen freedom to make breaking changes
should they be absolutely necessary. I'd also recommend an RFC to commit to
having async in 9 in some form without committing to any specific
implementation and set out the goals. I think passing that alone is
significant.
Furthermore, everyone remembers the PHP 6 debacle of committing to full
unicode support and then discovering halfway into the process that such was
a far bigger bear than realized. Edmond has explored the async problem
space well enough I think that we can be sure that an implementation is
possible. Rushing to commit in the first prototype though I feel is a
mistake.
Not that what I say has much weight - I'm a moron compared to most of you,
but I try.
Hello
Anyway, it seems like we're coming to the conclusion that mixing sync
and async behavior is going to cause quite some problems and is something
that would not pass a vote if I had to guess. Even if you know what you're
doing YMMV.The main question is how much code needs to be changed.
The example I gave withfile_put_contentsis a typical source of errors.
It is easy to fix, but not always easy to detect.Maybe we can improve this situation.
This requires discussion.
Hello
Susie doesn't know anything about the implementation, other than what is defined in the interface. That is literally the purpose of accepting an interface.
So it sounds like her only safe option is to restrict her usage of async coroutines to small self-contained pieces of code, and only use injected dependencies and callbacks in the "main" coroutine where they were passed in.Exactly. This means that for asynchronous logging, a separate
interface must be introduced in order to guarantee correct behavior.
If we're going to have to recreate some of the most basic interfaces to get async to work ... does it make sense to seriously consider coloured functions? As annoying as it would be to refactor so much code, I think we could realistically provide ourselves escape hatches by not requiring every function calling await to be async?
// non-async functions can still call await, but this just forces a blocking call to the async function
function i_call_await(): string {
return await someAsyncFunction(); // spawns and blocks until a coroutine completes
}
// returns Coroutine<string> -- since we don't have generics
async function someAsyncFunction(): string {
return file_get_contents_async('some_file'); // does not block but returns a coroutine since the function is async
}
// alternative implementation that technically returns CompletedCoroutine<string>
async function someAsyncFunction(): string {
return await file_get_contents_async('some_file'); // spawns and blocks until a coroutine completes
}
Just a thought and would mostly just be sugar around the proposed RFC.
— Rob
Op di 25 nov 2025 om 11:36 schreef Rob Landers rob@bottled.codes:
If we're going to have to recreate some of the most basic interfaces to get async to work ... does it make sense to seriously consider coloured functions? As annoying as it would be to refactor so much code, I think we could realistically provide ourselves escape hatches by not requiring every function calling
awaitto be async?
I have worked with coloured functions in PHP when Fibers were not
around and the developer experience is very bad. Before PHP 8.1; every
function I wrote would be coloured by either accepting or returning a
Promise<sometype> (which is made worse by the fact there's no
generics).
Since PHP 8.1; I can use Fibers and implement a widely spread
Interface in an async way. Currently, if I want to use an SDK client,
the first thing that I check is composer.json to find out what HTTP
client it uses. If it accepts a PSR-18 ClientInterface I use a small
wrapper around ReactPHP's Browser that awaits a Promise and I'm set.
We've been talking about shared state and possible breaks in backwards
compatibility, but I must say that in reality it's not something that
I find being in the way very often. Like I've made it seem before, if
there's any serious breaks in backward compatibility (not the order of
log lines that might be different from the sync implementation) I
would at that point just document the change and release a new major
version of a library. That is a lot less painful imo than working with
coloured functions.
Best Regards,
Bart
Op di 25 nov 2025 om 11:36 schreef Rob Landers rob@bottled.codes:
If we're going to have to recreate some of the most basic interfaces to get async to work ... does it make sense to seriously consider coloured functions? As annoying as it would be to refactor so much code, I think we could realistically provide ourselves escape hatches by not requiring every function calling
awaitto be async?I have worked with coloured functions in PHP when Fibers were not
around and the developer experience is very bad. Before PHP 8.1; every
function I wrote would be coloured by either accepting or returning a
Promise<sometype> (which is made worse by the fact there's no
generics).Since PHP 8.1; I can use Fibers and implement a widely spread
Interface in an async way. Currently, if I want to use an SDK client,
the first thing that I check iscomposer.jsonto find out what HTTP
client it uses. If it accepts a PSR-18 ClientInterface I use a small
wrapper around ReactPHP's Browser that awaits a Promise and I'm set.
We've been talking about shared state and possible breaks in backwards
compatibility, but I must say that in reality it's not something that
I find being in the way very often. Like I've made it seem before, if
there's any serious breaks in backward compatibility (not the order of
log lines that might be different from the sync implementation) I
would at that point just document the change and release a new major
version of a library. That is a lot less painful imo than working with
coloured functions.Best Regards,
Bart
Looking higher up the thread, you'll see that Larry also proposed something similar. I'm not saying everything needs to be coloured.
In other words, there is no "Promise" object that always needs to be handled. Adding an "async" to the function makes it return a Coroutine, that can be awaited. This is a bit different than Generators where you can only return a Generator. The return type stays as you'd expect it to be if it weren't async.
Then, when/if you call await on this coroutine, and we're not already in a coroutine, it will spawn one for us, blocking until it returns. If we are already inside a coroutine, then it blocks until the coroutine completes.
Then you have coloured functions when you want them, but can call them without having to change your code or "infect" your code with async/await. Its literally just sugar over the currently proposed RFC, as you'd have to return Coroutines and await them anyway, but this is actually more type-safe.
— Rob
Op di 25 nov 2025 om 12:54 schreef Rob Landers rob@bottled.codes:
... Adding an "async" to the function makes it return a Coroutine, that can be awaited.
This is just a different flavour of coloring. You would now have
functions that have "async" and functions that don't have "async".
Then, when/if you call await on this coroutine, and we're not already in a coroutine, it will spawn one for us, blocking until it returns. If we are already inside a coroutine, then it blocks until the coroutine completes.
Then you have coloured functions when you want them, but can call them without having to change your code or "infect" your code with async/await. Its literally just sugar over the currently proposed RFC, as you'd have to return Coroutines and await them anyway, but this is actually more type-safe.
At this point you need to know that a function is async because you
need to know if you need to call await on it. Without coloring, you
don't have this problem.
Let me see if I can find some time to create some examples using
ReactPHP on Github and then we can explore some additional syntax
there to see what it would feel like as a developer?
Hi Rowan,
This makes it impossible to accidentally shoot yourself in the foot. A
developer can still do something silly by explicitly passing objects
between coroutines, but now they are doing it consciously.Again, you completely avoided my question, and went back to ambiguous statements about "a developer".
To stick to my personas:
SDK Susie does not know what implementation of LoggerInterface will be passed to her library. How does she know if it is safe to use in her asynchronous code?
Legacy Les is using a LoggerInterface implementation written years ago. How does he know whether it is acceptable for use with Susie's library?
I would argue that LoggerInterface only accounts for a synchronous implementation. It is impossible for an asynchronous implementation to meet the requirements of the interface.
An AsyncLoggerInterface would be required. However, any synchronous implementation that otherwise matches the requirements would implicitly match. (Any non-asynchronous function is effectively the same as an asynchronous function that has no suspension points.)
Effectively, we would need something like:
interface LoggerInterface extends AsyncLoggerInterface {
public function log($level, $message, array $context = []);
...
}
interface AsyncLogerInterface {
public function log($level, $message, array $context = []) async;
...
}
So if SDK Susie can certify that their library will work correctly with an async logger, then they can indicate that by accepting an AsyncLoggerInterface (in place of, or separately from, a LoggerInterface). This is just an assertion that SDK Susie's library will work correctly in the face of an async logger. It's up to Async Alice and Beginner Bob to make sure they don't somehow footgun themselves with their own (new) async code. Legacy Les isn't passing in anything async, so as long as Susie's SDK doesn't violate its prior synchronous contract, Les shouldn't have anything to worry about.
-John
Hi,
A programmer working with coroutines will not be able to use static or
global variables to pass state.They have only two ways to do it:
- Function parameters
- use to capture variables in a closure
This makes it impossible to accidentally shoot yourself in the foot. A
developer can still do something silly by explicitly passing objects
between coroutines, but now they are doing it consciously.Although even here we can go further and create something similar to
ownership transfer of an object. In other words, an explicit semantics
that clearly specifies what to do with the object:
- Should the object be moved between coroutines?
This could be quite problematic for internal objects that might depend on
global that will switch.
- Should the object be cloned?
Something like this should be done explicitly through channels otherwise it
would be quite strange semantically for users.
- Is the object a special shared object that can be safely passed
around?
I think it should be the only allowed case.
Such semantics makes coroutines thread-safe.
In other words, by designing a special parameter-passing semantics for
coroutines, we can create a perfect specification for coroutines that
can be run both in another thread and in the current one.
At the same time, the memory model becomes equivalent to Erlang’s
model, where there are no shared objects except for specific special
ones.Such a change requires adding special Shared objects to the
language, which can be safely used across different threads. And
developers will no longer be able to use reference-based variables,
except within something likeSharedBox<T>.
At the same time, implementing multi-threading support is not required
immediately. But once this capability is added, the language semantics
will already be fully prepared for it.So props/cons:
- Coroutines become safe execution containers that cannot
accidentally damage shared memory.- Old code requires no changes.
- New coroutines cannot harm old code. And if parameter-passing
semantics are introduced, they won’t be able to harm it at all. PHP
will forbid a programmer from even trying to pass memory to another
coroutine just like that.- The language semantics make it possible to describe fully
thread-safe code, which can be added in the future at any time without
major changes.The cost:
- a developer must write a bit more code to work with shared objects
between coroutines.- In such a memory model, you cannot obtain the result of a
coroutine’s execution twice.- And this changes the philosophy of awaiting a coroutine: only one
coroutine can wait for another. However, this limitation has many
positive sides, because it greatly simplifies debugging.Such a memory model is quite modern and yet not new. It is essentially
supported by Go, Erlang, and other next-generation languages.
Therefore, if PHP’s strategy is to eventually become a language with
parallelism while guaranteeing coroutine safety with respect to shared
memory, then this is the right path.
But I want to warn once again about the price that must be paid from
the developer’s point of view. A developer will no longer be able to
use reference variables or pass objects between coroutines.
If we could make this work, then it could be a much better result as it
could open doors for true parallelisation. It will add more limitations in
terms of sharing code but I think it would be worth it.
I think the first thing for that would be good to look at is to make the
needed changes for TSRM which would be already useful for FrankenPHP :
https://github.com/php/frankenphp/discussions/1980 .
The context switches will likely become more expensive but should be still
cheaper than long IO. But obviously not idea if IO is available and switch
is not needed. The scheduler might need to be a bit smarter and try to
reduce switches though. I started writing IO ring library that is a thin
wrapper for liburing on Linux and it has compatibility layer for other
platforms using IO threads: https://github.com/libior/ior . This is still
limited and supports only basic ops but I plan to add more ops (including
epoll based ops). Also it will need Windows support.. But the advantage of
ring buffer is that it can significantly reduce syscalls and check multiple
completions in one go (it has already queue for them) so it could more
easily reduce number of switches (e.g. it can immediately get info whether
IO is available by going through all the completion events). I think it
should be a bit more flexible for scheduler. Anyway it's more a detail at
this stage but maybe something to think about later.
Kind regards,
Jakub
Hello
This could be quite problematic for internal objects that might depend on global that will switch.
Yes, this will require changes to the PHP Engine, and we will have to
determine their scope.
Something like this should be done explicitly through channels otherwise it would be quite strange semantically for users.
Yes, exactly, channels are an excellent way to explicitly pass objects
between coroutines.
But thanks to the ownership policy, the developer gains the ability to
explicitly clone an object before passing it.
Example:
function x(own object $y): void
{
}
$var = new Obj();
$var2 = $var;
x($var); // error: can't move object to x, because refcount > 1
x(clone $var); // yes it's possible
I think it should be the only allowed case.
Of course
The context switches will likely become more expensive but should be still cheaper than long IO.
I don’t know. To answer this question, we need to write code. But I
hope it’s possible to find an optimization that has almost no
performance impact.
To solve this problem, one important change is needed: removing static
var from opcodes.
This would make it possible to share PHP opcodes between N threads as
a readonly structure. I can’t say how feasible this task is. But if it
is feasible, it opens the door to the third memory model (shared
nothing).
But as you understand, this approach provides the maximum possible
level of protection.
This is still limited and supports only basic ops but I plan to add more ops
Yes, this will give additional capabilities for the Reactor.
I can see that libUV isn’t in much of a hurry to implement them.
Ed
If we could make this work, then it could be a much better result as
it could open doors for true parallelisation. It will add more
limitations in terms of sharing code but I think it would be worth it.
Just a plug / reminder that Joe Watkins already wrote a parallel code
implementation for PHP here: https://github.com/krakjoe/parallel
It uses a Channel-based data sharing approach based on Go:
https://www.php.net/manual/en/philosophy.parallel.php
--
Rowan Tommins
[IMSoP]
Just a plug / reminder that Joe Watkins already wrote a parallel code
Library parallel uses a trick to run code in another thread. It
clones the function’s opcodes, including the static vars, which is a
known PHP Engine behavior. In this case, the function runs inside a
completely separate Zend VM.
For true parallelism, the following changes are needed:
- Shared opcodes
- A new memory allocator
- A new GC similar to Go’s
- Virtual threads (what FrankenPHP is doing now)
- Possibly something else…
Before starting the TrueAsync project, I tried to consider the
possibility of implementing full parallelism, but ultimately I
assessed the implementation cost as “impossible.”
In addition, pure parallelism does not provide more performance (and
sometimes even performs worse) for a web server than concurrency.
Hello everyone.
Today I am officially announcing the start of the async working group.
Anyone may join the group without restrictions.
However, the discussion will follow a clear flow and will be divided
into several stages.
The discussion moderator will help keep the conversation on the right
track, separating secondary questions from the more important ones.
Anyone may propose a candidate for the role of moderator, and until
then I will fulfill this role.
https://github.com/true-async/php-true-async-rfc/discussions/8
Please note that this discussion is not tied to the RFC, is not a
continuation of it, and is essentially a discussion starting from
scratch. At this stage, PHP may choose any direction, as long as it
receives support.
The discussion stages will be finalized through voting and will also
be documented in the form of theses.
It is possible that some stages will need to be approved by the
broader PHP community. I am not yet sure how this should be done, but
I hope for support from the PHP Foundation.
In addition to the main discussion, anyone may create an independent
topic, which will be reviewed.
I also intend to bring this discussion to the attention of the
maintainers of PHP frameworks, because these changes are of primary
importance to them.
For RFC developers, it is important to identify the parts of the code
that will not work, or to understand how the code should be adapted so
that it functions correctly in asynchronous mode.
This is why collective discussion is so important.
For the developers of Swoole and FrankenPHP, this discussion is
equally important. Asynchronous execution makes little sense if PHP
does not have its own built-in web server. A built-in web server with
workers is the foundation for everything. Releasing Async without a
server is possible, but almost meaningless. This is why there is great
hope and attention directed toward your work.
Please share this message with anyone who may be able to help the project.
Best regards, Ed
ini settings are very frowned upon, but for the sake of a conversation, we can think of it like that. if I do async.enabled = 0, then nothing changes for me. Having this opt-in/opt-out control would mean that a broken website caused by spawn() only happens if they decide to enable async and they can start learning and developing their mental habit about how to work with async code.
Like I said in my previous email, having a feature flag like this
would be a reasonable compromise between adoption and backwards
compatibility. Shared state is definitely a problem but there's also
less obvious things that can go wrong.
For example: database transactions are done on a per connection basis.
Sharing a single database connection for purposes other than queries
that read things is dangerous. Imagine starting a transaction in one
coroutine, doing a couple of inserts and then switching do a different
coroutine before committing the transaction. As long as the
transaction has not been committed or rolled back, the second
coroutine will execute queries on the same connection!
As for the feature flag: I would prefer something that can be enabled
on a per-project basis. I'm guessing an ini-setting could work? You
can enable it in both the configuration and in code (index.php).
As an addition, a flag in composer.json that indicates async behavior
would be a nice to have. It could warn users when they are requiring a
library that does not support async.
Best Regards,
Bart Vanhoutte
Op vr 21 nov 2025 om 15:18 schreef Deleu deleugyn@gmail.com:
I have a mental habit: when I write asynchronous code, I always assume
that any object shared between coroutines can change at any moment.
Similarly, if you hand off memory ownership to another class or share
memory, you have to keep in mind that there is a chance some developer
might accidentally do something wrong.The only difference between synchronous and asynchronous code is that
you don’t have a precise moment in time when the change happens. And
yes, that makes debugging harder. It makes finding bugs harder. But
it’s not something fundamentally new.I don't know how else to describe it, but I decided to give a last attempt here since you mentioned your mental habit: having a mental habit about async code makes perfect sense. What I'm trying to say is that most PHP developers don't have that mental habit and that's not a problem because Async code in PHP doesn't come into your project out of nowhere. With
spawn()being native, anytime you do "composer update" your project may start running async code without your consent and without warning you that you should go through a mental habit that you don't even know exist because you never had to worry about async code before in your life.Having spawn() be an extremely easy way to start running async code is a great feature (not a bug). But the very first thing that seems to be missing is: I'm a dumb PHP developer that don't know and don't care about async php and when I upgrade to PHP X.Y, how do I keep my project always-sync without risking one of my composer packages suddenly calling spawn() and causing bugs I have no idea how to even begin to understand?
ini settings are very frowned upon, but for the sake of a conversation, we can think of it like that. if I do async.enabled = 0, then nothing changes for me. Having this opt-in/opt-out control would mean that a broken website caused by spawn() only happens if they decide to enable async and they can start learning and developing their mental habit about how to work with async code.
--
Marco Deleu
As long as the
transaction has not been committed or rolled back, the second
coroutine will execute queries on the same connection!
*transaction, the second coroutine will execute queries on the same
transaction.
Op vr 21 nov 2025 om 15:38 schreef Bart Vanhoutte <
bart+php@croquemonsieur.be>:
ini settings are very frowned upon, but for the sake of a conversation,
we can think of it like that. if I do async.enabled = 0, then nothing
changes for me. Having this opt-in/opt-out control would mean that a broken
website caused by spawn() only happens if they decide to enable async and
they can start learning and developing their mental habit about how to work
with async code.Like I said in my previous email, having a feature flag like this
would be a reasonable compromise between adoption and backwards
compatibility. Shared state is definitely a problem but there's also
less obvious things that can go wrong.For example: database transactions are done on a per connection basis.
Sharing a single database connection for purposes other than queries
that read things is dangerous. Imagine starting a transaction in one
coroutine, doing a couple of inserts and then switching do a different
coroutine before committing the transaction. As long as the
transaction has not been committed or rolled back, the second
coroutine will execute queries on the same connection!As for the feature flag: I would prefer something that can be enabled
on a per-project basis. I'm guessing an ini-setting could work? You
can enable it in both the configuration and in code (index.php).As an addition, a flag in composer.json that indicates async behavior
would be a nice to have. It could warn users when they are requiring a
library that does not support async.Best Regards,
Bart VanhoutteOp vr 21 nov 2025 om 15:18 schreef Deleu deleugyn@gmail.com:
On Fri, Nov 21, 2025 at 10:45 AM Edmond Dantes edmond.ht@gmail.com
wrote:I have a mental habit: when I write asynchronous code, I always assume
that any object shared between coroutines can change at any moment.
Similarly, if you hand off memory ownership to another class or share
memory, you have to keep in mind that there is a chance some developer
might accidentally do something wrong.The only difference between synchronous and asynchronous code is that
you don’t have a precise moment in time when the change happens. And
yes, that makes debugging harder. It makes finding bugs harder. But
it’s not something fundamentally new.I don't know how else to describe it, but I decided to give a last
attempt here since you mentioned your mental habit: having a mental habit
about async code makes perfect sense. What I'm trying to say is that most
PHP developers don't have that mental habit and that's not a problem
because Async code in PHP doesn't come into your project out of nowhere.
Withspawn()being native, anytime you do "composer update" your project
may start running async code without your consent and without warning you
that you should go through a mental habit that you don't even know exist
because you never had to worry about async code before in your life.Having spawn() be an extremely easy way to start running async code is a
great feature (not a bug). But the very first thing that seems to be
missing is: I'm a dumb PHP developer that don't know and don't care about
async php and when I upgrade to PHP X.Y, how do I keep my project
always-sync without risking one of my composer packages suddenly calling
spawn() and causing bugs I have no idea how to even begin to understand?ini settings are very frowned upon, but for the sake of a conversation,
we can think of it like that. if I do async.enabled = 0, then nothing
changes for me. Having this opt-in/opt-out control would mean that a broken
website caused by spawn() only happens if they decide to enable async and
they can start learning and developing their mental habit about how to work
with async code.--
Marco Deleu
I'm not sure why the To/CC list got so long. Please reply to the list, not to each person...
Hello.
To use AMPHP you don’t need to run its server. But let’s simplify the situation.
You have Fiber.
With Fiber you can build an EventLoop + Scheduler, create two
coroutines, and then run code inside them that, for example, renders a
template.It will require a fair amount of code, but it’s possible.
At the same time, ob_start/ob_end will immediately break, because they
don’t support Fiber switching. This is a known bug.
And if you pass global state into closures, and that state changes
unpredictably, the application logic will essentially break.
I don’t know why you’d build all of that from scratch. https://revolt.run/ will allow you to run amphp/reactphp in any request. It "just works"
You do need to consciously kick off the event loop, though, as well as install some specific extensions, or you’re stuck with the stream_select implementation, which isn’t the best.
That being said, the only problem you’d run into trying to install this in a WordPress plugin is if any other plugin is using Fibers or another Fiber library. From experience, they don’t always play nicely together, and there should only be one event loop running at a time.
What’s different with TrueAsync
- You don’t need to write a lot of code. Yes, just spawn();
ob_startandob_endwill work correctly.- If closures share a variable that multiple coroutines write to
unpredictably, the application will still break.
Without well-defined suspension points, literally all code is unpredictable. This is what people have been trying to tell you for the last several weeks. How can I know when the code will yield to the scheduler? How do I reason about it? The foundation is there in the RFC and the code, but it needs more definition and guarantees.
In other words, no matter how asynchrony is implemented in PHP (and
other langs), it will always require correct handling of shared
memory.
And I want to clarify a bit. This is not about a global variable. It’s
about shared memory. These are not the same things. A local variable
can be passed into a closure by reference.
But if you don’t call WP() inside a coroutine, nothing prevents you
from using asynchrony.
PHP is full of shared memory, even applications. Take, for example:
$val = $this->cache->get('key') ?? $this->cache->set('key', 'value');
Currently in PHP, this is unsafe if the cache is a proper cache (like memcached/redis), but for an in-memory implementation, it’s effectively a Check-And-Set. If the cache is implemented as async, then it becomes unsafe even for in-memory implementations. This may-or-may-not be desired.
However, this gets weird, very quickly, very fast. $_SERVER/$_REQUEST/$_ENV/etc are all global memory that usually doesn’t change. However, frameworks change it all the time, to inject environment variables from .env files, to sanitise inputs, etc. Swoole avoids this by having a Request object. This is simply not present in PHP.
Global state is a core part of PHP, like it or not.
— Rob
Hello
Without well-defined suspension points, literally all code is unpredictable. This is what people have been trying to tell you for the last several weeks. How can I know when the code will yield to the scheduler?
How do I reason about it? The foundation is there in the RFC and the code, but it needs more definition and guarantees.
What guarantee does suspend() give? You can still break the code with
it. I understand what you’re talking about, but I don’t see a big
difference between situations like these:
// ---------------------------
// 1. File get contents suspend inside PHP CORE
// ---------------------------
$data = ['value' => 1];
$content = file_get_contents('http://example.com');
$data['value'] = 2;
// ---------------------------
// 2. Asynchronous version special API
// ---------------------------
$data = ['value' => 1];
// Yes we know about this here.
$content = file_get_contents_async('http://example.com');
$data['value'] = 2; // write happens after resume, timing is different
I understand that you are trying to say that if file_get_contents is
blocking, then there is slightly more chance that the code won’t
break.
And if file_get_contents_async is explicitly non-blocking, then the
programmer will have to rewrite the code.
But which is cheaper:
- Rewrite all the code?
- Or rewrite only the code that uses shared memory?
And at the same time, using file_get_contents_async does not
save the programmer from mistakes.
I can already imagine a situation where a programmer, trying to adapt
the code for async, simply renames the functions hoping it will “just
work” :)
No. It's not!
This approach:
- Increases the amount of refactoring
- Does not protect you from errors
The foundation is there in the RFC and the code, but it needs more definition and guarantees.
There is another approach.
You can use a specialasyncattribute for functions that support it.
This would allow not only highlighting the code in the editor, but
also validating it with static analysis.
In transparent asynchrony there is a rule when writing code.
Only code that makes no function calls at all is considered guaranteed safe.
Any call is potentially treated as a hypothetical yield point. I use
the same mindset when writing highly reliable code.
I imagine that it can crash literally at any line. The principle here
is similar.
Asynchronous code in this sense is always divided into two major parts:
- code that accesses shared memory
- code that does not access shared memory
You only need to be careful in the first case. Everything else
requires no special attention.
This leads to other principles:
- Shared state should only be passed when it is truly necessary.
- If an object can be made immutable, it should be immutable.
- If an object must be mutable, then it deserves double attention.
Above these principles there is one more, the most important:
- if you can avoid writing asynchronous code, you should avoid it
Global state is a core part of PHP, like it or not.
I love the global state. I’m not one of those people who shout that
some approach is “bad.”
Global state is great. The only question is how and when to use it.
So... summary:
Coroutines require:
- A framework that can work correctly in an asynchronous environment.
- A different mindset for framework and library developers.
- More knowledge from regular developers — a bit of a minus… and a
plus at the same time. - Strict memory discipline for everyone. Hooray! This is the future
of programming. You can’t run away from it.
I’m confident that PHP can be adapted for more convenient memory
handling. There are possibilities for that.... at the syntax level and
at the helper-object level. This still needs discussion.
P.S.
If someone ever writes documentation, these would be excellent phrases for it :)
Here is a very simple hypothetical:
Suppose I run a website and I have very little understanding of PHP, but I
know the basics. Every Monday I runcomposer update, do a little testing
and deploy the website. Suppose next Monday when I runcomposer update
one of my dependencies (Package A) starts to pull in AMPHP as its nested
dependency. When I try to open my website locally it will either have
everything broken or it will just work regularly and nothing will be
running async. There is no other option.
Now suppose the same example but with PHP 9 + TrueAsync. I docomposer updateone day and test my home page and it works. But deeply nested
somewhere there is a feature that will use Package A which then spawns a
coroutine and leads to my global state code behaving weirdly. It's not a
fatal error. It's not very clear at all to me that something changed, but
now my array indexes are triggering DB updates out of order.
This is basically the exact scenario I had in mind.
For me, having a cast-iron way to avoid this problem is a hard requirement for releasing a version of PHP with True Async included. Whether that's an ini setting, an annotation that protects particular parts of the code, etc, is detail we can work out later, but it must be a design requirement that protecting legacy code in some way is possible.
Debugging tools would be a useful addition, but wouldn't replace the off switch. I know for a fact that there are applications I will want to run on PHP 9 which will never be worth the investment to make async-ready. Other applications, I might well run in sync mode initially, then test and enable async.
And this again highlights why we need a project structure, where we can agree selected details of the design, with known outstanding issues. I want to be able to say "I approve of the design so far, with the understanding that X, Y and Z will be addressed before final release".
Regards,
Rowan Tommins
[IMSoP]
As for next lines:
- Disallow suspension of the main sync code which is effectively some sort of colouring.
- Preventing access to globals from coroutine which I'm not sure is even fully doable from the engine PoV
Yesterday there was a small discussion about debugging capabilities in
PHP for async.
For example: throwing an error if more than one coroutine writes to a variable.
Or issuing a warning if someone creates a coroutine inside a function.
This can be called an extended debugging mode. Almost no other
language has this, and if we implement it, it would be very cool and
very useful.
We can also come up with other ways to protect PHP users from silly mistakes.
For example, blocking async entirely or per namespace.
But I don’t think discussing these features is the top priority right now.
All of this is nice to have once everything else is already in place.
Hey Jakub,
Hi,
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
- Disallow suspension of the main sync code which is effectively some
sort of colouring.- 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.
I think you seriously misunderstand this.
Async does not allow you to introduce some effects from the outside. You
have to actively opt-in to running stuff in parallel.
The problematic part, is, when you call code inside of coroutines, which
is not safe to be run in parallel with other code.
I.e. if you start writing code like await([new Coroutine(fn() => not_safe_to_run_twice()), new Coroutine(fn() => not_safe_to_run_twice())]) (pseudo code).
Where not_safe_to_run_twice() shares some context.
So, when you write async code, you have to know that the called code
is allowed to run in parallel with any other code which you explicitly
chose to run in parallel with it. E.g. if you'd run two wordpress
functions which don't have side-effects (not even to internal state),
then you're perfectly fine doing that. If you run wordpress functions in
parallel which may jump back to the scheduler during its operation and
leave the internal state in some intermittently invalid form, then yes,
then you have a problem.
But if your goal is to just do a localized parallelized operation which
you know is safe to run in parallel - e.g. some image processing and
storing some stuff in the database, within your wordpress plugin, that's
absolutely fine. It won't break anything.
Essentially: Just don't call stuff in parallel which you don't know is
safe to be called in parallel and you're golden.
Bob
Hi Bob,
Hey Jakub,
Hi,
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
- Disallow suspension of the main sync code which is effectively some
sort of colouring.- 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.I think you seriously misunderstand this.
Async does not allow you to introduce some effects from the outside. You
have to actively opt-in to running stuff in parallel.
But you don't have to opt in into it in this current form. If some external
plugin calls spawn (without await), then it can suspend other plugins (that
didn't opt in to it) in places where it wasn't expected.
The problematic part, is, when you call code inside of coroutines, which
is not safe to be run in parallel with other code.I.e. if you start writing code like
await([new Coroutine(fn() => not_safe_to_run_twice()), new Coroutine(fn() => not_safe_to_run_twice())])
(pseudo code). Where not_safe_to_run_twice() shares some context.Well if you call await, you should be safe but that's not requirement
here. You can just spawn coroutine and main functions continues the flow to
other plugins that didn't opt in to async.
So, when you write async code, you have to know that the called code is
allowed to run in parallel with any other code which you explicitly chose
to run in parallel with it. E.g. if you'd run two wordpress functions which
don't have side-effects (not even to internal state), then you're perfectly
fine doing that. If you run wordpress functions in parallel which may jump
back to the scheduler during its operation and leave the internal state in
some intermittently invalid form, then yes, then you have a problem.
I'm not saying that is not possible to write safe code. This would be
perfectly possible and recommended, of course. The problem is that it
doesn't prohibit writing unsafe code in any way.
But if your goal is to just do a localized parallelized operation which
you know is safe to run in parallel - e.g. some image processing and
storing some stuff in the database, within your wordpress plugin, that's
absolutely fine. It won't break anything.
Not sure if you worked with WordPress but if you want to query something,
it is often done through $wp_query global so it might actually not be
always safe if there is some IO done in between building the query.
Essentially: Just don't call stuff in parallel which you don't know is
safe to be called in parallel and you're golden.
That's, of course, correct advise but I'm not sure you realise that if
those rules are not somehow enforced, it will be most likely get ignored
and lead to problems that many people are worried about. Also it might not
be immediately obvious that something is safe because the change of state
might happen indirectly and be deeply nested.
Kind regards,
Jakub
Hello
Here again is the link to the instructions: https://wiki.php.net/rfc/howto
I think it was just my internal feeling that voting on a page with a
lot of text is inconvenient. So I tried to find some solution in the
documentation. That’s why I thought that having the voting on a
separate page would probably be the right thing.
Anyway, don’t pay attention to it
On Monday, you replied to a message from Bart Vanhoutte, saying:
Yes, I was unexpectedly surprised that someone suggested the same
thing I had proposed a few months earlier.
That proposal also included a separate workflow for TrueAsync, which
would keep it in a special experimental status for at least six months
or a year.
Let's find a way to get that working group set up, and get people from other projects involved.
There were never any objections from my side.
But it seems to me that people have made their choice. And that’s okay.
Ed
Hello, Derick
Why is the voting widget not on the RFC page where it belongs?
It was fixed.
Please use: https://wiki.php.net/rfc/true_async#voting_choices
Hi
Am 2025-11-19 13:35, schrieb Edmond Dantes:
According to all previous discussions, version 1.6 of this RFC has
been prepared and is now being submitted for a vote:Voting Page: https://wiki.php.net/rfc/true_async/voting
RFC https://wiki.php.net/rfc/true_asyncThe vote officially starts tomorrow, as previously announced.
I'm seeing that you added the full API stub of the proposed API on
November 13 (which I'm only seeing now, did I miss an email where you
mentioned that you made the change?). I'm afraid to note that the
proposed API is in violation of our policy regarding Throwables:
https://github.com/php/policies/blob/main/coding-standards-and-naming.rst#throwables.
Particularly, there the “base exception” for the Async namespace is
missing. I'm only noticing it now with the stub, since the exact class
hierarchy was not mentioned in the RFC before.
Given the policy violation I believe that the RFC is unfit to go to vote
in the current state.
Best regards
Tim Düsterhus
Hello.
I'm afraid to note that the proposed API is in violation of our policy regarding Throwables
Thank you. It was fixed.
Hi
Am 2025-11-19 16:19, schrieb Edmond Dantes:
I'm afraid to note that the proposed API is in violation of our policy
regarding Throwables
Thank you. It was fixed.
That is not quite right. The base exception should be
\Async\AsyncException. I am also noticing that you changed the
DeadlockError to DeadlockException which is a significant change
from the previous proposal. The Error hierarchy is meant for problems
that are not expected to be caught, because they cannot usefully be
handled. The Exception hierarchy is something that should be caught.
Given that the RFC says (highlighting mine):
This condition is called a Deadlock, and it represents a serious
logical error.
I believe it should be a DeadlockError like it originally proposed.
That one should then extend \Async\AsyncError and similarly
\Async\AsyncError should extend `\Error.
Similarly for CancellationError. It should extend \Async\AsyncError.
I'm seeing the comment “Does not extend Exception to avoid accidental
suppression” which makes sense to me, but the Error hierarchy
specifically isn't an Exception. And folks catching \Throwable will
catch CancellationError as well, so I don't see a reason why it should
be treated differently in this regard.
Looking at the stub more carefully, I'm also seeing that
\Async\gracefulShutdown() should be \Async\graceful_shutdown()
instead (and similarly for currentCoroutine() -> current_coroutine()
and getCoroutines() -> get_coroutines()). This is something that I
could have caught earlier and I'm sorry that I did not. However since
changes to the RFC will still be required (which means that the vote
will need to be cancelled), it probably makes sense to fix that as well.
Best regards
Tim Düsterhus
Hello.
I believe it should be a
DeadlockErrorlike it originally proposed.
Thank you, this is worth discussing.
Am 19.11.2025, 13:35:13 schrieb Edmond Dantes edmond.ht@gmail.com:
Hello all
According to all previous discussions, version 1.6 of this RFC has
been prepared and is now being submitted for a vote:Voting Page: https://wiki.php.net/rfc/true_async/voting
RFC https://wiki.php.net/rfc/true_async
voted No soley because of the API design problems that Tim mentioned and
needs to be addressed before voting.
Its not a vote on the merits of the RFC in general.
The vote officially starts tomorrow, as previously announced.
For version 1.6 the following important change was made:
All input/output functions are now bound by the shared requirement of
being non-blocking with respect to the process. However, the specific
behavior of each function may (optionally) be defined in separate
RFCs.Thus, I/O functions themselves are not part of this RFC, but the main
RFC defines the general way in which they must operate. Thus (as I see
it), the RFC achieves a balance between cohesion and separation of
concerns.Since the discussion period has ended, I will not be engaging in
further debate (except regarding the voting process itself). If you
have any questions for me of any kind, you may ask them either in a
separate thread or privately. (This also means that I will not be
answering RFC-related questions in this thread). I will be glad to
hear your opinions and feedback. I wish all participants the best of
luck.
Best Regards, Ed
Hello Edmond,
Hello all
According to all previous discussions, version 1.6 of this RFC has
been prepared and is now being submitted for a vote:Voting Page: https://wiki.php.net/rfc/true_async/voting
RFC https://wiki.php.net/rfc/true_asyncThe vote officially starts tomorrow, as previously announced.
This comes a bit of sudden. For sure, you respected the minimum
discussion period, though there was still active discussion just
yesterday and the few days before.
I was also planning to give the now shortened variant of the RFC a
thorough read and read the discussions around it.
Others seem to also indicate that there were still a couple open points
for discussion - thus I would advise you to cancel the vote and discuss
these points first, then go ahead with a vote again a week later.
My own thoughts on the RFC (from just reading the RFC in depth, without
considering prior conversations now):
I agree with the choices of separating Fiber and Coroutine, but I
disagree that there's a fundamental problem with allowing Fibers in
Coroutines. A Fiber is essentially just a stackful generator, from a
very-high-level perspective. You allow generators to be used with
Coroutines. There should be no fundamental reason this isn't possible.
It just might require some refactoring of the code to manage it properly
(i.e. just like you cannot continue a generator which is currently
active, but suspended in a fiber, you should not be able to resume a
fiber which is currently active, but suspended in a coroutine).
Cancellation is not an Error - Errors are generally for programmer
errors, not for exceptional behaviour. There might be an argument for
making Cancellation a top-level item below Throwable, because it's
really special. But definitely not Error. (And if you do, the RFC should
note a small policy amendment too.)
I'm not particularly fond of the FutureLike name. Is there a reason why
it isn't simply called "Future"?
I know that in an earlier iteration there was a Future class. But Future
is only the promise of a future value. It's the callee API.
Callers can return specific implementations of Future, like "new
Async\Completed(true)", "new Async\Completable" (which has a $future
object as part of its API, which then can be awaited on), "new
Async\Failed($someException)".
A Coroutine should not only offer the current suspend location as API,
but the full stacktrace too. (Like ReflectionFiber does.)
Async\suspend() should maybe be called Async\yield() - as in yielding
control back to the scheduler. Suspension is the observable side-effect
of yielding or awaiting. But you don't actually suspend a coroutine, the
action of suspending is done by the scheduler. [This also would match
terminology used by the linux kernel for example, which calls it
sched_yield(). I think it's misnamed on Fiber.] I don't feel strongly
about that though.
I find it confusing that an "await" operation does not accept Awaitable
as its primary argument, but FutureLike per its signature. Why is that?
The doc for Awaitable says that there can be await calls on those. But
Async\await can't await it? What exactly is an Awaitable then? Would it
essentially be the equivalent of an iterable Future (as in: can be
awaited multiple times)?
Minor nit: Coroutine::onFinally() could be just called Coroutine->finally().
Thank you for working on this,
Bob
śr., 19 lis 2025 o 13:36 Edmond Dantes edmond.ht@gmail.com napisał(a):
Hello all
According to all previous discussions, version 1.6 of this RFC has
been prepared and is now being submitted for a vote:Voting Page: https://wiki.php.net/rfc/true_async/voting
RFC https://wiki.php.net/rfc/true_asyncThe vote officially starts tomorrow, as previously announced.
For version 1.6 the following important change was made:
All input/output functions are now bound by the shared requirement of
being non-blocking with respect to the process. However, the specific
behavior of each function may (optionally) be defined in separate
RFCs.Thus, I/O functions themselves are not part of this RFC, but the main
RFC defines the general way in which they must operate. Thus (as I see
it), the RFC achieves a balance between cohesion and separation of
concerns.Since the discussion period has ended, I will not be engaging in
further debate (except regarding the voting process itself). If you
have any questions for me of any kind, you may ask them either in a
separate thread or privately. (This also means that I will not be
answering RFC-related questions in this thread). I will be glad to
hear your opinions and feedback. I wish all participants the best of
luck.
Best Regards, Ed
Just a quick clarification regarding my vote.
I cast a “yes” because I wanted to express general support for the idea of
native async in PHP, not because I believed the RFC in its current form was
ready to pass. The ongoing discussion clearly shows there are still open
questions to resolve, and I fully respect that.
My intention was simply to show encouragement for the direction and
acknowledge the work that went into this effort, even if this particular
iteration is unlikely to succeed. I’m absolutely fine with the vote
continuing or being paused if needed — whatever best serves the process.
Regardless of the outcome, I hope the exploration of async in PHP
continues. It’s an important topic, and I appreciate the dedication behind
the proposal.
Cheers,
Michał Marcin Brzuchalski
Hello, Michal
Regardless of the outcome, I hope the exploration of async in PHP continues. It’s an important topic, and I appreciate the dedication behind the proposal.
Honest, open, and clear position. Thank you very much!
Ed
Enviado com um e-mail seguro do Proton Mail.
Em quarta-feira, 19 de novembro de 2025 às 09:38, Edmond Dantes edmond.ht@gmail.com escreveu:
Hello all
According to all previous discussions, version 1.6 of this RFC has
been prepared and is now being submitted for a vote:Voting Page: https://wiki.php.net/rfc/true_async/voting
RFC https://wiki.php.net/rfc/true_asyncThe vote officially starts tomorrow, as previously announced.
For version 1.6 the following important change was made:
All input/output functions are now bound by the shared requirement of
being non-blocking with respect to the process. However, the specific
behavior of each function may (optionally) be defined in separate
RFCs.Thus, I/O functions themselves are not part of this RFC, but the main
RFC defines the general way in which they must operate. Thus (as I see
it), the RFC achieves a balance between cohesion and separation of
concerns.Since the discussion period has ended, I will not be engaging in
further debate (except regarding the voting process itself). If you
have any questions for me of any kind, you may ask them either in a
separate thread or privately. (This also means that I will not be
answering RFC-related questions in this thread). I will be glad to
hear your opinions and feedback. I wish all participants the best of
luck.
Best Regards, Ed
Most of the people who advocate for this are responsible for the language's backwardness; they don't know how to conduct a conversation, it's just their opinion and nothing more. Worse, many times they aren't even using the language; they're stuck in the past, like dinosaurs. And the conversation is always the same: "Want asynchronous programming? Change languages, go to Node, switch to Go," but it's not that simple.
I'm amazed at how shallow their knowledge is, yet they defend the cause as if they were experts, without even trying to delve deeper into the subject. Those who do this don't lift a finger to move in that direction, and instead they come up with things nobody asked for, insignificant things that only 1% of people will use.
They say few people use asynchronous PHP. The adoption is totally different when the language already offers it natively. "Ah, but it will only be used by a small percentage of PHP users," and what about the useless things we didn't ask for that will only be used by 1%?
You could argue that it's easy to say, that they dedicate time from their hobby to improving PHP, and I appreciate that, but someone, in this case Ed, also dedicated time to it.
Most of the people who ...
I'm going to stop you there.
Personal attacks are not acceptable.
Making assumptions about what other people think is not helpful.
Framing the discussion as "us vs them" is not productive.
Please, anyone thinking of replying with vague opinions and rants, stop.
We're here to make the language better, not to trade insults.
Thank you.
--
Rowan Tommins
[IMSoP]
Hello all.
I’d like to add a few words regarding the 10 percent. Let’s assume
there really are 10% of developers in PHP who use Async. We can
replace this with some number X. What’s important is how exactly these
percentages are calculated.
For example, if this X percent is calculated based on the total number
of projects, that’s one thing. Suppose PHP is used in 1,000 projects.
Out of those, 800 projects are fairly simple, 150 are medium-sized,
and 50 are complex.
Most likely, async would be used in 200 projects, not in all 800. So
if there truly are X% of developers using it, then it turns out this
is actually a very large percentage of developers working on medium
and complex projects.
Do you see the difference?
The difference is that medium and complex projects bring PHP
developers more money. This means that if the PHP language and its
ecosystem cannot compete with Go or Python, then PHP developers earn
less because they’re not participating in the projects that generate
that money. Even if only 5% of developers use synchronous PHP, that
can still be a lot when you recalculate it.
And it is precisely the segment of developers working on medium- and
high-complexity tasks that pushes the language forward.
Therefore, losing 10%, or 5%, or even 2% may actually cost much more
for the language’s progress than it seems.
Skype and Nokia were once great too, and they also decided not to
develop their “1%.”
Hi,
Hello all
According to all previous discussions, version 1.6 of this RFC has
been prepared and is now being submitted for a vote:Voting Page: https://wiki.php.net/rfc/true_async/voting
RFC https://wiki.php.net/rfc/true_asyncThe vote officially starts tomorrow, as previously announced.
Just note here that you effectively started the vote on Nov 19th (votes
were already coming in so that should be the official start). There were
changes to the policy that were merged on Nov 20th so they should apply
from that date. The current text is following:
https://github.com/php/policies/blob/c2a1c602deee3988a9ce9cb740169973f5f4a781/feature-proposals.rst
. If it was started after that merge, this RFC would be automatically
invalid (there wasn't proper pre-announcement and so on) but as it started
before, I think it's still a valid vote. It means that you have only 3 days
to either stop the vote or you will need to let it finish. If it fails, you
won't be able to propose this for for another 6 months unless significant
changes are done. I guess it will need significant changes anyway but just
wanted to let you know what the options are here in terms of stopping the
vote.
Kind regards,
Jakub
Hi
invalid (there wasn't proper pre-announcement and so on) but as it started
before, I think it's still a valid vote. It means that you have only 3 days
As the author of the RFC amending the policy, I agree. While there was
some confusion with regard to the start of the vote (and the placement
on a separate page vs the RFC page) it was a valid vote with regard to
the previous policy and as such is “grandfathered” in.
Vote cancellation will need to follow the new policy, though.
Best regards
Tim Düsterhus
Hello.
To stop the vote, do I need to change the status?
вс, 23 нояб. 2025 г., 18:59 Jakub Zelenka bukka@php.net:
Hi,
Hello all
According to all previous discussions, version 1.6 of this RFC has
been prepared and is now being submitted for a vote:Voting Page: https://wiki.php.net/rfc/true_async/voting
RFC https://wiki.php.net/rfc/true_asyncThe vote officially starts tomorrow, as previously announced.
Just note here that you effectively started the vote on Nov 19th (votes
were already coming in so that should be the official start). There were
changes to the policy that were merged on Nov 20th so they should apply
from that date. The current text is following:
https://github.com/php/policies/blob/c2a1c602deee3988a9ce9cb740169973f5f4a781/feature-proposals.rst
. If it was started after that merge, this RFC would be automatically
invalid (there wasn't proper pre-announcement and so on) but as it started
before, I think it's still a valid vote. It means that you have only 3 days
to either stop the vote or you will need to let it finish. If it fails, you
won't be able to propose this for for another 6 months unless significant
changes are done. I guess it will need significant changes anyway but just
wanted to let you know what the options are here in terms of stopping the
vote.Kind regards,
Jakub
Hello all.
The voting process has been stopped.
Thanks to all.
Ed
Hi
The voting process has been stopped.
I moved the RFC back to “Under Discussion” in the overview:
https://wiki.php.net/rfc#under_discussion
Best regards
Tim Düsterhus
Hello.
Bob Weinand shared an excellent idea about how Fiber can be used
together with coroutines without breaking backward compatibility.
It is enough to automatically assign a coroutine to a fiber, and then
the code inside the fiber will be normalized relative to the
Scheduler.
Fiber receives the same properties as a coroutine and can be used as a
stackful generator.
This change makes it possible not to block Fiber, but instead allow it
to work together with TrueAsync and be used in cases where stackful
generators are needed.
A Fiber blocks the execution of the coroutine from which it was
started, meaning its symmetric behavior is preserved.
Respect to Bob Weinand
Ed
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, 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 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,
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.