Skip to content

Streams Poll API#19572

Open
bukka wants to merge 2 commits into
php:masterfrom
bukka:poll_api
Open

Streams Poll API#19572
bukka wants to merge 2 commits into
php:masterfrom
bukka:poll_api

Conversation

@bukka
Copy link
Copy Markdown
Member

@bukka bukka commented Aug 24, 2025

This introduces a new API for polling that supports various backends:

  • epoll for Linux
  • kqueue for MacOS and BSD variants
  • WSAPoll for Windows
  • Event ports for Solaris and Illumos
  • poll for the rest

The API tries to reflect primarily epoll and the rest is done in a way so it is compatible.

RFC: https://wiki.php.net/rfc/poll_api

@bukka
Copy link
Copy Markdown
Member Author

bukka commented Aug 24, 2025

Currently just kqueue and poll on MacOS are properly tested. The epoll should be easy to get tested but will need to then properly look to iocp and event ports. For event ports I will need to first set OmniOS (Illumos) up.

@bukka
Copy link
Copy Markdown
Member Author

bukka commented Aug 24, 2025

So epoll looks good and all tests passing there.

Tha API also builds on Windows but unsuprisingly the iocp backend does not really work well so this is someting that I will be looking to.

And then event ports will likely need some work as well which I will look then too.

@bukka bukka force-pushed the poll_api branch 3 times, most recently from 2d53464 to 4edefc1 Compare September 24, 2025 20:28
@bukka bukka force-pushed the poll_api branch 2 times, most recently from e2f2ec8 to 22689ad Compare October 30, 2025 20:55
@bukka bukka marked this pull request as ready for review October 30, 2025 20:55
@bukka bukka force-pushed the poll_api branch 2 times, most recently from a68b8be to 1d55dce Compare December 24, 2025 00:18
@bukka bukka force-pushed the poll_api branch 2 times, most recently from a74b2b8 to d48b64f Compare February 22, 2026 15:43
@bukka bukka force-pushed the poll_api branch 2 times, most recently from 9c3c346 to 1378608 Compare March 15, 2026 16:17
bukka added 2 commits April 25, 2026 21:10
This introduces various IO polling backend including epoll, kqueue,
event ports, poll and WSAPoll. It makes the usage compatible between
backends.
It makes use of internal polling API and exposes its functionality to
user space.
}
?>
--EXPECT--
ERROR: Handle already added
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need to throw when this happens? can't we just ignore?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue is that the second call can have different events and data and that would get completely ignored. I should probably change the second call to:

pt_stream_poll_add($poll_ctx, $socket1w, [Io\Poll\Event::Read], "socket_ignored_data_and_event");

to make it clearer...

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well that read even doesn't really make much sense for write socket but you get what I mean :)

Copy link
Copy Markdown
Member

@DanielEScherzer DanielEScherzer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not going to pretend like I fully understand this, but some comments

Comment thread build/php.m4
])

dnl Set poll mechanisms including poll that is always available
poll_mechanisms="$poll_mechanisms poll"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if this matters, but $poll_mechanisms will always have a leading space (unless autoconf removes those automatically???) - if you start with poll being included at the top, then there wouldn't be that space

Comment thread ext/standard/io_poll.c
+----------------------------------------------------------------------+
| Copyright (c) The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will need update for the license change

Comment thread ext/standard/io_poll.c

/* Accessor macros */
#define PHP_POLL_CONTEXT_OBJ_FROM_ZOBJ(_obj) \
((php_io_poll_context_object *) ((char *) (_obj) - XtOffsetOf(php_io_poll_context_object, std)))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will need update for XtOffsetOf removal

Comment thread ext/standard/io_poll.c
#define PHP_POLL_CONTEXT_OBJ_FROM_ZV(_zv) PHP_POLL_CONTEXT_OBJ_FROM_ZOBJ(Z_OBJ_P(_zv))

/* Helper to throw failed operation exceptions with error code */
static inline void php_io_poll_throw_failed_operation(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that this kind of helper function actually helps - it me, it just adds another level of indirection. C.f. #21399 - what is the issue with using zend_throw_exception directly?

Comment thread ext/standard/io_poll.c
case PHP_POLL_BACKEND_WSAPOLL:
return "WSAPoll";
case PHP_POLL_BACKEND_AUTO:
default:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest that default be marked as unreachable rather than working - if someone casts an invalid integer to php_poll_backend_type we should fail loudly imo

Comment thread main/poll/poll_core.c
Comment on lines +117 to +134
PHPAPI bool php_poll_backend_supports_edge_triggering(php_poll_backend_type backend)
{
if (backend == PHP_POLL_BACKEND_AUTO) {
/* Check the first (best) available backend */
if (num_registered_backends > 0 && registered_backends[0]) {
return registered_backends[0]->supports_et;
}
return false;
}

for (int i = 0; i < num_registered_backends; i++) {
if (registered_backends[i] && registered_backends[i]->type == backend) {
return registered_backends[i]->supports_et;
}
}

return false;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like this can be deduplicated with php_poll_get_backend_ops()

Suggested change
PHPAPI bool php_poll_backend_supports_edge_triggering(php_poll_backend_type backend)
{
if (backend == PHP_POLL_BACKEND_AUTO) {
/* Check the first (best) available backend */
if (num_registered_backends > 0 && registered_backends[0]) {
return registered_backends[0]->supports_et;
}
return false;
}
for (int i = 0; i < num_registered_backends; i++) {
if (registered_backends[i] && registered_backends[i]->type == backend) {
return registered_backends[i]->supports_et;
}
}
return false;
}
PHPAPI bool php_poll_backend_supports_edge_triggering(php_poll_backend_type backend)
{
const php_poll_backend_ops *backend_ops = php_poll_get_backend_ops(backend);
return backend_ops && backend_ops->supports_et;
}

Comment thread main/poll/poll_core.c
}

/* Get backend name */
PHPAPI const char *php_poll_backend_name(php_poll_ctx *ctx)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you document why this function and the ones below support having a missing context? Or a context with no backend_ops set? That seems confusing

Comment thread main/poll/poll_handle.c
Comment on lines +58 to +60
php_poll_handle_ops php_poll_handle_default_ops = { .get_fd = php_poll_handle_default_get_fd,
.is_valid = php_poll_handle_default_is_valid,
.cleanup = php_poll_handle_default_cleanup };
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a bit hard to read, suggest formatting like the other struct initializations

Suggested change
php_poll_handle_ops php_poll_handle_default_ops = { .get_fd = php_poll_handle_default_get_fd,
.is_valid = php_poll_handle_default_is_valid,
.cleanup = php_poll_handle_default_cleanup };
php_poll_handle_ops php_poll_handle_default_ops = {
.get_fd = php_poll_handle_default_get_fd,
.is_valid = php_poll_handle_default_is_valid,
.cleanup = php_poll_handle_default_cleanup
};

Comment thread main/php_poll.h
#define PHP_POLL_ERROR_CODE_NOSUPPORT 11

/* Error codes */
typedef enum {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I'm a bit confused as to the difference between the defined codes and the corresponding enum cases - why not always use the enum cases, and just put the underlying literals integers in the enum definition?

Comment thread main/php_poll.h
* @param handle The handle object
* @return true if valid, false if invalid
*/
int (*is_valid)(php_poll_handle_object *handle);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the documentation says that this returns true/false

Suggested change
int (*is_valid)(php_poll_handle_object *handle);
bool (*is_valid)(php_poll_handle_object *handle);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants