[RFC] Polling API

php.internals

Jakub Zelenka

305 days ago
Hello, I would like to introduce a new polling API RFC that is part of my stream evolution work: https://wiki.php.net/rfc/poll_api Kind regards, Jakub

Tim Düsterhus

305 days ago
Hi Am 2025-10-30 22:06, schrieb Jakub Zelenka:
> I would like to introduce a new polling API RFC that is part of my > stream > evolution work: > > https://wiki.php.net/rfc/poll_api
1. Thank you for the RFC. I've taken a first skim of the proposal and it immediately raised the question of naming and namespacing in particular. Our naming policy at https://github.com/php/policies/blob/main/coding-standards-and-naming.rst#bundled-extensions says that “namespaces SHOULD be used” and given that this is a completely new API, I think we should namespace them. My understanding is that the proposed API relies on a file descriptor and not something like a timeout. It therefore makes sense to me to put it into a `namespace Io\Poll;` or similar. We would then also have: namespace Io; class IoException extends \Exception {} namespace Io\Poll; class PollException extends \Io\IoException {} The StreamPollHandle method should possibly be placed in the global namespace still, since the stream functions are sitting there - and a SocketPollHandle would of course be sitting in the namespace of the Sockets extension. 2. As for the PollBackend enum. Is there a reason why this is a backed enum? Generally speaking enums should not be backed, unless there is a good reason for this. I don't think there is in this case - and none of the native enums are backed so far. 3. Exception-wise. StreamPollHandle::__construct(): This should probably be a ValueError, not a PollException, since passing an invalid stream is a programmer error. In the other cases it probably makes sense to further split the PollException into purpose-built exceptions according to the Throwable policy at https://github.com/php/policies/blob/main/coding-standards-and-naming.rst#throwables (“The exception message MUST NOT be the only means of distinguishing exception causes that the user might want to handle differently.”). As an example PollContext::__construct() should probably throw a BackendUnavailableException or something like this. For PollContext::add() I'm wondering in which cases a handle “cannot be added”. Is this an error situation that users will encounter in the real world? Similarly, when can PollContext::wait() fail? 4. PollBackend Is the availability of the backends known at compile time of PHP or at runtime only? Depending on that it might make sense to only conditionally define the enum cases, allowing users to check availability with `defined()` or checking the output of `::cases()`. Alternatively, a `public static function getAvailableBackends(): array` could be added. -------- I'll give the proposal a more in-depth read at a later point, but this email should already provide for some discussion points. Best regards Tim Düsterhus

Jakub Zelenka

305 days ago
Hi, On Fri, Oct 31, 2025 at 10:40 AM Tim Düsterhus <tim@bastelstu.be> wrote:
> Hi > > Am 2025-10-30 22:06, schrieb Jakub Zelenka: > > I would like to introduce a new polling API RFC that is part of my > > stream > > evolution work: > > > > https://wiki.php.net/rfc/poll_api > > 1. > > Thank you for the RFC. I've taken a first skim of the proposal and it > immediately raised the question of naming and namespacing in particular. > Our naming policy at > https://github.com/php/policies/blob/main/coding-standards-and-naming.rst#bundled-ewill > be alwaysxtensions > <https://github.com/php/policies/blob/main/coding-standards-and-naming.rst#bundled-extensions> > says that “namespaces SHOULD be used” and given that this is a > completely new API, I think we should namespace them. > > My understanding is that the proposed API relies on a file descriptor > and not something like a timeout. It therefore makes sense to me to put > it into a `namespace Io\Poll;` or similar. We would then also have: > > namespace Io; > class IoException extends \Exception {} > namespace Io\Poll; > class PollException extends \Io\IoException {} > >
I thought about this and think this might be a good idea. Just to note internally it might not always be a fd. For example, TimerHandle might implement internal API not requiring fd because some platforms (e.g. kqueue ones) don't use it but from the API design (and on Linux), it is fd based so putting that to Io namespace might make sense. The thing is that I also started working on new IO copy API: https://github.com/php/php-src/compare/master...bukka:php-src:io_copy . This is just a stub but it aims to introduce new IO layer (initial mainly for copying but potentially cover more operations) and it should also contain a new IO ring (on Linux based on io_uring) variant that could be potentially also exposed in some form. I will get it to some working form in the coming weeks and then thing how to organise it with the poll.
> The StreamPollHandle method should possibly be placed in the global > namespace still, since the stream functions are sitting there - and a > SocketPollHandle would of course be sitting in the namespace of the > Sockets extension. >
Yeah those could stay in global and just extend Io\Poll\Handle.
> 2. As for the PollBackend enum. > > Is there a reason why this is a backed enum? Generally speaking enums > should not be backed, unless there is a good reason for this. I don't > think there is in this case - and none of the native enums are backed so > far. > >
I missed that they should not be backed. I just saw `enum AdjacentPosition : string` in Dom so thought that it's fine to use it... Will change it.
> 3. Exception-wise. > > StreamPollHandle::__construct(): This should probably be a ValueError, > not a PollException, since passing an invalid stream is a programmer > error. >
This makes sense.
> > In the other cases it probably makes sense to further split the > PollException into purpose-built exceptions according to the Throwable > policy at > > https://github.com/php/policies/blob/main/coding-standards-and-naming.rst#throwables > (“The exception message MUST NOT be the only means of distinguishing > exception causes that the user might want to handle differently.”). > >
This makes sense and I will introduce more exceptions. I will not use exception per errno but some middle ground is a good idea. Maybe per op exception with codes representing specific errors would be make sense?
> As an example PollContext::__construct() should probably throw a > BackendUnavailableException or something like this. For > PollContext::add() I'm wondering in which cases a handle “cannot be > added”.
It fails if the same fd is added. This is also limitation of backends (e.g. epoll does not allow the same fd to be added twice). I got error code for the specific errors so this should be probably exposed as well. It would still make sense to differentiate that it's an exception for addition.
> Is this an error situation that users will encounter in the real > world? Similarly, when can PollContext::wait() fail? >
Wait might also fail but less likely.
> > 4. PollBackend > > Is the availability of the backends known at compile time of PHP or at > runtime only? Depending on that it might make sense to only > conditionally define the enum cases, allowing users to check > availability with `defined()` or checking the output of `::cases()`. > Alternatively, a `public static function getAvailableBackends(): array` > could be added. > >
It's compile time but not sure if I like exposing enum only if compiled in as it makes the checks harder. This part is not really something that users should use but it's really more for testing purpose. In reality everyone should just use default Auto... But getAvailableBackends() might make sense even for testing. Maybe it could also have per enum method isAvailable() so user can check that but not sure if it's needed. Kind regards, Jakub

Tim Düsterhus

282 days ago
Hi Apologies for the late reply. It was a few busy weeks leading up to the PHP 8.5 Release. When searching for the RFC text I just noticed that it does not appear to be listed in https://wiki.php.net/rfc#under_discussion yet. On 10/31/25 13:06, Jakub Zelenka wrote:
>> 2. As for the PollBackend enum. >> >> Is there a reason why this is a backed enum? Generally speaking enums >> should not be backed, unless there is a good reason for this. I don't >> think there is in this case - and none of the native enums are backed so >> far. >> >> > I missed that they should not be backed. I just saw `enum AdjacentPosition > : string` in Dom so thought that it's fine to use it... Will change it.
I missed that one indeed. That one is a little special, because ext/dom implements an existing API standard, where the API is defined based on string parameters. So there already was a “well-defined” string for each enum case that is needed for better interoperability with the external standard.
>> >> In the other cases it probably makes sense to further split the >> PollException into purpose-built exceptions according to the Throwable >> policy at >> >> https://github.com/php/policies/blob/main/coding-standards-and-naming.rst#throwables >> (“The exception message MUST NOT be the only means of distinguishing >> exception causes that the user might want to handle differently.”). >> >> > This makes sense and I will introduce more exceptions. I will not use > exception per errno but some middle ground is a good idea. Maybe per op > exception with codes representing specific errors would be make sense?
Yes, that makes sense to me. I don't exactly know which types of error can appear, but as a developer I am generally not interested in super-granular handling of the errors but rather broad categories. If the details are only available as the code, then that's totally fine and within policy.
> >> As an example PollContext::__construct() should probably throw a >> BackendUnavailableException or something like this. For >> PollContext::add() I'm wondering in which cases a handle “cannot be >> added”. > > > It fails if the same fd is added. This is also limitation of backends (e.g. > epoll does not allow the same fd to be added twice). I got error code for > the specific errors so this should be probably exposed as well. It would > still make sense to differentiate that it's an exception for addition.
I see. I was wondering if it would make sense to simply ignore the error, thus making the operation idempotent (more convenient for a high-level language like PHP). But I suppose that doesn't work, because the `$data` could be different? Exception-wise this should probably be a DuplicateHandleError (i.e. within the Error hierarchy, since it's a programming error to add duplicate handles).
> It's compile time but not sure if I like exposing enum only if compiled in > as it makes the checks harder. This part is not really something that > users should use but it's really more for testing purpose. In reality > everyone should just use default Auto... But getAvailableBackends() might > make sense even for testing. Maybe it could also have per enum method > isAvailable() so user can check that but not sure if it's needed.
I'll think about this more when you made a decision and updated the RFC text :-) Best regards Tim Düsterhus

Jakub Zelenka

249 days ago
Hi, On Sun, Nov 23, 2025 at 4:05 PM Tim Düsterhus <tim@bastelstu.be> wrote:
> Hi > > Apologies for the late reply. It was a few busy weeks leading up to the > PHP 8.5 Release. > > When searching for the RFC text I just noticed that it does not appear > to be listed in https://wiki.php.net/rfc#under_discussion yet. > >
This is fixed.
> > > > >> As an example PollContext::__construct() should probably throw a > >> BackendUnavailableException or something like this. For > >> PollContext::add() I'm wondering in which cases a handle “cannot be > >> added”. > > > > > > It fails if the same fd is added. This is also limitation of backends > (e.g. > > epoll does not allow the same fd to be added twice). I got error code for > > the specific errors so this should be probably exposed as well. It would > > still make sense to differentiate that it's an exception for addition. > > I see. I was wondering if it would make sense to simply ignore the > error, thus making the operation idempotent (more convenient for a > high-level language like PHP). But I suppose that doesn't work, because > the `$data` could be different? > >
Yeah data and events can be different.
> Exception-wise this should probably be a DuplicateHandleError (i.e. > within the Error hierarchy, since it's a programming error to add > duplicate handles). > >
I don't think users should be expected to keep track of added handles. So I think that this is something that can be nicely handled and in some cases it will be just fine to just catch it and ignore. So I wouldn't see this as a programmer error and therefore the exception is more appropriate IMHO. I created a specific exception for this called \Io\Poll\HandleAlreadyWatchedException so it will be easy for users to differentiate it. Kind regards, Jakub

Jakub Zelenka

249 days ago
On Fri, Oct 31, 2025 at 1:06 PM Jakub Zelenka <bukka@php.net> wrote:
> Hi, > > On Fri, Oct 31, 2025 at 10:40 AM Tim Düsterhus <tim@bastelstu.be> wrote: > >> Hi >> >> Am 2025-10-30 22:06, schrieb Jakub Zelenka: >> > I would like to introduce a new polling API RFC that is part of my >> > stream >> > evolution work: >> > >> > https://wiki.php.net/rfc/poll_api >> >> 1. >> >> Thank you for the RFC. I've taken a first skim of the proposal and it >> immediately raised the question of naming and namespacing in particular. >> Our naming policy at >> https://github.com/php/policies/blob/main/coding-standards-and-naming.rst#bundled-ewill >> be alwaysxtensions >> <https://github.com/php/policies/blob/main/coding-standards-and-naming.rst#bundled-extensions> >> says that “namespaces SHOULD be used” and given that this is a >> completely new API, I think we should namespace them. >> >> My understanding is that the proposed API relies on a file descriptor >> and not something like a timeout. It therefore makes sense to me to put >> it into a `namespace Io\Poll;` or similar. We would then also have: >> >> namespace Io; >> class IoException extends \Exception {} >> namespace Io\Poll; >> class PollException extends \Io\IoException {} >> >> > I thought about this and think this might be a good idea. > >
This is implemented as suggested and RFC updated.
> >> The StreamPollHandle method should possibly be placed in the global >> namespace still, since the stream functions are sitting there - and a >> SocketPollHandle would of course be sitting in the namespace of the >> Sockets extension. >> > > Yeah those could stay in global and just extend Io\Poll\Handle. > > >> 2. As for the PollBackend enum. >> >> Is there a reason why this is a backed enum? Generally speaking enums >> should not be backed, unless there is a good reason for this. I don't >> think there is in this case - and none of the native enums are backed so >> far. >> >> > I missed that they should not be backed. I just saw `enum AdjacentPosition > : string` in Dom so thought that it's fine to use it... Will change it. > >
This is also fixed and it's no longer a backed enum.
> 3. Exception-wise. >> >> StreamPollHandle::__construct(): This should probably be a ValueError, >> not a PollException, since passing an invalid stream is a programmer >> error. >> > > This makes sense. > > >> >> In the other cases it probably makes sense to further split the >> PollException into purpose-built exceptions according to the Throwable >> policy at >> >> https://github.com/php/policies/blob/main/coding-standards-and-naming.rst#throwables >> (“The exception message MUST NOT be the only means of distinguishing >> exception causes that the user might want to handle differently.”). >> >> > This makes sense and I will introduce more exceptions. I will not use > exception per errno but some middle ground is a good idea. Maybe per op > exception with codes representing specific errors would be make sense? > >
I created a new exception hierarchy that is hopefully in line with Throwable policy.
> As an example PollContext::__construct() should probably throw a >> BackendUnavailableException or something like this. > >
There are now getAvailableBackends so users should check it out before so I used ValueError for this part.
> >> 4. PollBackend >> >> Is the availability of the backends known at compile time of PHP or at >> runtime only? Depending on that it might make sense to only >> conditionally define the enum cases, allowing users to check >> availability with `defined()` or checking the output of `::cases()`. >> Alternatively, a `public static function getAvailableBackends(): array` >> could be added. >> >> > It's compile time but not sure if I like exposing enum only if compiled in > as it makes the checks harder. This part is not really something that > users should use but it's really more for testing purpose. In reality > everyone should just use default Auto... But getAvailableBackends() might > make sense even for testing. Maybe it could also have per enum method > isAvailable() so user can check that but not sure if it's needed. > >
Added getAvailableBackends(), isAvailable() and supportsEdgeTriggering() methods. Kind regards, Jakub

Ben Ramsey

189 days ago
On 12/26/25 11:57, Jakub Zelenka wrote:
> On Fri, Oct 31, 2025 at 1:06 PM Jakub Zelenka <bukka@php.net> wrote: > >> Hi, >> >> On Fri, Oct 31, 2025 at 10:40 AM Tim Düsterhus <tim@bastelstu.be> wrote: >> >>> Hi >>> >>> Am 2025-10-30 22:06, schrieb Jakub Zelenka: >>>> I would like to introduce a new polling API RFC that is part of my >>>> stream >>>> evolution work: >>>> >>>> https://wiki.php.net/rfc/poll_api >>> >>> 1. >>> >>> Thank you for the RFC. I've taken a first skim of the proposal and it >>> immediately raised the question of naming and namespacing in particular. >>> Our naming policy at >>> https://github.com/php/policies/blob/main/coding-standards-and-naming.rst#bundled-ewill >>> be alwaysxtensions >>> <https://github.com/php/policies/blob/main/coding-standards-and-naming.rst#bundled-extensions> >>> says that “namespaces SHOULD be used” and given that this is a >>> completely new API, I think we should namespace them. >>> >>> My understanding is that the proposed API relies on a file descriptor >>> and not something like a timeout. It therefore makes sense to me to put >>> it into a `namespace Io\Poll;` or similar. We would then also have: >>> >>> namespace Io; >>> class IoException extends \Exception {} >>> namespace Io\Poll; >>> class PollException extends \Io\IoException {} >>> >>> >> I thought about this and think this might be a good idea. >> >> > This is implemented as suggested and RFC updated. >
Has anyone done any research into the userland impact that introducing `Io` and `Io\Poll` namespaces to core might have? I don't see any discussion regarding this in the thread or in the RFC. Cheers, Ben

Jakub Zelenka

188 days ago
Hi, On Tue, Feb 24, 2026 at 2:32 AM Ben Ramsey <ramsey@php.net> wrote:
> On 12/26/25 11:57, Jakub Zelenka wrote: > > On Fri, Oct 31, 2025 at 1:06 PM Jakub Zelenka <bukka@php.net> wrote: > > > >> Hi, > >> > >> On Fri, Oct 31, 2025 at 10:40 AM Tim Düsterhus <tim@bastelstu.be> > wrote: > >> > >>> Hi > >>> > >>> Am 2025-10-30 22:06, schrieb Jakub Zelenka: > >>>> I would like to introduce a new polling API RFC that is part of my > >>>> stream > >>>> evolution work: > >>>> > >>>> https://wiki.php.net/rfc/poll_api > >>> > >>> 1. > >>> > >>> Thank you for the RFC. I've taken a first skim of the proposal and it > >>> immediately raised the question of naming and namespacing in > particular. > >>> Our naming policy at > >>> > https://github.com/php/policies/blob/main/coding-standards-and-naming.rst#bundled-ewill > >>> be alwaysxtensions > >>> < > https://github.com/php/policies/blob/main/coding-standards-and-naming.rst#bundled-extensions > > > >>> says that “namespaces SHOULD be used” and given that this is a > >>> completely new API, I think we should namespace them. > >>> > >>> My understanding is that the proposed API relies on a file descriptor > >>> and not something like a timeout. It therefore makes sense to me to put > >>> it into a `namespace Io\Poll;` or similar. We would then also have: > >>> > >>> namespace Io; > >>> class IoException extends \Exception {} > >>> namespace Io\Poll; > >>> class PollException extends \Io\IoException {} > >>> > >>> > >> I thought about this and think this might be a good idea. > >> > >> > > This is implemented as suggested and RFC updated. > > > > > Has anyone done any research into the userland impact that introducing > `Io` and `Io\Poll` namespaces to core might have? I don't see any > discussion regarding this in the thread or in the RFC. > >
I did some searching yesterday and haven't found any major project using it. Just one active project using \Io but not \Io\Poll or anything that is planned for the future in this namespace. I updated the RFC. Kind regards, Jakub

Dennis Birkholz

304 days ago
Hi Jakub, Am 30.10.25 um 10:06 PM schrieb Jakub Zelenka:
> I would like to introduce a new polling API RFC that is part of my > stream evolution work:
thank you for putting this RFC forward. I missed a build-in unified polling API for a long time! I just have some minor remarks: 1. Why not use a "Pollable" interface that will be implemented by Socket, CurlHandle, etc? That would allow to directly use the "resource classes" without a step in between. (The remaining resources should be converted to classes, too, and implement Pollable, obviously). 2. Is there a reason why mysqli (when using async queries) is missing from the Future Scope list? Or did it just not come to mind? Kind regards Dennis

Jakub Zelenka

304 days ago
Hi, On Fri, Oct 31, 2025 at 11:58 PM Dennis Birkholz <php@dennis.birkholz.biz> wrote:
> 1. Why not use a "Pollable" interface that will be implemented by > Socket, CurlHandle, etc? That would allow to directly use the "resource > classes" without a step in between. >
I actually planned to use interface initially but there are few issue with it. - as I mentioned above, not all handles will always allow using file descriptors (e.g. timer for kqueue) - abstract class allow internal api defintion and not calling the PHP functions internally (that's how the above can be handled as well) - it would require exposing the actual fd numbers for streams which after some thinking might not be best idea because it would make easier for people to have two streams for a single fd which might cause issues with filtering, buffering and so on.
> 2. Is there a reason why mysqli (when using async queries) is missing > from the Future Scope list? Or did it just not come to mind? >
I put there just those that I actually plan to implement and are relatively straight forward. For mysqli it might require some abstraction to get the mysqlnd stream so would need to check it out first. In other words I haven't fully investigated it yet. But it should be probably added too. Kind regards, Jakub

Larry Garfield

303 days ago
On Thu, Oct 30, 2025, at 4:06 PM, Jakub Zelenka wrote:
> Hello, > > I would like to introduce a new polling API RFC that is part of my > stream evolution work: > > https://wiki.php.net/rfc/poll_api > > Kind regards, > > Jakub
I freely admit to not being fully versed in this area, so take my feedback with however much sodium chloride you feel is appropriate. - Given that a lot of people reading this are probably no more versed in kernel IO polling than I am, a section early on explaining the context of what is even being discussed would be most appreciated. - I really would rather not add more global constants. Better to at minimum make them class constants of a class that the new API provides. (Maybe PollHandle?) - Conversely, I'm unclear why the PollBackend is an enum. That implies the list of backend implementations is fixed and immutable, and not extensible now or in the future. I find that claim suspect, as there are six already. (I find that following the 0-1-many rule in most cases pays off in the long run.) - PollWatcher is created only by PollContext. OK, then please list PollContext first so that reading PollWatcher I have the, er, context for where it fits. - Speaking of, when there's very long code blocks like this I much prefer to break it up to a block per class, so as to minimize codeblock scrolling. That makes it much easier to read and jump around as I figure out how it all works. - How would requesting a specific poll backend be helpful, if it varies by OS? If I'm running on Windows, asking for the Linux backend wouldn't help me much, or vice versa. I don't see the use case here. (Ie, please describe the use case in more detail.) - Who is the target audience for this? I'm pretty sure it's not anything I normally work on, so it's hard for me to judge if certain decisions are good, bad, or "sucky but we have to." Eg, getData() returning "who the hell knows" strikes me as a footgun in waiting, but I don't have enough context to know if that's an inherited problem from elsewhere. Overall, I think my biggest feedback is "please explain better why any of this matters, because I assume it does somehow but don't understand how from the RFC." --Larry Garfield

=?utf-8?B?6Z+p5aSp5bOw?=

299 days ago
I don't know what changes this proposal could bring to PHP if the aim is simply to introduce event loop mechanisms like epoll or kqueue.&nbsp;&nbsp; Why not make ext-uv (libuv) or ext-event (libev) built-in PHP extensions, just like ext-curl, ext-xml, or ext-bcmath?&nbsp;&nbsp; These event loop libraries have been thoroughly tested across numerous projects, proving to be extremely stable and reliable. They could serve as the foundational infrastructure for implementing asynchronous IO in PHP. ----- Tianfeng.Han &nbsp; &nbsp; ------------------&nbsp;Original&nbsp;------------------ From: "Larry&nbsp;Garfield"; Date: 2025年11月2日(星期天) 凌晨3:24 To: "php internals"; Subject: Re: [PHP-DEV] [RFC] Polling API &nbsp; On Thu, Oct 30, 2025, at 4:06 PM, Jakub Zelenka wrote: &gt; Hello, &gt; &gt; I would like to introduce a new polling API RFC that is part of my &gt; stream evolution work: &gt; &gt; https://wiki.php.net/rfc/poll_api &gt; &gt; Kind regards, &gt; &gt; Jakub I freely admit to not being fully versed in this area, so take my feedback with however much sodium chloride you feel is appropriate. - Given that a lot of people reading this are probably no more versed in kernel IO polling than I am, a section early on explaining the context of what is even being discussed would be most appreciated. - I really would rather not add more global constants.&nbsp; Better to at minimum make them class constants of a class that the new API provides.&nbsp; (Maybe PollHandle?) - Conversely, I'm unclear why the PollBackend is an enum.&nbsp; That implies the list of backend implementations is fixed and immutable, and not extensible now or in the future.&nbsp; I find that claim suspect, as there are six already.&nbsp; (I find that following the 0-1-many rule in most cases pays off in the long run.) - PollWatcher is created only by PollContext.&nbsp; OK, then please list PollContext first so that reading PollWatcher I have the, er, context for where it fits. - Speaking of, when there's very long code blocks like this I much prefer to break it up to a block per class, so as to minimize codeblock scrolling.&nbsp; That makes it much easier to read and jump around as I figure out how it all works. - How would requesting a specific poll backend be helpful, if it varies by OS?&nbsp; If I'm running on Windows, asking for the Linux backend wouldn't help me much, or vice versa.&nbsp; I don't see the use case here.&nbsp; (Ie, please describe the use case in more detail.) - Who is the target audience for this?&nbsp; I'm pretty sure it's not anything I normally work on, so it's hard for me to judge if certain decisions are good, bad, or "sucky but we have to."&nbsp; Eg, getData() returning "who the hell knows" strikes me as a footgun in waiting, but I don't have enough context to know if that's an inherited problem from elsewhere. Overall, I think my biggest feedback is "please explain better why any of this matters, because I assume it does somehow but don't understand how from the RFC." --Larry Garfield

Jakub Zelenka

299 days ago
Hi, First of all, please don't top post. On Thu, Nov 6, 2025 at 3:47 AM 韩天峰 <rango@swoole.com> wrote:
> I don't know what changes this proposal could bring to PHP if the aim is > simply to introduce event loop mechanisms like epoll or kqueue. >
The primary reason for this is to have an internal API that we can use internally. The primary motivation was to actually have a better mechanism for handling signals that can be safely used in ZTS. This is one of the main blocker for introducing coroutine based TSRM mode that could be used by FrankenPHP to use goroutines instead of threads. There was also related timer issue on MacOS and at that time Arnaud came up with kqueue only PoC implentation but we decided that it would be great to have something more generic but we wanted something really minimal without a need for the whole event loop abstraction like libuv or libevent (libev is dead AFAIC) offers. After that I also had few other use cases in FPM as I wanted a bit more generic event handling than what is currently there so I can use it in child before accept. Note that we have got already its own event implementation in FPM so this is more advancement in that area. So when we have an internal API which we plan anyway, then I thought it could be nice to also expose it to user space so project like AMPHP can use it and effectively drop all other backends. In addition it didn't look good that the only current polling mechanism for streams is based on select so this is also effectively replacement for stream_select. Again not all users using it need the full even loop.
> Why not make ext-uv (libuv) or ext-event (libev) built-in PHP extensions, > just like ext-curl, ext-xml, or ext-bcmath? >
Those are all optional extensions because they depend on external library. Currently the only way how we could make it always available is to bundle those libraries but especially libuv is quite bloated and adds unnecessary overhead for our other use cases. We actually discussed it internally if we should use or bundled libevent but the agreement between couple of core devs was to create a simple polling API instead so here we are. I will update the RFC and add more explanation there as this was also requested by Larry and I can see that the motivation and primary use cases for this are not exactly clear. Thanks for the feedback. Kind regards, Jakub

Jakub Zelenka

249 days ago
Hi, On Sat, Nov 1, 2025 at 8:23 PM Larry Garfield <larry@garfieldtech.com> wrote:
> On Thu, Oct 30, 2025, at 4:06 PM, Jakub Zelenka wrote: > > Hello, > > > > I would like to introduce a new polling API RFC that is part of my > > stream evolution work: > > > > https://wiki.php.net/rfc/poll_api > > > > Kind regards, > > > > Jakub > > I freely admit to not being fully versed in this area, so take my feedback > with however much sodium chloride you feel is appropriate. > > - Given that a lot of people reading this are probably no more versed in > kernel IO polling than I am, a section early on explaining the context of > what is even being discussed would be most appreciated. > >
I added a section with some basic explanation so users that are more interested in details can easily search for it.
> - I really would rather not add more global constants. Better to at > minimum make them class constants of a class that the new API provides. > (Maybe PollHandle?) > >
I replaced them with \Io\Poll\Event enum so this should be covered. There are no global constants. The only constants are now on class and they are for error codes.
> - Conversely, I'm unclear why the PollBackend is an enum. That implies > the list of backend implementations is fixed and immutable, and not > extensible now or in the future. I find that claim suspect, as there are > six already. (I find that following the 0-1-many rule in most cases pays > off in the long run.) >
This is unlikely to grow and if it ever grew, it would happen only as a new feature in new minor version. Or do we have any policy that disallows extending enums? This is just a theoretical concern though because all the main backends are covered. The only one that could be potentially added is a different variant for Windows if WSAPoll shows poor performance as it was the case in past but not sure if anyone will do such work.
> - PollWatcher is created only by PollContext. OK, then please list > PollContext first so that reading PollWatcher I have the, er, context for > where it fits. > >
Updated.
> - Speaking of, when there's very long code blocks like this I much prefer > to break it up to a block per class, so as to minimize codeblock > scrolling. That makes it much easier to read and jump around as I figure > out how it all works. > >
Fixed.
> - How would requesting a specific poll backend be helpful, if it varies by > OS? If I'm running on Windows, asking for the Linux backend wouldn't help > me much, or vice versa. I don't see the use case here. (Ie, please > describe the use case in more detail.) >
There is poll supported on all unix variants and then Illumos has also variant of epoll so there are more than one backend for each platform (except Windows that currently support just one). As noted in the RFC, setting of the backend is mostly useful for testing. The actual enum is more useful to get info of the selected backend. Currently the most useful thing is to find out whether edge triggering is possible.
> - Who is the target audience for this? I'm pretty sure it's not anything > I normally work on, so it's hard for me to judge if certain decisions are > good, bad, or "sucky but we have to." Eg, getData() returning "who the > hell knows" strikes me as a footgun in waiting, but I don't have enough > context to know if that's an inherited problem from elsewhere. >
Added a section explaining the motivation and possible users of this API. In terms of getData, we would need to have generics to make it typed as this is for user data. You can see the examples that should give you hopefully some idea how this can be used. Kind regards, Jakub

Derick Rethans

266 days ago
On Thu, 30 Oct 2025, Jakub Zelenka wrote:
> I would like to introduce a new polling API RFC that is part of my > stream evolution work: > > https://wiki.php.net/rfc/poll_api
Under "Event Constants", I realised that we can't use enums for this due to them needing to be OR'ed, but it would be nice if at some point in the future we had a way of doing: PollEvent::Read | PollEvent::Write; The enum PollBackend (not sure if that needs to be a backed enum), but this can not stand: case EventPorts = "eventport"; All the others have the case name and value the same, but this one misses the 's'. /** * Remove this watcher from the poll context * * After removal, the watcher becomes inactive and cannot be reused. */ public function remove(): void {} Wouldn't this leave the PollWatcher object as a zombie: ie, it exists, but you can't do anything with it. Would it be possible to move this API somewhere else, so that the memory manager can just destruct it and release the object? * @param int $maxEvents Maximum number of events to return (-1 for unlimited) * @return array Array of PollWatcher instances that have triggered events * @throws PollException If the wait operation fails */ public function wait(int $timeout = -1, int $maxEvents = -1): array {} I am not sure if I like this returning an array. Would it perhaps be better to always return 1 (or 0 in case of non-blocking) events, which allows typing the return value as ?PollWatcher? Alternative, perhaps this can be split up into two methods, wait() and waitMultiple() to be able to handle both approaches? Under "Future Scope", you have (for example) TimerHandle, but as that will (have to) extend PollHandle, it makes little sense to have a getFileDescriptor() method on PollHandle. Perhaps there should be another (abstract) class, so that you can have: - PollHandle - FileDescriptorPollHandle - StreamPollHandle - SocketPollHandle - CurlPollHandle - TimerPollHandle - SignalPollHandle The only other concern I have is that some polling backends allow for different events to be watched, which makes it harder to write portable code. cheers, Derick
-- https://derickrethans.nl | https://xdebug.org | https://dram.io Author of Xdebug. Like it? Consider supporting me: https://xdebug.org/support mastodon: @derickr@phpc.social @xdebug@phpc.social

Jakub Zelenka

248 days ago
Hi, On Tue, Dec 9, 2025 at 12:48 PM Derick Rethans <derick@php.net> wrote:
> On Thu, 30 Oct 2025, Jakub Zelenka wrote: > > > I would like to introduce a new polling API RFC that is part of my > > stream evolution work: > > > > https://wiki.php.net/rfc/poll_api > > Under "Event Constants", I realised that we can't use enums for this > due to them needing to be OR'ed, but it would be nice if at some point > in the future we had a way of doing: PollEvent::Read | PollEvent::Write; > >
I introduce \Io\Poll\Event enum. It doesn't allow ORing but all functions now accept array of those events which is hopefully good enough and cover this. If we ever introduced something like enum sets supporting OR operator, the functions could accept it in addition to the array.
> The enum PollBackend (not sure if that needs to be a backed enum), but > this can not stand: > > case EventPorts = "eventport"; > > All the others have the case name and value the same, but this one > misses the 's'. >
It's no longer a backed enum so this is sorted.
> > /** > * Remove this watcher from the poll context > * > * After removal, the watcher becomes inactive and cannot be reused. > */ > public function remove(): void {} > > Wouldn't this leave the PollWatcher object as a zombie: ie, it exists, > but you can't do anything with it. Would it be possible to move this API > somewhere else, so that the memory manager can just destruct it and > release the object? >
The PollWatcher was introduced on request from Bob so the new objects are not created after each polling. This should be more efficient but to be able to remove it, it needs to be done explicitly because the reference is held so we need to somehow inform context that it can be removed. I think we could introduce API to re-use it in the future but for now leaving clean up to GC is the way. Btw. there isActive() method that still works after removal and it returns false. The modification method will throw exception if used. If you have some suggestion for better API, I will be happy to consider it.
> > * @param int $maxEvents Maximum number of events to return (-1 for > unlimited) > * @return array Array of PollWatcher instances that have triggered > events > * @throws PollException If the wait operation fails > */ > public function wait(int $timeout = -1, int $maxEvents = -1): array {} > > I am not sure if I like this returning an array. Would it perhaps be > better to always return 1 (or 0 in case of non-blocking) events, which > allows typing the return value as ?PollWatcher? >
Returning just a single event is not ideal for performance if there are many events so not sure it should be the preferred usage of the API.
> > Alternative, perhaps this can be split up into two methods, wait() and > waitMultiple() to be able to handle both approaches? >
I would see it more like introducing waitSingle but it's basically just public function waitSingle(int $timeout = -1): ?PollWatcher { return $this->wait($timeout, 1)[0] ?? null; } so not sure if it's that useful. But if you think, it's worth it, I can add it..?
> > Under "Future Scope", you have (for example) TimerHandle, but as that > will (have to) extend PollHandle, it makes little sense to have a > getFileDescriptor() method on PollHandle.
The timer and signal are actually file descriptors on Linux (timerfd and signalfd) but you are right that this is not always the case. This is however internal and that abstract class offers a different API for internal extending that is more flexible. It means it won't call getFileDescriptor() for internal classes but use direct C API. That getFileDescriptor() is really meant for user classes that extend PollHandle so they do something rather than extending class with empty interface.
> Perhaps there should be > another (abstract) class, so that you can have: > > - PollHandle > - FileDescriptorPollHandle > - StreamPollHandle > - SocketPollHandle > - CurlPollHandle > - TimerPollHandle > - SignalPollHandle > >
So in this case I think I would need to come up with some different way how to allow polling without file descriptor for user classes otherwise user space class could extend PollHandle and it would just do nothing which I don't think is the right thing. I thought about it and I just don't have any idea what that could be. I think that non fd variants don't make much sense for user space or at least I don't see any reasonable use case. The only other concern I have is that some polling backends allow for
> different events to be watched, which makes it harder to write portable > code. >
I tried to make it as portable as possible but edge triggering is just not possible to simulate and I think it's worth exposing it even though it doesn't work on Windows and Solaris. I added at least flag in the backend so it's quite simple to check. Kind regards, Jakub

Tim Düsterhus

226 days ago
Hi On 10/30/25 22:06, Jakub Zelenka wrote:
> I would like to introduce a new polling API RFC that is part of my stream > evolution work: > > https://wiki.php.net/rfc/poll_api
Since you made some larger changes to the RFC, rather than checking each change individually, I've taken another full look at the RFC and have the following remarks: - Context::wait() takes a millisecond timeout. This feels insufficiently granular. I am wondering if we should rather use either micro (as returned by microtime) or nanoseconds (as returned by hrtime) - or possible an instance of DateInterval (which would use microsecond resolution). - For Handle: I assume that getFileDescriptor() should be abstract? - For Handle I am wondering about abstract class vs interface. Since the Handle presumably doesn't have any non-abstract methods, an interface feels more correct / more flexible. - For Handle: What happens if I extend this class in userland and attach it to a Context. Will things break? What if I return the number of a non-existent FD? - For the stubs: It would be useful if you used the “generics notation” for the array returns. e.g. `@return list<Backend>` for Backend::getAvailableBackends(). This makes it easier to understand how exactly the result will look like to check for mistakes / suboptimal choices. - InactiveWatcherException: This one feels more like a programmer error, so should possibly be a PollError / InactiveWatcherError (but I don't know enough about the topic to be sure). - Internal API: For php_poll_wait(), the timeout should probably be a struct timespec for future-proofing. - Policy-wise: I just added the Abstain option to the voting widget. - Examples: I like them. They nicely showcase how the API works! Best regards Tim Düsterhus

Jakub Zelenka

191 days ago
Hi, Thanks for the feedback! On Sun, Jan 18, 2026 at 1:24 PM Tim Düsterhus <tim@bastelstu.be> wrote:
> Hi > > On 10/30/25 22:06, Jakub Zelenka wrote: > > I would like to introduce a new polling API RFC that is part of my stream > > evolution work: > > > > https://wiki.php.net/rfc/poll_api > > Since you made some larger changes to the RFC, rather than checking each > change individually, I've taken another full look at the RFC and have > the following remarks: > > - Context::wait() takes a millisecond timeout. This feels insufficiently > granular. I am wondering if we should rather use either micro (as > returned by microtime) or nanoseconds (as returned by hrtime) - or > possible an instance of DateInterval (which would use microsecond > resolution). >
Yeah this makes sense. I used the same convention that is used in streams which means two params so it is changed to: public function wait(int $timeoutSeconds = -1, int $timeoutMicroseconds = 0, int $maxEvents = -1): array {}
> - For Handle: I assume that getFileDescriptor() should be abstract? >
I actually thought that it might be nicer to have default method for internal classes but that wouldn't work well for user classes anyway so it's better to make it abstract which I just did.
> - For Handle I am wondering about abstract class vs interface. Since the > Handle presumably doesn't have any non-abstract methods, an interface > feels more correct / more flexible. >
I have been considering this already and there are reasons why I prefer abstract method here. The abstract method is better for internal implementation and limit overhead of PHP calls that would be required for interface. The advantage is that I can use internal struct API through C operations which allows bypasing the call and can do optimization for some backends - e.g. timeouts for kqueue don't need file descriptor. I also think that there is not really much use case for user space to implement their own handles so such interface would be used only internally anyway. In addition interface would effectively expose the internal stream fd which is currently hidden and makes harder messing up with stream fd which might cause various issues.
> - For Handle: What happens if I extend this class in userland and attach > it to a Context. Will things break? What if I return the number of a > non-existent FD? >
Depending on backend it will result either in FailedHandleAddException (epoll, kqueue, possible event ports as well) or on Windows it would likely fail during wait with FailedPollWaitException. So it can be handled by catching FailedPollOperationException. This is however very unlikely edge case so I don't think there is much point to worry about consistency. As I said the use case for implementing custom Handle is a bit moot.
> - For the stubs: It would be useful if you used the “generics notation” > for the array returns. e.g. `@return list<Backend>` for > Backend::getAvailableBackends(). This makes it easier to understand how > exactly the result will look like to check for mistakes / suboptimal > choices. >
Makes sense. I added it also for Watcher::getTriggeredEvents, Watcher::getWatchedEvents that return list<Event> and Context::wait that returns list<Watcher>.
> - InactiveWatcherException: This one feels more like a programmer error, > so should possibly be a PollError / InactiveWatcherError (but I don't > know enough about the topic to be sure). >
I don't think it should be error as it is not necessary for user to track removal of watcher (which can also happen automatically for one shot events). It should be just fine to normally recover from this rather than requiring checking for isActive. It's more state issue which is usually handled using exceptions IMHO.
> - Internal API: For php_poll_wait(), the timeout should probably be a > struct timespec for future-proofing. > >
This has been changed as suggested (together with introducing microseconds support for user space). In addition to the above changes I also changed ValueError for passing backend that is not available on the current platform to new BackendUnavailableException as this is more runtime specific issue that application might want to handle rather than forcing users to always use Backend::isAvailable. Basically I feel if something can vary and it's not possible to be immediately caught by developer (because they can develop it on different platform), then it should be more an exception than error. Kind regards, Jakub

Tim Düsterhus

177 days ago
Hi Am 2026-02-22 17:16, schrieb Jakub Zelenka:
> Thanks for the feedback!
Thank you. I've given the RFC another read now and have some minor things to remark: 1. I don't think it makes sense for the majority of the classes to be serialized, because they rely on “in-process” state, specifically file descriptors. This includes Context, Watcher, and the handles. Serializing the enum and Exceptions by themselves is fine. The RFC should note that serialization is prevented and the implementation adjusted. 2. Similarly for any new classes, I recommend to always set `@strict-properties` to prevent shenanigans with folks accidentally adding dynamic properties to these classes. This should be done for everything, including the exceptions. 3. I have a question as someone who never used Fibers in production: Is it necessary or helpful for the API to provide some “convenience” functionality related to Fibers (e.g. automatically suspending or unsuspending Fibers)? I'm asking now, because adding that functionality later might be a breaking change in behavior. If it's not necessary or can be added in a backwards compatible way, that's fine, of course.
> On Sun, Jan 18, 2026 at 1:24 PM Tim Düsterhus <tim@bastelstu.be> wrote: >> - Context::wait() takes a millisecond timeout. This feels >> insufficiently >> granular. I am wondering if we should rather use either micro (as >> returned by microtime) or nanoseconds (as returned by hrtime) - or >> possible an instance of DateInterval (which would use microsecond >> resolution). >> > > Yeah this makes sense. I used the same convention that is used in > streams > which means two params so it is changed to: > > public function wait(int $timeoutSeconds = -1, int $timeoutMicroseconds > = > 0, int $maxEvents = -1): array {}
I feel that using two parameters is providing for a very clunky API, which is also acknowledged by the doc comment on $timeoutMicroseconds "Only used when $timeoutSeconds >= 0". I would have suggested DateInterval again, but I'm just seeing that it seems there is no documented way to manually construct a DateInterval with microseconds. Perhaps the following suggestion would be a good middleground: - Change $timeoutSeconds to int|null: If null is specified, the timeout is infinite. - Throw ValueError if $timeoutSeconds < 0 - Throw ValueError if $timeoutMicroseconds < 0 - Throw ValueError if $timeoutSeconds === null && timeoutMicroseconds > 0 `null` is a much more natural value for “no timeout” and by throwing ValueError we make sure that we don't have a situation where a parameter is silently ignored. Similarly for `$maxEvents` it might make sense to use `null` for “use a default value”? Also: What happens if I specify `$maxEvents = 0`? Is this useful?
> >> - For Handle I am wondering about abstract class vs interface. Since >> the >> Handle presumably doesn't have any non-abstract methods, an interface >> feels more correct / more flexible. >> > > I have been considering this already and there are reasons why I prefer > abstract method here. > > The abstract method is better for internal implementation and limit > overhead of PHP calls that would be required for interface. The > advantage > is that I can use internal struct API through C operations which allows > bypasing the call and can do optimization for some backends - e.g. > timeouts > for kqueue don't need file descriptor.
You can bypass the “VM method” for interfaces and abstract methods alike. A good example would be ext/random which allows changing the engines by the Random\Engine interface, but for engines implemented in C, the Randomizer doesn't go through the userland API, but instead calls the engines directly.
> I also think that there is not really much use case for user space to > implement their own handles so such interface would be used only > internally > anyway.
This applies equally to interfaces and abstract methods. The abstract base class however will make it much weirder when a specific (future) handle might need to implement additional interfaces or abstract classes.
> In addition interface would effectively expose the internal stream fd > which > is currently hidden and makes harder messing up with stream fd which > might > cause various issues.
I don't understand that point. For both an interface and an abstract method, the method would exist on the child class and thus can be called by a developer.
>> - For Handle: What happens if I extend this class in userland and >> attach >> it to a Context. Will things break? What if I return the number of a >> non-existent FD? >> > > Depending on backend it will result either in FailedHandleAddException > (epoll, kqueue, possible event ports as well) or on Windows it would > likely > fail during wait with FailedPollWaitException. So it can be handled by > catching FailedPollOperationException. This is however very unlikely > edge > case so I don't think there is much point to worry about consistency. > As I > said the use case for implementing custom Handle is a bit moot.
Okay, I just wanted to make sure that nothing *bad* happens. If it safely errors, this is fine. Best regards Tim Düsterhus

Jakub Zelenka

173 days ago
Hi, On Sun, Mar 8, 2026 at 12:27 PM Tim Düsterhus <tim@bastelstu.be> wrote:
> Hi > > Am 2026-02-22 17:16, schrieb Jakub Zelenka: > > Thanks for the feedback! > > Thank you. I've given the RFC another read now and have some minor > things to remark: > > 1. I don't think it makes sense for the majority of the classes to be > serialized, because they rely on “in-process” state, specifically file > descriptors. This includes Context, Watcher, and the handles. > Serializing the enum and Exceptions by themselves is fine. The RFC > should note that serialization is prevented and the implementation > adjusted. > >
Sure this was my omission - I marked all classes as not serializable.
> 2. Similarly for any new classes, I recommend to always set > `@strict-properties` to prevent shenanigans with folks accidentally > adding dynamic properties to these classes. This should be done for > everything, including the exceptions. > >
And I also marked them with @strict-properties
> 3. I have a question as someone who never used Fibers in production: Is > it necessary or helpful for the API to provide some “convenience” > functionality related to Fibers (e.g. automatically suspending or > unsuspending Fibers)? I'm asking now, because adding that functionality > later might be a breaking change in behavior. If it's not necessary or > can be added in a backwards compatible way, that's fine, of course. > >
I can't really think about something reasonable here - it seems better not to mix it. And even if we wanted to add something, I think it could be backward compatible.
> > On Sun, Jan 18, 2026 at 1:24 PM Tim Düsterhus <tim@bastelstu.be> wrote: > >> - Context::wait() takes a millisecond timeout. This feels > >> insufficiently > >> granular. I am wondering if we should rather use either micro (as > >> returned by microtime) or nanoseconds (as returned by hrtime) - or > >> possible an instance of DateInterval (which would use microsecond > >> resolution). > >> > > > > Yeah this makes sense. I used the same convention that is used in > > streams > > which means two params so it is changed to: > > > > public function wait(int $timeoutSeconds = -1, int $timeoutMicroseconds > > = > > 0, int $maxEvents = -1): array {} > > I feel that using two parameters is providing for a very clunky API, > which is also acknowledged by the doc comment on $timeoutMicroseconds > "Only used when $timeoutSeconds >= 0". I would have suggested > DateInterval again, but I'm just seeing that it seems there is no > documented way to manually construct a DateInterval with microseconds. > > Perhaps the following suggestion would be a good middleground: > > - Change $timeoutSeconds to int|null: If null is specified, the timeout > is infinite. > - Throw ValueError if $timeoutSeconds < 0 > - Throw ValueError if $timeoutMicroseconds < 0 > - Throw ValueError if $timeoutSeconds === null && timeoutMicroseconds > > 0 > > `null` is a much more natural value for “no timeout” and by throwing > ValueError we make sure that we don't have a situation where a parameter > is silently ignored. >
Yeah this makes much more sense and stream_select works similar - not sure why I didn't add it. It's fixed.
> > Similarly for `$maxEvents` it might make sense to use `null` for “use a > default value”? Also: What happens if I specify `$maxEvents = 0`? Is > this useful? >
0 was used to select default so the same as -1. I changed it to null and it now has to be positive value.
> > > > >> - For Handle I am wondering about abstract class vs interface. Since > >> the > >> Handle presumably doesn't have any non-abstract methods, an interface > >> feels more correct / more flexible. > >> > > > > I have been considering this already and there are reasons why I prefer > > abstract method here. > > > > The abstract method is better for internal implementation and limit > > overhead of PHP calls that would be required for interface. The > > advantage > > is that I can use internal struct API through C operations which allows > > bypasing the call and can do optimization for some backends - e.g. > > timeouts > > for kqueue don't need file descriptor. > > You can bypass the “VM method” for interfaces and abstract methods > alike. A good example would be ext/random which allows changing the > engines by the Random\Engine interface, but for engines implemented in > C, the Randomizer doesn't go through the userland API, but instead calls > the engines directly. > >
Yeah I think perf would be solvable here actually.
> > I also think that there is not really much use case for user space to > > implement their own handles so such interface would be used only > > internally > > anyway. > > This applies equally to interfaces and abstract methods. The abstract > base class however will make it much weirder when a specific (future) > handle might need to implement additional interfaces or abstract > classes. > > > In addition interface would effectively expose the internal stream fd > > which > > is currently hidden and makes harder messing up with stream fd which > > might > > cause various issues. > > I don't understand that point. For both an interface and an abstract > method, the method would exist on the child class and thus can be called > by a developer. > >
Well if it is abstract, then the method can be protected and because the classes are final, user spaces cannot call it. But for interface I would need to make it public which means that StreamHandle would need to expose callabable (public) method. I know that I could just return 0 and use different handling internally but I think this would be surprising and created obvious inconsistency. I mean it's fine if the calls happen internally but if the exposed methods are just dummy and return nonsense for user space, then I don't think it would be a good design. Kind regards, Jakub

Tim Düsterhus

154 days ago
Hi Sorry for the late feedback, it's hard juggling all the different RFCs that are in progress to provide meaningful feedback. Am 2026-03-11 22:07, schrieb Jakub Zelenka:
>> > I also think that there is not really much use case for user space to >> > implement their own handles so such interface would be used only >> > internally >> > anyway. >> >> This applies equally to interfaces and abstract methods. The abstract >> base class however will make it much weirder when a specific (future) >> handle might need to implement additional interfaces or abstract >> classes. >> >> > In addition interface would effectively expose the internal stream fd >> > which >> > is currently hidden and makes harder messing up with stream fd which >> > might >> > cause various issues. >> >> I don't understand that point. For both an interface and an abstract >> method, the method would exist on the child class and thus can be >> called >> by a developer. >> >> > Well if it is abstract, then the method can be protected and because > the > classes are final, user spaces cannot call it. But for interface I > would
Protected methods of internal classes should still be accessible to Reflection.
> need to make it public which means that StreamHandle would need to > expose > callabable (public) method. I know that I could just return 0 and use > different handling internally but I think this would be surprising and > created obvious inconsistency. I mean it's fine if the calls happen > internally but if the exposed methods are just dummy and return > nonsense > for user space, then I don't think it would be a good design.
It's equally weird that there is a protected method that is effectively internally called by an unrelated class (Context) that is not supposed to know about it. Perhaps the correct solution would then be removing the `getFileDescriptor()` method from the public API entirely and change `Handle` to be an empty “marker interface” that cannot be implemented in userland (similarly to Throwable) to leave all options open for the future? Best regards Tim Düsterhus

Jakub Zelenka

154 days ago
Hi, On Tue, Mar 31, 2026 at 11:23 AM Tim Düsterhus <tim@bastelstu.be> wrote:
> Hi > > Sorry for the late feedback, it's hard juggling all the different RFCs > that are in progress to provide meaningful feedback. > > Am 2026-03-11 22:07, schrieb Jakub Zelenka: > >> > I also think that there is not really much use case for user space to > >> > implement their own handles so such interface would be used only > >> > internally > >> > anyway. > >> > >> This applies equally to interfaces and abstract methods. The abstract > >> base class however will make it much weirder when a specific (future) > >> handle might need to implement additional interfaces or abstract > >> classes. > >> > >> > In addition interface would effectively expose the internal stream fd > >> > which > >> > is currently hidden and makes harder messing up with stream fd which > >> > might > >> > cause various issues. > >> > >> I don't understand that point. For both an interface and an abstract > >> method, the method would exist on the child class and thus can be > >> called > >> by a developer. > >> > >> > > Well if it is abstract, then the method can be protected and because > > the > > classes are final, user spaces cannot call it. But for interface I > > would > > Protected methods of internal classes should still be accessible to > Reflection. > >
Good point.
> > need to make it public which means that StreamHandle would need to > > expose > > callabable (public) method. I know that I could just return 0 and use > > different handling internally but I think this would be surprising and > > created obvious inconsistency. I mean it's fine if the calls happen > > internally but if the exposed methods are just dummy and return > > nonsense > > for user space, then I don't think it would be a good design. > > It's equally weird that there is a protected method that is effectively > internally called by an unrelated class (Context) that is not supposed > to know about it. Perhaps the correct solution would then be removing > the `getFileDescriptor()` method from the public API entirely and change > `Handle` to be an empty “marker interface” that cannot be implemented in > userland (similarly to Throwable) to leave all options open for the > future? > >
This (marker interface just for internal classes) is actually a really good idea. Will change it. Thanks Jakub

Jakub Zelenka

128 days ago
Hi, On Tue, Mar 31, 2026 at 3:24 PM Jakub Zelenka <bukka@php.net> wrote:
> Hi, > > On Tue, Mar 31, 2026 at 11:23 AM Tim Düsterhus <tim@bastelstu.be> wrote: > >> Hi >> >> Sorry for the late feedback, it's hard juggling all the different RFCs >> that are in progress to provide meaningful feedback. >> >> Am 2026-03-11 22:07, schrieb Jakub Zelenka: >> >> > I also think that there is not really much use case for user space to >> >> > implement their own handles so such interface would be used only >> >> > internally >> >> > anyway. >> >> >> >> This applies equally to interfaces and abstract methods. The abstract >> >> base class however will make it much weirder when a specific (future) >> >> handle might need to implement additional interfaces or abstract >> >> classes. >> >> >> >> > In addition interface would effectively expose the internal stream fd >> >> > which >> >> > is currently hidden and makes harder messing up with stream fd which >> >> > might >> >> > cause various issues. >> >> >> >> I don't understand that point. For both an interface and an abstract >> >> method, the method would exist on the child class and thus can be >> >> called >> >> by a developer. >> >> >> >> >> > Well if it is abstract, then the method can be protected and because >> > the >> > classes are final, user spaces cannot call it. But for interface I >> > would >> >> Protected methods of internal classes should still be accessible to >> Reflection. >> >> > Good point. > > >> > need to make it public which means that StreamHandle would need to >> > expose >> > callabable (public) method. I know that I could just return 0 and use >> > different handling internally but I think this would be surprising and >> > created obvious inconsistency. I mean it's fine if the calls happen >> > internally but if the exposed methods are just dummy and return >> > nonsense >> > for user space, then I don't think it would be a good design. >> >> It's equally weird that there is a protected method that is effectively >> internally called by an unrelated class (Context) that is not supposed >> to know about it. Perhaps the correct solution would then be removing >> the `getFileDescriptor()` method from the public API entirely and change >> `Handle` to be an empty “marker interface” that cannot be implemented in >> userland (similarly to Throwable) to leave all options open for the >> future? >> >> > This (marker interface just for internal classes) is actually a really > good idea. Will change it. > >
I just implemented this and it's much better so thanks for this. RFC updated as well.

Tim Düsterhus

120 days ago
Hi Am 2026-04-25 23:58, schrieb Jakub Zelenka:
>> This (marker interface just for internal classes) is actually a really >> good idea. Will change it. >> >> > I just implemented this and it's much better so thanks for this. RFC > updated as well.
I've just given the RFC another read and the changes look good to me. Best regards Tim Düsterhus

Jakub Zelenka

155 days ago
Hi, On Thu, Oct 30, 2025 at 10:06 PM Jakub Zelenka <bukka@php.net> wrote:
> Hello, > > I would like to introduce a new polling API RFC that is part of my stream > evolution work: > > https://wiki.php.net/rfc/poll_api > >
Just a heads up that I plan to open voting on Tuesday 31st March around the same time like now. Kind regards, Jakub

Удальцов Валентин

154 days ago
вс, 29 мар. 2026 г., 23:54 Jakub Zelenka <bukka@php.net>:
> Hi, > > On Thu, Oct 30, 2025 at 10:06 PM Jakub Zelenka <bukka@php.net> wrote: > >> Hello, >> >> I would like to introduce a new polling API RFC that is part of my stream >> evolution work: >> >> https://wiki.php.net/rfc/poll_api >> >> > Just a heads up that I plan to open voting on Tuesday 31st March around > the same time like now. > > Kind regards, > > Jakub >
Hi, Jakub! Why do you use get and is methods if we have powerful prioritize with get hooks nowadays? I think that PHP should promote its modern features in its own API.

Jakub Zelenka

154 days ago
Hi, On Tue, Mar 31, 2026 at 11:41 AM Valentin Udaltsov < udaltsov.valentin@gmail.com> wrote:
> вс, 29 мар. 2026 г., 23:54 Jakub Zelenka <bukka@php.net>: > >> Hi, >> >> On Thu, Oct 30, 2025 at 10:06 PM Jakub Zelenka <bukka@php.net> wrote: >> >>> Hello, >>> >>> I would like to introduce a new polling API RFC that is part of my >>> stream evolution work: >>> >>> https://wiki.php.net/rfc/poll_api >>> >>> >> Just a heads up that I plan to open voting on Tuesday 31st March around >> the same time like now. >> >> Kind regards, >> >> Jakub >> > > Hi, Jakub! > > Why do you use get and is methods if we have powerful prioritize with get > hooks nowadays? I think that PHP should promote its modern features in its > own API. >
There is actually no internal API for hooks so it would require using object handlers (which is from the user space point of view just __get). It means no stubs declaration either. We also don't have an internal policy where to use hooks and where methods. So there are still lots of blockers to start using it in core. Kind regards, Jakub

Tim Düsterhus

151 days ago
Hi Am 2026-03-31 15:23, schrieb Jakub Zelenka:
> There is actually no internal API for hooks so it would require using > object handlers (which is from the user space point of view just > __get). It > means no stubs declaration either. We also don't have an internal > policy > where to use hooks and where methods. So there are still lots of > blockers > to start using it in core.
While I agree with the conclusion that using hooks internally is complicated and there is no real policy there, Valentin was actually making a good point: We could use regular `readonly` properties for those cases where we're just exposing a “constructor parameter”. Specifically: - Context::getBackend() -> public readonly Backend $backend; - Watcher::getHandle() -> public readonly Handle $handle; Potentially Watcher::$data could also be a regular (non-readonly) property, or does `modifyData()` contain additional logic that is not just “store the value somewhere”? There is precedent for this kind of regular `readonly` property referring to an input object in ext/random with Randomizer::$engine: https://github.com/php/php-src/blob/32c1931f18109655bc074dd5cda3248b838de636/ext/random/random.stub.php#L139 Best regards Tim Düsterhus

Jakub Zelenka

128 days ago
On Thu, Apr 2, 2026 at 10:15 PM Tim Düsterhus <tim@bastelstu.be> wrote:
> Hi > > Am 2026-03-31 15:23, schrieb Jakub Zelenka: > > There is actually no internal API for hooks so it would require using > > object handlers (which is from the user space point of view just > > __get). It > > means no stubs declaration either. We also don't have an internal > > policy > > where to use hooks and where methods. So there are still lots of > > blockers > > to start using it in core. > > While I agree with the conclusion that using hooks internally is > complicated and there is no real policy there, Valentin was actually > making a good point: We could use regular `readonly` properties for > those cases where we're just exposing a “constructor parameter”. > Specifically: > > - Context::getBackend() -> public readonly Backend $backend; > - Watcher::getHandle() -> public readonly Handle $handle; > > Potentially Watcher::$data could also be a regular (non-readonly) > property, or does `modifyData()` contain additional logic that is not > just “store the value somewhere”? >
I tried to convert few but it resulted in quite ugly inconsistent mess. Some of those need back calls to backend so they are really not ideal for this. So I will stick with the current getters as the API is a bit nicer with them IMO. Kind regards, Jakub

Jakub Zelenka

106 days ago
Hi, On Thu, Oct 30, 2025 at 10:06 PM Jakub Zelenka <bukka@php.net> wrote:
> Hello, > > I would like to introduce a new polling API RFC that is part of my stream > evolution work: > > https://wiki.php.net/rfc/poll_api > >
Just a heads up that I plan to open voting on this in 48 hours (18th May night). Kind regards, Jakub