diff --git a/build/php.m4 b/build/php.m4 index e82e856667e94..c8ce0ac681704 100644 --- a/build/php.m4 +++ b/build/php.m4 @@ -1375,6 +1375,48 @@ int main(void) { ]) ]) +AC_DEFUN([PHP_POLL_MECHANISMS], +[ + AC_MSG_CHECKING([for polling mechanisms]) + poll_mechanisms="" + + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([ + #include + ], [ + int fd = epoll_create(1); + return fd; + ])], [ + AC_DEFINE([HAVE_EPOLL], [1], [Define if epoll is available]) + poll_mechanisms="$poll_mechanisms epoll" + ]) + + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([ + #include + #include + ], [ + int kq = kqueue(); + return kq; + ])], [ + AC_DEFINE([HAVE_KQUEUE], [1], [Define if kqueue is available]) + poll_mechanisms="$poll_mechanisms kqueue" + ]) + + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([ + #include + ], [ + int port = port_create(); + return port; + ])], [ + AC_DEFINE([HAVE_EVENT_PORTS], [1], [Define if event ports are available]) + poll_mechanisms="$poll_mechanisms eventport" + ]) + + dnl Set poll mechanisms including poll that is always available + poll_mechanisms="$poll_mechanisms poll" + + AC_MSG_RESULT([$poll_mechanisms]) +]) + dnl ---------------------------------------------------------------------------- dnl Library/function existence and build sanity checks. dnl ---------------------------------------------------------------------------- diff --git a/configure.ac b/configure.ac index 54f6157d96aa4..cf5f6ffbc17b4 100644 --- a/configure.ac +++ b/configure.ac @@ -442,6 +442,7 @@ AC_CHECK_HEADERS(m4_normalize([ ]) PHP_FOPENCOOKIE +PHP_POLL_MECHANISMS PHP_BROKEN_GETCWD AS_VAR_IF([GCC], [yes], [PHP_BROKEN_GCC_STRLEN_OPT]) @@ -1693,6 +1694,17 @@ PHP_ADD_SOURCES_X([main], [PHP_FASTCGI_OBJS], [no]) +PHP_ADD_SOURCES([main/poll], m4_normalize([ + poll_backend_epoll.c + poll_backend_eventport.c + poll_backend_kqueue.c + poll_backend_poll.c + poll_core.c + poll_fd_table.c + poll_handle.c + ]), + [-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1]) + PHP_ADD_SOURCES([main/streams], m4_normalize([ cast.c filter.c diff --git a/ext/reflection/tests/ReflectionExtension_getClassNames_basic.phpt b/ext/reflection/tests/ReflectionExtension_getClassNames_basic.phpt index 47813255381e4..6d654022405c5 100644 --- a/ext/reflection/tests/ReflectionExtension_getClassNames_basic.phpt +++ b/ext/reflection/tests/ReflectionExtension_getClassNames_basic.phpt @@ -14,7 +14,12 @@ foreach ($classNames as $className) { --EXPECT-- AssertionError Directory +PollContext +PollException +PollHandle +PollWatcher RoundingMode StreamBucket +StreamPollHandle __PHP_Incomplete_Class php_user_filter diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index e0c4230dae27c..e0b1155c71479 100644 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -303,6 +303,7 @@ PHP_MINIT_FUNCTION(basic) /* {{{ */ BASIC_MINIT_SUBMODULE(browscap) BASIC_MINIT_SUBMODULE(standard_filters) BASIC_MINIT_SUBMODULE(user_filters) + BASIC_MINIT_SUBMODULE(poll) BASIC_MINIT_SUBMODULE(password) BASIC_MINIT_SUBMODULE(image) diff --git a/ext/standard/basic_functions.h b/ext/standard/basic_functions.h index bad6fcf4e645e..7a3ea4c13f701 100644 --- a/ext/standard/basic_functions.h +++ b/ext/standard/basic_functions.h @@ -42,6 +42,7 @@ PHP_MINFO_FUNCTION(basic); ZEND_API void php_get_highlight_struct(zend_syntax_highlighter_ini *syntax_highlighter_ini); +PHP_MINIT_FUNCTION(poll); PHP_MINIT_FUNCTION(user_filters); PHP_RSHUTDOWN_FUNCTION(user_filters); PHP_RSHUTDOWN_FUNCTION(browscap); diff --git a/ext/standard/config.m4 b/ext/standard/config.m4 index ef6b3c5a01018..6c19dd7fed0b1 100644 --- a/ext/standard/config.m4 +++ b/ext/standard/config.m4 @@ -432,6 +432,7 @@ PHP_NEW_EXTENSION([standard], m4_normalize([ pageinfo.c password.c php_fopen_wrapper.c + poll.c proc_open.c quot_print.c scanf.c diff --git a/ext/standard/config.w32 b/ext/standard/config.w32 index c7c14b8705ca2..b842a8a520d60 100644 --- a/ext/standard/config.w32 +++ b/ext/standard/config.w32 @@ -35,7 +35,8 @@ EXTENSION("standard", "array.c base64.c basic_functions.c browscap.c \ url_scanner_ex.c ftp_fopen_wrapper.c http_fopen_wrapper.c \ php_fopen_wrapper.c credits.c css.c var_unserializer.c ftok.c sha1.c \ user_filters.c uuencode.c filters.c proc_open.c password.c \ - streamsfuncs.c http.c flock_compat.c hrtime.c", false /* never shared */, + poll.c streamsfuncs.c http.c flock_compat.c hrtime.c", + false /* never shared */, '/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1'); ADD_SOURCES("ext/standard/libavifinfo", "avifinfo.c", "standard"); PHP_STANDARD = "yes"; diff --git a/ext/standard/poll.c b/ext/standard/poll.c new file mode 100644 index 0000000000000..a71c878d71c72 --- /dev/null +++ b/ext/standard/poll.c @@ -0,0 +1,656 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Jakub Zelenka | + +----------------------------------------------------------------------+ +*/ + +#include "php.h" +#include "php_network.h" +#include "php_poll.h" +#include "poll_arginfo.h" +#include "zend_exceptions.h" + +/* Class entries */ +static zend_class_entry *php_poll_context_class_entry; +static zend_class_entry *php_poll_watcher_class_entry; +static zend_class_entry *php_poll_handle_class_entry; +static zend_class_entry *php_stream_poll_handle_class_entry; +static zend_class_entry *php_poll_exception_class_entry; + +/* Object handlers */ +static zend_object_handlers php_poll_context_object_handlers; +static zend_object_handlers php_poll_watcher_object_handlers; +static zend_object_handlers php_poll_handle_object_handlers; + +/* Watcher object structure */ +typedef struct { + php_poll_handle_object *handle; + uint32_t watched_events; + uint32_t triggered_events; + zval data; + bool active; + php_poll_ctx *poll_ctx; /* Back reference to poll context */ + zend_object std; +} php_poll_watcher_object; + +/* Context object structure */ +typedef struct { + php_poll_ctx *ctx; + HashTable *watchers; /* Maps handle pointer -> watcher object */ + zend_object std; +} php_poll_context_object; + +/* Stream poll handle specific data */ +typedef struct { + php_stream *stream; +} php_stream_poll_handle_data; + +/* Accessor macros */ +#define PHP_POLL_CONTEXT_OBJ_FROM_ZOBJ(_obj) \ + ((php_poll_context_object *) ((char *) (_obj) - XtOffsetOf(php_poll_context_object, std))) + +#define PHP_POLL_WATCHER_OBJ_FROM_ZOBJ(_obj) \ + ((php_poll_watcher_object *) ((char *) (_obj) - XtOffsetOf(php_poll_watcher_object, std))) + +#define PHP_POLL_WATCHER_OBJ_FROM_ZV(_zv) PHP_POLL_WATCHER_OBJ_FROM_ZOBJ(Z_OBJ_P(_zv)) +#define PHP_POLL_CONTEXT_OBJ_FROM_ZV(_zv) PHP_POLL_CONTEXT_OBJ_FROM_ZOBJ(Z_OBJ_P(_zv)) + +/* === Stream Poll Handle Implementation === */ + +static php_socket_t php_stream_poll_handle_get_fd(php_poll_handle_object *handle) +{ + php_stream_poll_handle_data *data = (php_stream_poll_handle_data *) handle->handle_data; + php_socket_t fd; + + if (!data || !data->stream) { + return SOCK_ERR; + } + + if (php_stream_cast(data->stream, PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL, + (void *) &fd, + 1) != SUCCESS + || fd == -1) { + return SOCK_ERR; + } + + return fd; +} + +static int php_stream_poll_handle_is_valid(php_poll_handle_object *handle) +{ + php_stream_poll_handle_data *data = (php_stream_poll_handle_data *) handle->handle_data; + return data && data->stream && !php_stream_eof(data->stream); +} + +static void php_stream_poll_handle_cleanup(php_poll_handle_object *handle) +{ + php_stream_poll_handle_data *data = (php_stream_poll_handle_data *) handle->handle_data; + if (data) { + /* Don't close the stream - user still owns it */ + efree(data); + handle->handle_data = NULL; + } +} + +static php_poll_handle_ops php_stream_poll_handle_ops = { + .get_fd = php_stream_poll_handle_get_fd, + .is_valid = php_stream_poll_handle_is_valid, + .cleanup = php_stream_poll_handle_cleanup +}; + +/* === Object Creation Functions === */ + +static zend_object *php_poll_handle_create_object(zend_class_entry *ce) +{ + php_poll_handle_object *intern = php_poll_handle_object_create( + sizeof(php_poll_handle_object), ce, &php_poll_handle_default_ops); + return &intern->std; +} + +static zend_object *php_stream_poll_handle_create_object(zend_class_entry *ce) +{ + php_poll_handle_object *intern = php_poll_handle_object_create( + sizeof(php_poll_handle_object), ce, &php_stream_poll_handle_ops); + return &intern->std; +} + +static zend_object *php_poll_watcher_create_object(zend_class_entry *ce) +{ + php_poll_watcher_object *intern = zend_object_alloc(sizeof(php_poll_watcher_object), ce); + + zend_object_std_init(&intern->std, ce); + object_properties_init(&intern->std, ce); + + intern->handle = NULL; + intern->watched_events = 0; + intern->triggered_events = 0; + intern->active = false; + intern->poll_ctx = NULL; + ZVAL_NULL(&intern->data); + + return &intern->std; +} + +static zend_object *php_poll_context_create_object(zend_class_entry *ce) +{ + php_poll_context_object *intern = zend_object_alloc(sizeof(php_poll_context_object), ce); + + zend_object_std_init(&intern->std, ce); + object_properties_init(&intern->std, ce); + + intern->ctx = NULL; + intern->watchers = NULL; + + return &intern->std; +} + +/* === Object Destruction Functions === */ + +static void php_poll_watcher_free_object(zend_object *obj) +{ + php_poll_watcher_object *intern = PHP_POLL_WATCHER_OBJ_FROM_ZOBJ(obj); + + zval_ptr_dtor(&intern->data); + + if (intern->handle) { + OBJ_RELEASE(&intern->handle->std); + } + + zend_object_std_dtor(&intern->std); +} + +static void php_poll_context_free_object(zend_object *obj) +{ + php_poll_context_object *intern = PHP_POLL_CONTEXT_OBJ_FROM_ZOBJ(obj); + + if (intern->ctx) { + php_poll_destroy(intern->ctx); + } + + if (intern->watchers) { + zend_hash_destroy(intern->watchers); + efree(intern->watchers); + } + + zend_object_std_dtor(&intern->std); +} + +/* === Utility functions === */ + +static zend_always_inline zend_ulong php_poll_compute_ptr_key(void *ptr) +{ + zend_ulong key = (zend_ulong) (uintptr_t) ptr; + return (key >> 3) | (key << ((sizeof(key) * 8) - 3)); +} + +static zend_result php_poll_watcher_modify_events(php_poll_watcher_object *watcher, uint32_t events) +{ + if (!watcher->active || !watcher->poll_ctx) { + zend_throw_exception(php_poll_exception_class_entry, "Cannot modify inactive watcher", 0); + return FAILURE; + } + + php_socket_t fd = php_poll_handle_get_fd(watcher->handle); + if (fd == SOCK_ERR) { + zend_throw_exception(php_poll_exception_class_entry, "Invalid handle for polling", 0); + return FAILURE; + } + + /* Modify in poll context */ + if (php_poll_modify(watcher->poll_ctx, (int) fd, events, watcher) != SUCCESS) { + zend_throw_exception( + php_poll_exception_class_entry, "Failed to modify watcher in polling system", 0); + return FAILURE; + } + + /* Update watcher state */ + watcher->watched_events = events; + + return SUCCESS; +} + +static zend_result php_poll_watcher_modify_data(php_poll_watcher_object *watcher, zval *data) +{ + if (!watcher->active) { + zend_throw_exception(php_poll_exception_class_entry, "Cannot modify inactive watcher", 0); + return FAILURE; + } + + /* Update user data */ + zval_ptr_dtor(&watcher->data); + ZVAL_COPY(&watcher->data, data); + + return SUCCESS; +} + +/* === PHP Method Implementations === */ + +PHP_METHOD(PollHandle, getFileDescriptor) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + php_poll_handle_object *intern = PHP_POLL_HANDLE_OBJ_FROM_ZV(getThis()); + php_socket_t fd = php_poll_handle_get_fd(intern); + + if (fd == SOCK_ERR) { + RETURN_LONG(0); + } + + RETURN_LONG((zend_long) fd); +} + +PHP_METHOD(StreamPollHandle, __construct) +{ + php_stream *stream; + + ZEND_PARSE_PARAMETERS_START(1, 1) + PHP_Z_PARAM_STREAM(stream) + ZEND_PARSE_PARAMETERS_END(); + + php_poll_handle_object *intern = PHP_POLL_HANDLE_OBJ_FROM_ZV(getThis()); + + /* Set up stream-specific data */ + php_stream_poll_handle_data *data = emalloc(sizeof(php_stream_poll_handle_data)); + data->stream = stream; + intern->handle_data = data; + + /* Add reference to stream */ + GC_ADDREF(stream->res); +} + +PHP_METHOD(StreamPollHandle, getStream) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + php_poll_handle_object *intern = PHP_POLL_HANDLE_OBJ_FROM_ZV(getThis()); + php_stream_poll_handle_data *data = (php_stream_poll_handle_data *) intern->handle_data; + + if (!data || !data->stream) { + RETURN_NULL(); + } + + GC_ADDREF(data->stream->res); + php_stream_to_zval(data->stream, return_value); +} + +PHP_METHOD(StreamPollHandle, isValid) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + php_poll_handle_object *intern = PHP_POLL_HANDLE_OBJ_FROM_ZV(getThis()); + RETURN_BOOL(intern->ops->is_valid(intern)); +} + +PHP_METHOD(PollWatcher, __construct) +{ + zval *handle_obj; + zend_long events; + zval *data = NULL; + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_OBJECT_OF_CLASS(handle_obj, php_poll_handle_class_entry) + Z_PARAM_LONG(events) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(data) + ZEND_PARSE_PARAMETERS_END(); + + php_poll_watcher_object *intern = PHP_POLL_WATCHER_OBJ_FROM_ZV(getThis()); + intern->handle = PHP_POLL_HANDLE_OBJ_FROM_ZV(handle_obj); + intern->watched_events = (uint32_t) events; + intern->triggered_events = 0; + intern->active = false; + + GC_ADDREF(&intern->handle->std); + + if (data) { + ZVAL_COPY(&intern->data, data); + } else { + ZVAL_NULL(&intern->data); + } +} + +PHP_METHOD(PollWatcher, getHandle) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + php_poll_watcher_object *intern = PHP_POLL_WATCHER_OBJ_FROM_ZV(getThis()); + if (!intern->handle) { + RETURN_NULL(); + } + + RETURN_OBJ_COPY(&intern->handle->std); +} + +PHP_METHOD(PollWatcher, getWatchedEvents) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + php_poll_watcher_object *intern = PHP_POLL_WATCHER_OBJ_FROM_ZV(getThis()); + RETURN_LONG((zend_long) intern->watched_events); +} + +PHP_METHOD(PollWatcher, getTriggeredEvents) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + php_poll_watcher_object *intern = PHP_POLL_WATCHER_OBJ_FROM_ZV(getThis()); + RETURN_LONG((zend_long) intern->triggered_events); +} + +PHP_METHOD(PollWatcher, getData) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + php_poll_watcher_object *intern = PHP_POLL_WATCHER_OBJ_FROM_ZV(getThis()); + ZVAL_COPY(return_value, &intern->data); +} + +PHP_METHOD(PollWatcher, hasTriggered) +{ + zend_long events; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(events) + ZEND_PARSE_PARAMETERS_END(); + + php_poll_watcher_object *intern = PHP_POLL_WATCHER_OBJ_FROM_ZV(getThis()); + RETURN_BOOL((intern->triggered_events & (uint32_t) events) != 0); +} + +PHP_METHOD(PollWatcher, isActive) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + php_poll_watcher_object *intern = PHP_POLL_WATCHER_OBJ_FROM_ZV(getThis()); + RETURN_BOOL(intern->active); +} + +PHP_METHOD(PollWatcher, modify) +{ + zend_long events; + zval *data = NULL; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_LONG(events) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(data) + ZEND_PARSE_PARAMETERS_END(); + + php_poll_watcher_object *intern = PHP_POLL_WATCHER_OBJ_FROM_ZV(getThis()); + + /* Modify events first */ + if (php_poll_watcher_modify_events(intern, (uint32_t) events) != SUCCESS) { + RETURN_THROWS(); + } + + /* Then modify data if provided */ + if (data) { + if (php_poll_watcher_modify_data(intern, data) != SUCCESS) { + RETURN_THROWS(); + } + } +} + +PHP_METHOD(PollWatcher, modifyEvents) +{ + zend_long events; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(events) + ZEND_PARSE_PARAMETERS_END(); + + php_poll_watcher_object *intern = PHP_POLL_WATCHER_OBJ_FROM_ZV(getThis()); + + if (php_poll_watcher_modify_events(intern, (uint32_t) events) != SUCCESS) { + RETURN_THROWS(); + } +} + +PHP_METHOD(PollWatcher, modifyData) +{ + zval *data; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(data) + ZEND_PARSE_PARAMETERS_END(); + + php_poll_watcher_object *intern = PHP_POLL_WATCHER_OBJ_FROM_ZV(getThis()); + + if (php_poll_watcher_modify_data(intern, data) != SUCCESS) { + RETURN_THROWS(); + } +} + +PHP_METHOD(PollWatcher, remove) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + php_poll_watcher_object *intern = PHP_POLL_WATCHER_OBJ_FROM_ZV(getThis()); + + if (!intern->active || !intern->poll_ctx) { + zend_throw_exception(php_poll_exception_class_entry, "Cannot remove inactive watcher", 0); + RETURN_THROWS(); + } + + php_socket_t fd = php_poll_handle_get_fd(intern->handle); + if (fd != SOCK_ERR) { + php_poll_remove(intern->poll_ctx, (int) fd); + } + + intern->active = false; + intern->poll_ctx = NULL; +} + +PHP_METHOD(PollContext, __construct) +{ + zend_long backend_long = PHP_POLL_BACKEND_AUTO; + zend_string *backend_str = NULL; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_STR_OR_LONG(backend_str, backend_long) + ZEND_PARSE_PARAMETERS_END(); + + php_poll_context_object *intern = PHP_POLL_CONTEXT_OBJ_FROM_ZV(getThis()); + + if (backend_str == NULL) { + intern->ctx = php_poll_create((php_poll_backend_type) backend_long, 0); + } else { + intern->ctx = php_poll_create_by_name(ZSTR_VAL(backend_str), 0); + } + + if (!intern->ctx) { + zend_throw_exception(php_poll_exception_class_entry, "Failed to create polling context", 0); + RETURN_THROWS(); + } + + if (php_poll_init(intern->ctx) != SUCCESS) { + php_poll_destroy(intern->ctx); + intern->ctx = NULL; + zend_throw_exception( + php_poll_exception_class_entry, "Failed to initialize polling context", 0); + RETURN_THROWS(); + } + + intern->watchers = emalloc(sizeof(HashTable)); + zend_hash_init(intern->watchers, 8, NULL, ZVAL_PTR_DTOR, 0); +} + +PHP_METHOD(PollContext, add) +{ + zval *handle_obj; + zend_long events; + zval *data = NULL; + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_OBJECT_OF_CLASS(handle_obj, php_poll_handle_class_entry) + Z_PARAM_LONG(events) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(data) + ZEND_PARSE_PARAMETERS_END(); + + php_poll_context_object *intern = PHP_POLL_CONTEXT_OBJ_FROM_ZV(getThis()); + php_poll_handle_object *handle = PHP_POLL_HANDLE_OBJ_FROM_ZV(handle_obj); + + /* Get file descriptor */ + php_socket_t fd = php_poll_handle_get_fd(handle); + if (fd == SOCK_ERR) { + zend_throw_exception(php_poll_exception_class_entry, "Invalid handle for polling", 0); + RETURN_THROWS(); + } + + /* Create watcher object */ + object_init_ex(return_value, php_poll_watcher_class_entry); + php_poll_watcher_object *watcher = PHP_POLL_WATCHER_OBJ_FROM_ZV(return_value); + + watcher->handle = handle; + watcher->watched_events = (uint32_t) events; + watcher->triggered_events = 0; + watcher->active = true; + watcher->poll_ctx = intern->ctx; + + GC_ADDREF(&handle->std); + + if (data) { + ZVAL_COPY(&watcher->data, data); + } else { + ZVAL_NULL(&watcher->data); + } + + /* Add to poll context */ + if (php_poll_add(intern->ctx, (int) fd, (uint32_t) events, watcher) != SUCCESS) { + if (php_poll_get_error(intern->ctx) == PHP_POLL_ERR_EXISTS) { + zend_throw_exception( + php_poll_exception_class_entry, "Handle already added", 0); + } else { + zend_throw_exception( + php_poll_exception_class_entry, "Failed to add handle", 0); + } + RETURN_THROWS(); + } + + /* Now mark as active */ + watcher->active = true; + watcher->poll_ctx = intern->ctx; + + /* Store in our watchers map using shifted pointer as key */ + zval watcher_zv; + ZVAL_OBJ(&watcher_zv, &watcher->std); + GC_ADDREF(&watcher->std); + + zend_ulong hash_key = php_poll_compute_ptr_key(handle); + zend_hash_index_add(intern->watchers, hash_key, &watcher_zv); +} + +PHP_METHOD(PollContext, wait) +{ + zend_long timeout = -1; + zend_long max_events = -1; + + ZEND_PARSE_PARAMETERS_START(0, 2) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(timeout) + Z_PARAM_LONG(max_events) + ZEND_PARSE_PARAMETERS_END(); + + php_poll_context_object *intern = PHP_POLL_CONTEXT_OBJ_FROM_ZV(getThis()); + + if (max_events <= 0) { + max_events = php_poll_get_suitable_max_events(intern->ctx); + if (max_events <= 0) { + max_events = 64; + } + } + + php_poll_event *events = emalloc(sizeof(php_poll_event) * max_events); + int num_events = php_poll_wait(intern->ctx, events, max_events, (int) timeout); + + if (num_events < 0) { + efree(events); + zend_throw_exception(php_poll_exception_class_entry, "Poll wait failed", 0); + RETURN_THROWS(); + } + + array_init(return_value); + + for (int i = 0; i < num_events; i++) { + php_poll_watcher_object *watcher = (php_poll_watcher_object *) events[i].data; + if (watcher) { + watcher->triggered_events = events[i].revents; + + zval watcher_zv; + ZVAL_OBJ(&watcher_zv, &watcher->std); + GC_ADDREF(&watcher->std); + + add_next_index_zval(return_value, &watcher_zv); + } + } + + efree(events); +} + +PHP_METHOD(PollContext, getBackendName) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + php_poll_context_object *intern = PHP_POLL_CONTEXT_OBJ_FROM_ZV(getThis()); + const char *backend_name = php_poll_backend_name(intern->ctx); + RETURN_STRING(backend_name); +} + +/* Initialize the stream poll classes - add to PHP_MINIT_FUNCTION */ +PHP_MINIT_FUNCTION(poll) +{ + /* Register symbols */ + register_poll_symbols(module_number); + + /* Register base PollHandle class */ + php_poll_handle_class_entry = register_class_PollHandle(); + php_poll_handle_class_entry->create_object = php_poll_handle_create_object; + + memcpy(&php_poll_handle_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); + php_poll_handle_object_handlers.offset = XtOffsetOf(php_poll_handle_object, std); + php_poll_handle_object_handlers.free_obj = php_poll_handle_object_free; + php_poll_handle_class_entry->default_object_handlers = &php_poll_handle_object_handlers; + + /* Register StreamPollHandle class */ + php_stream_poll_handle_class_entry + = register_class_StreamPollHandle(php_poll_handle_class_entry); + php_stream_poll_handle_class_entry->create_object = php_stream_poll_handle_create_object; + + /* Register PollWatcher class */ + php_poll_watcher_class_entry = register_class_PollWatcher(); + php_poll_watcher_class_entry->create_object = php_poll_watcher_create_object; + + memcpy(&php_poll_watcher_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); + php_poll_watcher_object_handlers.offset = XtOffsetOf(php_poll_watcher_object, std); + php_poll_watcher_object_handlers.free_obj = php_poll_watcher_free_object; + php_poll_watcher_class_entry->default_object_handlers = &php_poll_watcher_object_handlers; + + /* Register PollContext class */ + php_poll_context_class_entry = register_class_PollContext(); + php_poll_context_class_entry->create_object = php_poll_context_create_object; + + memcpy(&php_poll_context_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); + php_poll_context_object_handlers.offset = XtOffsetOf(php_poll_context_object, std); + php_poll_context_object_handlers.free_obj = php_poll_context_free_object; + php_poll_context_class_entry->default_object_handlers = &php_poll_context_object_handlers; + + /* Register exception class */ + php_poll_exception_class_entry = register_class_PollException(zend_ce_exception); + + /* Initialize polling backends */ + php_poll_register_backends(); + + return SUCCESS; +} diff --git a/ext/standard/poll.stub.php b/ext/standard/poll.stub.php new file mode 100644 index 0000000000000..a0a06734c9b38 --- /dev/null +++ b/ext/standard/poll.stub.php @@ -0,0 +1,127 @@ +add(new StreamPollHandle($stream), $events, $data); +} + +function pt_skip_for_backend($backend, $msg): void { + $backends_to_skip = is_array($backend) ? $backend : array($backend); + $current_backend = pt_new_stream_poll()->getBackendName(); + if (in_array($current_backend, $backends_to_skip)) { + die("skip backend $current_backend $msg\n"); + } +} + +function pt_new_socket_pair(): array { + $domain = (strtoupper(substr(PHP_OS, 0, 3) == 'WIN') ? STREAM_PF_INET : STREAM_PF_UNIX); + $sockets = stream_socket_pair($domain, STREAM_SOCK_STREAM, 0); + if ($sockets === false) { + die("Cannot create socket pair\n"); + } + return $sockets; +} + +function pt_new_tcp_socket_pair(): array { + $server = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr); + if (!$server) { + die("Cannot create TCP server: $errstr\n"); + } + $address = stream_socket_get_name($server, false); + + $client = stream_socket_client("tcp://$address", $errno, $errstr); + if (!$client) { + fclose($server); + die("Cannot connect to TCP server: $errstr\n"); + } + + $server_conn = stream_socket_accept($server); + if (!$server_conn) { + fclose($server); + fclose($client); + die("Cannot accept connection\n"); + } + + // Close the listening socket (no longer needed) + fclose($server); + + return [$client, $server_conn]; +} + +function pt_new_tcp_socket_connections(int $num_conns): array { + $server = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr); + if (!$server) { + die("Cannot create TCP server: $errstr\n"); + } + $address = stream_socket_get_name($server, false); + + $clients = []; + $server_conns = []; + for ($i = 0; $i < $num_conns; ++$i) { + $clients[$i] = stream_socket_client("tcp://$address", $errno, $errstr); + if (!$clients[$i]) { + fclose($server); + die("Cannot connect to TCP server: $errstr\n"); + } + + $server_conns[$i] = stream_socket_accept($server); + if (!$server_conns[$i]) { + fclose($server); + die("Cannot accept connection\n"); + } + } + + + // Close the listening socket (no longer needed) + fclose($server); + + return [$clients, $server_conns]; +} + +function pt_write_sleep($stream, $data, $delay = 10000): int|false { + $result = fwrite($stream, $data, $delay); + usleep($delay); + return $result; +} + +function pt_print_events($watchers, $read_data = false): void { + if (!is_array($watchers)) { + die("Events must be an array\n"); + } + echo "Events count: " . count($watchers) . "\n"; + foreach ($watchers as $i => $watcher) { + if (!$watcher instanceof PollWatcher) { + die('Invalid event type'); + } + echo "Event[$i]: " . $watcher->getTriggeredEvents() . ", user data: " . $watcher->getData(); + if ($read_data && $watcher->hasTriggered(POLL_EVENT_READ)) { + $data = fread($watcher->getHandle()->getStream(), 1024); + echo ", read data: '$data'"; + } + echo "\n"; + } +} + +function pt_expect_events($watchers, $expected, ?PollContext $poll_ctx = null): void { + if (!is_array($watchers)) { + die("Events must be an array\n"); + } + + if (!is_array($expected)) { + die("Expected events must be an array\n"); + } + + $event_count = count($watchers); + $expected_count = count($expected); + + // Get current backend name for backend-specific expectations + $backend_name = $poll_ctx ? $poll_ctx->getBackendName() : 'unknown'; + + if ($event_count !== $expected_count) { + echo "Event count mismatch: got $event_count, expected $expected_count\n"; + pt_print_mismatched_events($watchers, $expected, [], $backend_name); + return; + } + + // Convert events to comparable format for matching + $actual_events = []; + foreach ($watchers as $watcher) { + if (!$watcher instanceof PollWatcher) { + die('Invalid event type'); + } + + $event_data = [ + 'events' => $watcher->getTriggeredEvents(), + 'data' => $watcher->getData(), + ]; + + $actual_events[] = $event_data; + } + + // Resolve backend-specific expectations + $resolved_expected = []; + foreach ($expected as $exp_event) { + $resolved_event = $exp_event; + + if (isset($exp_event['events']) && is_array($exp_event['events'])) { + $resolved_event['events'] = pt_resolve_backend_specific_value($exp_event['events'], $backend_name); + } + + $resolved_expected[] = $resolved_event; + } + + // Try to match each expected event with an actual event + $matched = []; + $unmatched_expected = []; + + foreach ($resolved_expected as $exp_idx => $exp_event) { + $found_match = false; + + foreach ($actual_events as $act_idx => $act_event) { + if (isset($matched[$act_idx])) { + continue; // Already matched + } + + // Check if events and data match + if ($act_event['events'] === $exp_event['events'] && + $act_event['data'] === $exp_event['data']) { + + // If read data is expected, check it + if (isset($exp_event['read'])) { + $read_data = fread($watchers[$act_idx]->getHandle()->getStream(), 1024); + if ($read_data !== $exp_event['read']) { + continue; // Read data doesn't match + } + } + + $matched[$act_idx] = $exp_idx; + $found_match = true; + break; + } + } + + if (!$found_match) { + $unmatched_expected[] = $exp_event; + } + } + + // Check if all events matched + if (count($matched) === $event_count && empty($unmatched_expected)) { + echo "Events matched - count: $event_count\n"; + } else { + echo "Events did not match:\n"; + pt_print_mismatched_events($watchers, $expected, $matched, $backend_name); + } +} + +function pt_resolve_backend_specific_value($backend_map, $current_backend) { + // Direct backend match + if (isset($backend_map[$current_backend])) { + return $backend_map[$current_backend]; + } + + // Check for multi-backend keys (e.g., "poll|epoll") + foreach ($backend_map as $key => $value) { + if (strpos($key, '|') !== false) { + $backends = array_map('trim', explode('|', $key)); + if (in_array($current_backend, $backends)) { + return $value; + } + } + } + + // Fall back to default + if (isset($backend_map['default'])) { + return $backend_map['default']; + } + + // If no match found, this is an error + die("No backend-specific value found for '$current_backend' and no default specified\n"); +} + +function pt_event_flags_to_string($flags): string { + $names = []; + if ($flags & POLL_EVENT_READ) $names[] = 'READ'; + if ($flags & POLL_EVENT_WRITE) $names[] = 'WRITE'; + if ($flags & POLL_EVENT_ERROR) $names[] = 'ERROR'; + if ($flags & POLL_EVENT_HUP) $names[] = 'HUP'; + + return empty($names) ? 'NONE:' . $flags : implode('|', $names); +} + +function pt_print_mismatched_events($actual_watchers, $expected_watchers, $matched = [], $backend_name = null): void { + echo "Actual events:\n"; + foreach ($actual_watchers as $i => $watcher) { + $match_status = isset($matched[$i]) ? " [MATCHED]" : " [UNMATCHED]"; + $watcher_names = pt_event_flags_to_string($watcher->getTriggeredEvents()); + echo " Event[$i]: $watcher_names, user data: " . $watcher->getData() . $match_status . "\n"; + } + + echo "Expected events:\n"; + foreach ($expected_watchers as $i => $exp_event) { + $was_matched = in_array($i, $matched); + $match_status = $was_matched ? " [MATCHED]" : " [UNMATCHED]"; + + $watchers_value = $exp_event['events']; + if (is_array($watchers_value) && $backend_name !== null) { + $watchers_value = pt_resolve_backend_specific_value($watchers_value, $backend_name); + } + + $watcher_names = pt_event_flags_to_string($watchers_value); + echo " Event[$i]: $watcher_names, user data: " . $exp_event['data']; + if (isset($exp_event['read'])) { + echo ", read data: '" . $exp_event['read'] . "'"; + } + echo $match_status . "\n"; + } +} diff --git a/ext/standard/tests/poll/poll_stream_add_error_duplicate.phpt b/ext/standard/tests/poll/poll_stream_add_error_duplicate.phpt new file mode 100644 index 0000000000000..bae748a5d71c6 --- /dev/null +++ b/ext/standard/tests/poll/poll_stream_add_error_duplicate.phpt @@ -0,0 +1,19 @@ +--TEST-- +Poll stream - add duplicite error +--FILE-- +getMessage() . "\n"; +} +?> +--EXPECT-- +ERROR: Handle already added diff --git a/ext/standard/tests/poll/poll_stream_backend_name_unix.phpt b/ext/standard/tests/poll/poll_stream_backend_name_unix.phpt new file mode 100644 index 0000000000000..3a4e0b65462c7 --- /dev/null +++ b/ext/standard/tests/poll/poll_stream_backend_name_unix.phpt @@ -0,0 +1,22 @@ +--TEST-- +Poll stream - backend name +--SKIPIF-- + +--FILE-- +getBackendName()); +// test with string +$poll_ctx = new PollContext('poll'); +var_dump($poll_ctx->getBackendName()); + +?> +--EXPECT-- +string(4) "poll" +string(4) "poll" diff --git a/ext/standard/tests/poll/poll_stream_backend_name_windows.phpt b/ext/standard/tests/poll/poll_stream_backend_name_windows.phpt new file mode 100644 index 0000000000000..d7e5f00f97c5a --- /dev/null +++ b/ext/standard/tests/poll/poll_stream_backend_name_windows.phpt @@ -0,0 +1,22 @@ +--TEST-- +Poll stream - backend name on Windows +--SKIPIF-- + +--FILE-- +getBackendName()); +// test with string +$poll_ctx = new PollContext('wsapoll'); +var_dump($poll_ctx->getBackendName()); + +?> +--EXPECT-- +string(7) "wsapoll" +string(7) "wsapoll" diff --git a/ext/standard/tests/poll/poll_stream_sock_add_only.phpt b/ext/standard/tests/poll/poll_stream_sock_add_only.phpt new file mode 100644 index 0000000000000..c6e4db3f6dff7 --- /dev/null +++ b/ext/standard/tests/poll/poll_stream_sock_add_only.phpt @@ -0,0 +1,17 @@ +--TEST-- +Poll stream - only add +--FILE-- + +--EXPECT-- +object(PollContext)#1 (0) { +} diff --git a/ext/standard/tests/poll/poll_stream_sock_modify_write.phpt b/ext/standard/tests/poll/poll_stream_sock_modify_write.phpt new file mode 100644 index 0000000000000..490192255419d --- /dev/null +++ b/ext/standard/tests/poll/poll_stream_sock_modify_write.phpt @@ -0,0 +1,18 @@ +--TEST-- +Poll stream - socket modify write +--FILE-- +modify(POLL_EVENT_WRITE, "modified_data"); + +pt_expect_events($poll_ctx->wait(0), [ + ['events' => POLL_EVENT_WRITE, 'data' => 'modified_data'] +]); +?> +--EXPECT-- +Events matched - count: 1 diff --git a/ext/standard/tests/poll/poll_stream_sock_read.phpt b/ext/standard/tests/poll/poll_stream_sock_read.phpt new file mode 100644 index 0000000000000..d07c274e1e6f3 --- /dev/null +++ b/ext/standard/tests/poll/poll_stream_sock_read.phpt @@ -0,0 +1,19 @@ +--TEST-- +Poll stream - socket read +--FILE-- +wait(100), [ + ['events' => POLL_EVENT_READ, 'data' => 'socket_data', 'read' => 'test data'] +]); + +?> +--EXPECT-- +Events matched - count: 1 diff --git a/ext/standard/tests/poll/poll_stream_sock_remove_write.phpt b/ext/standard/tests/poll/poll_stream_sock_remove_write.phpt new file mode 100644 index 0000000000000..d25d4d2c23dbd --- /dev/null +++ b/ext/standard/tests/poll/poll_stream_sock_remove_write.phpt @@ -0,0 +1,38 @@ +--TEST-- +Poll stream - socket remove write +--FILE-- +wait(0), [ + ['events' => POLL_EVENT_WRITE, 'data' => 'socket_data_1'], + ['events' => POLL_EVENT_WRITE, 'data' => 'socket_data_2'] +]); + +$watcher1w->remove(); + +pt_expect_events($poll_ctx->wait(0), [ + ['events' => POLL_EVENT_WRITE, 'data' => 'socket_data_2'] +]); + +// check that both streams are still usable +var_dump(fwrite($socket1w, "test 1")); +var_dump(fwrite($socket2w, "test 2")); +var_dump(fread($socket1r, 100)); +var_dump(fread($socket2r, 100)); + +?> +--EXPECT-- +Events matched - count: 2 +Events matched - count: 1 +int(6) +int(6) +string(6) "test 1" +string(6) "test 2" diff --git a/ext/standard/tests/poll/poll_stream_sock_rw_close.phpt b/ext/standard/tests/poll/poll_stream_sock_rw_close.phpt new file mode 100644 index 0000000000000..d2245b6b3cfcd --- /dev/null +++ b/ext/standard/tests/poll/poll_stream_sock_rw_close.phpt @@ -0,0 +1,29 @@ +--TEST-- +Poll stream - socket write / read close +--FILE-- +wait(100), [ + [ + 'events' => POLL_EVENT_WRITE|POLL_EVENT_ERROR|POLL_EVENT_HUP, + 'data' => 'socket2_data' + ] +], $poll_ctx); + +fclose($socket1w); +pt_expect_events($poll_ctx->wait(100), []); + +?> +--EXPECT-- +Events matched - count: 1 +Events matched - count: 0 diff --git a/ext/standard/tests/poll/poll_stream_sock_rw_multi_edge.phpt b/ext/standard/tests/poll/poll_stream_sock_rw_multi_edge.phpt new file mode 100644 index 0000000000000..56af06d28b6c9 --- /dev/null +++ b/ext/standard/tests/poll/poll_stream_sock_rw_multi_edge.phpt @@ -0,0 +1,62 @@ +--TEST-- +Poll stream - socket write / read multiple times with edge triggering +--SKIPIF-- + +--FILE-- +wait(0), [ + ['events' => POLL_EVENT_WRITE, 'data' => 'socket2_data'] +]); + +pt_expect_events($poll_ctx->wait(0), []); + +fwrite($socket1w, "test data"); +pt_expect_events($poll_ctx->wait(100), [ + ['events' => POLL_EVENT_READ, 'data' => 'socket1_data', 'read' => 'test data'] +]); + +fwrite($socket1w, "more data"); +pt_expect_events($poll_ctx->wait(100), [ + ['events' => POLL_EVENT_WRITE, 'data' => 'socket2_data'], + ['events' => POLL_EVENT_READ, 'data' => 'socket1_data'] +]); + +pt_expect_events($poll_ctx->wait(100), []); + +fwrite($socket1w, " and even more data"); +pt_expect_events($poll_ctx->wait(100), [ + ['events' => POLL_EVENT_READ, 'data' => 'socket1_data', 'read' => 'more data and even more data'] +]); + +fclose($socket1r); +pt_expect_events($poll_ctx->wait(100), [ + [ + 'events' => ['default' => POLL_EVENT_WRITE|POLL_EVENT_HUP, 'poll' => POLL_EVENT_HUP], + 'data' => 'socket2_data' + ] +], $poll_ctx); + +fclose($socket1w); +pt_expect_events($poll_ctx->wait(100), []); + +?> +--EXPECT-- +Events matched - count: 1 +Events matched - count: 0 +Events matched - count: 1 +Events matched - count: 2 +Events matched - count: 0 +Events matched - count: 1 +Events matched - count: 1 +Events matched - count: 0 diff --git a/ext/standard/tests/poll/poll_stream_sock_rw_multi_level.phpt b/ext/standard/tests/poll/poll_stream_sock_rw_multi_level.phpt new file mode 100644 index 0000000000000..6b7e563d53d6e --- /dev/null +++ b/ext/standard/tests/poll/poll_stream_sock_rw_multi_level.phpt @@ -0,0 +1,64 @@ +--TEST-- +Poll stream - socket write / read multiple times with level triggering +--FILE-- +wait(0), [ + ['events' => POLL_EVENT_WRITE, 'data' => 'socket2_data'] +]); + +pt_expect_events($poll_ctx->wait(0), [ + ['events' => POLL_EVENT_WRITE, 'data' => 'socket2_data'] +]); + +fwrite($socket1w, "test data"); +pt_expect_events($poll_ctx->wait(100), [ + ['events' => POLL_EVENT_WRITE, 'data' => 'socket2_data'], + ['events' => POLL_EVENT_READ, 'data' => 'socket1_data', 'read' => 'test data'] +]); + +fwrite($socket1w, "more data"); +pt_expect_events($poll_ctx->wait(100), [ + ['events' => POLL_EVENT_WRITE, 'data' => 'socket2_data'], + ['events' => POLL_EVENT_READ, 'data' => 'socket1_data'] +]); + +pt_expect_events($poll_ctx->wait(100), [ + ['events' => POLL_EVENT_WRITE, 'data' => 'socket2_data'], + ['events' => POLL_EVENT_READ, 'data' => 'socket1_data'] +]); + +fwrite($socket1w, " and even more data"); +pt_expect_events($poll_ctx->wait(100), [ + ['events' => POLL_EVENT_WRITE, 'data' => 'socket2_data'], + ['events' => POLL_EVENT_READ, 'data' => 'socket1_data', 'read' => 'more data and even more data'] +]); + +fclose($socket1r); +pt_expect_events($poll_ctx->wait(100), [ + [ + 'events' => POLL_EVENT_WRITE|POLL_EVENT_HUP, + 'data' => 'socket2_data' + ] +], $poll_ctx); + +fclose($socket1w); +pt_expect_events($poll_ctx->wait(100), []); + +?> +--EXPECT-- +Events matched - count: 1 +Events matched - count: 1 +Events matched - count: 2 +Events matched - count: 2 +Events matched - count: 2 +Events matched - count: 2 +Events matched - count: 1 +Events matched - count: 0 diff --git a/ext/standard/tests/poll/poll_stream_sock_rw_single_edge.phpt b/ext/standard/tests/poll/poll_stream_sock_rw_single_edge.phpt new file mode 100644 index 0000000000000..0235af7cd472e --- /dev/null +++ b/ext/standard/tests/poll/poll_stream_sock_rw_single_edge.phpt @@ -0,0 +1,29 @@ +--TEST-- +Poll stream - socket write / read few time only +--SKIPIF-- + +--FILE-- +wait(0), [ + ['events' => POLL_EVENT_WRITE, 'data' => 'socket2_data'] +]); +fwrite($socket1w, "test data"); +pt_expect_events($poll_ctx->wait(100), [ + ['events' => POLL_EVENT_READ, 'data' => 'socket1_data', 'read' => 'test data'] +]); + +?> +--EXPECT-- +Events matched - count: 1 +Events matched - count: 1 diff --git a/ext/standard/tests/poll/poll_stream_sock_rw_single_level.phpt b/ext/standard/tests/poll/poll_stream_sock_rw_single_level.phpt new file mode 100644 index 0000000000000..c4bfa36614a1d --- /dev/null +++ b/ext/standard/tests/poll/poll_stream_sock_rw_single_level.phpt @@ -0,0 +1,25 @@ +--TEST-- +Poll stream - socket write / read few time only +--FILE-- +wait(0), [ + ['events' => POLL_EVENT_WRITE, 'data' => 'socket2_data'] +]); +fwrite($socket1w, "test data"); +pt_expect_events($poll_ctx->wait(100), [ + ['events' => POLL_EVENT_WRITE, 'data' => 'socket2_data'], + ['events' => POLL_EVENT_READ, 'data' => 'socket1_data', 'read' => 'test data'] +]); + +?> +--EXPECT-- +Events matched - count: 1 +Events matched - count: 2 diff --git a/ext/standard/tests/poll/poll_stream_sock_write.phpt b/ext/standard/tests/poll/poll_stream_sock_write.phpt new file mode 100644 index 0000000000000..f9781b41b5b63 --- /dev/null +++ b/ext/standard/tests/poll/poll_stream_sock_write.phpt @@ -0,0 +1,18 @@ +--TEST-- +Poll stream - socket write +--FILE-- +wait(0), [ + ['events' => POLL_EVENT_WRITE, 'data' => 'socket_data'] +]); + +?> +--EXPECT-- +Events matched - count: 1 diff --git a/ext/standard/tests/poll/poll_stream_sock_write_close.phpt b/ext/standard/tests/poll/poll_stream_sock_write_close.phpt new file mode 100644 index 0000000000000..46bfb5690c9f1 --- /dev/null +++ b/ext/standard/tests/poll/poll_stream_sock_write_close.phpt @@ -0,0 +1,21 @@ +--TEST-- +Poll stream - socket write close +--FILE-- +wait(100), []); + +?> +--EXPECT-- +Events matched - count: 0 diff --git a/ext/standard/tests/poll/poll_stream_tcp_read.phpt b/ext/standard/tests/poll/poll_stream_tcp_read.phpt new file mode 100644 index 0000000000000..bdbe4b7592edb --- /dev/null +++ b/ext/standard/tests/poll/poll_stream_tcp_read.phpt @@ -0,0 +1,19 @@ +--TEST-- +Poll stream - socket read +--FILE-- +wait(100), [ + ['events' => POLL_EVENT_READ, 'data' => 'socket_data', 'read' => 'test data'] +]); + +?> +--EXPECT-- +Events matched - count: 1 diff --git a/ext/standard/tests/poll/poll_stream_tcp_read_multiple_level.phpt b/ext/standard/tests/poll/poll_stream_tcp_read_multiple_level.phpt new file mode 100644 index 0000000000000..7c6e3fdd3ac15 --- /dev/null +++ b/ext/standard/tests/poll/poll_stream_tcp_read_multiple_level.phpt @@ -0,0 +1,41 @@ +--TEST-- +Poll stream - TCP read write level +--FILE-- +wait(0), []); + +for ($i = 0; $i < count($clients); $i++) { + pt_write_sleep($clients[$i], "test $i data"); +} + +// Build expected events for all 20 connections +$expected_events = []; +for ($i = 0; $i < 20; $i++) { + $expected_events[] = ['events' => POLL_EVENT_READ, 'data' => "server{$i}_data", 'read' => "test $i data"]; +} +pt_expect_events($poll_ctx->wait(100), $expected_events); + +pt_write_sleep($clients[1], "more data"); +pt_write_sleep($clients[2], "more data"); +pt_expect_events($poll_ctx->wait(100), [ + ['events' => POLL_EVENT_READ, 'data' => 'server1_data', 'read' => 'more data'], + ['events' => POLL_EVENT_READ, 'data' => 'server2_data', 'read' => 'more data'] +]); + +pt_expect_events($poll_ctx->wait(100), []); + +?> +--EXPECT-- +Events matched - count: 0 +Events matched - count: 20 +Events matched - count: 2 +Events matched - count: 0 diff --git a/ext/standard/tests/poll/poll_stream_tcp_read_one_shot.phpt b/ext/standard/tests/poll/poll_stream_tcp_read_one_shot.phpt new file mode 100644 index 0000000000000..0e3161d67a43e --- /dev/null +++ b/ext/standard/tests/poll/poll_stream_tcp_read_one_shot.phpt @@ -0,0 +1,33 @@ +--TEST-- +Poll stream - TCP read write oneshot +--FILE-- +wait(0), []); + +pt_write_sleep($client1, "test data"); +pt_write_sleep($client2, "test data"); +pt_expect_events($poll_ctx->wait(100), [ + ['events' => POLL_EVENT_READ, 'data' => 'server1_data', 'read' => 'test data'], + ['events' => POLL_EVENT_READ, 'data' => 'server2_data', 'read' => 'test data'] +]); + +pt_write_sleep($client1, "more data"); +pt_write_sleep($client2, "more data"); +pt_expect_events($poll_ctx->wait(100), []); + +?> +--EXPECT-- +Events matched - count: 0 +Events matched - count: 2 +Events matched - count: 0 diff --git a/ext/standard/tests/poll/poll_stream_tcp_rw_one_shot.phpt b/ext/standard/tests/poll/poll_stream_tcp_rw_one_shot.phpt new file mode 100644 index 0000000000000..82b45ef1c6ad6 --- /dev/null +++ b/ext/standard/tests/poll/poll_stream_tcp_rw_one_shot.phpt @@ -0,0 +1,28 @@ +--TEST-- +Poll stream - TCP read write oneshot combined +--FILE-- +wait(0), [ + ['events' => POLL_EVENT_WRITE, 'data' => 'client_data'], + ['events' => POLL_EVENT_WRITE, 'data' => 'server_data'] +]); + +pt_write_sleep($client, "test data"); +pt_expect_events($poll_ctx->wait(100), []); + +pt_write_sleep($client, "test data"); +pt_expect_events($poll_ctx->wait(100), []); + +?> +--EXPECT-- +Events matched - count: 2 +Events matched - count: 0 +Events matched - count: 0 diff --git a/ext/standard/tests/poll/poll_stream_tcp_rw_single_level.phpt b/ext/standard/tests/poll/poll_stream_tcp_rw_single_level.phpt new file mode 100644 index 0000000000000..9834bbd8d523a --- /dev/null +++ b/ext/standard/tests/poll/poll_stream_tcp_rw_single_level.phpt @@ -0,0 +1,28 @@ +--TEST-- +Poll stream - TCP read write level combined +--FILE-- +wait(0), [ + ['events' => POLL_EVENT_WRITE, 'data' => 'client_data'], + ['events' => POLL_EVENT_WRITE, 'data' => 'server_data'] +]); + +fwrite($client, "test data"); +usleep(10000); +pt_expect_events($poll_ctx->wait(100), [ + ['events' => POLL_EVENT_WRITE, 'data' => 'client_data'], + ['events' => POLL_EVENT_READ | POLL_EVENT_WRITE, 'data' => 'server_data', 'read' => 'test data'] +]); + +?> +--EXPECT-- +Events matched - count: 2 +Events matched - count: 2 diff --git a/ext/standard/tests/poll/poll_stream_wait_no_add.phpt b/ext/standard/tests/poll/poll_stream_wait_no_add.phpt new file mode 100644 index 0000000000000..825755645d18c --- /dev/null +++ b/ext/standard/tests/poll/poll_stream_wait_no_add.phpt @@ -0,0 +1,12 @@ +--TEST-- +Poll stream - only wait +--FILE-- +wait(0); +pt_print_events($events); + +?> +--EXPECT-- +Events count: 0 diff --git a/main/php_poll.h b/main/php_poll.h new file mode 100644 index 0000000000000..a4723267202fa --- /dev/null +++ b/main/php_poll.h @@ -0,0 +1,157 @@ +/* + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Authors: Jakub Zelenka | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_POLL_H +#define PHP_POLL_H + +#include "php.h" +#include "php_network.h" + +/* ----- Public generic API ----- */ + +/* clang-format off */ + +/* Event types */ +#define PHP_POLL_READ 0x01 +#define PHP_POLL_WRITE 0x02 +#define PHP_POLL_ERROR 0x04 +#define PHP_POLL_HUP 0x08 +#define PHP_POLL_RDHUP 0x10 +#define PHP_POLL_ONESHOT 0x20 +#define PHP_POLL_ET 0x40 /* Edge-triggered */ + +/* Poll flags */ +#define PHP_POLL_FLAG_PERSISTENT 0x01 +#define PHP_POLL_FLAG_RAW_EVENTS 0x02 + +/* Poll backend types */ +typedef enum { + PHP_POLL_BACKEND_AUTO = -1, + PHP_POLL_BACKEND_POLL = 0, + PHP_POLL_BACKEND_EPOLL, + PHP_POLL_BACKEND_KQUEUE, + PHP_POLL_BACKEND_EVENTPORT, + PHP_POLL_BACKEND_WSAPOLL +} php_poll_backend_type; + +/* Error codes */ +typedef enum { + PHP_POLL_ERR_NONE, /* No error */ + PHP_POLL_ERR_SYSTEM, /* Generic system error */ + PHP_POLL_ERR_NOMEM, /* Out of memory (ENOMEM) */ + PHP_POLL_ERR_INVALID, /* Invalid argument (EINVAL, EBADF) */ + PHP_POLL_ERR_EXISTS, /* Already exists (EEXIST) */ + PHP_POLL_ERR_NOTFOUND, /* Not found (ENOENT) */ + PHP_POLL_ERR_TIMEOUT, /* Operation timed out (ETIME, ETIMEDOUT) */ + PHP_POLL_ERR_INTERRUPTED, /* Interrupted by signal (EINTR) */ + PHP_POLL_ERR_PERMISSION, /* Permission denied (EACCES, EPERM) */ + PHP_POLL_ERR_TOOBIG, /* Too many resources (EMFILE, ENFILE) */ + PHP_POLL_ERR_AGAIN, /* Try again (EAGAIN, EWOULDBLOCK) */ + PHP_POLL_ERR_NOSUPPORT, /* Not supported (ENOSYS, EOPNOTSUPP) */ +} php_poll_error; + +/* clang-format on */ + +/* Poll event structure */ +struct php_poll_event { + int fd; /* File descriptor */ + uint32_t events; /* Requested events */ + uint32_t revents; /* Returned events */ + void *data; /* User data pointer */ +}; + +/* Forward declarations */ +typedef struct php_poll_ctx php_poll_ctx; +typedef struct php_poll_backend_ops php_poll_backend_ops; +typedef struct php_poll_event php_poll_event; + +PHPAPI php_poll_ctx *php_poll_create(php_poll_backend_type preferred_backend, uint32_t flags); +PHPAPI php_poll_ctx *php_poll_create_by_name(const char *preferred_backend, uint32_t flags); + +PHPAPI zend_result php_poll_set_max_events_hint(php_poll_ctx *ctx, int max_events); +PHPAPI zend_result php_poll_init(php_poll_ctx *ctx); +PHPAPI void php_poll_destroy(php_poll_ctx *ctx); + +PHPAPI zend_result php_poll_add(php_poll_ctx *ctx, int fd, uint32_t events, void *data); +PHPAPI zend_result php_poll_modify(php_poll_ctx *ctx, int fd, uint32_t events, void *data); +PHPAPI zend_result php_poll_remove(php_poll_ctx *ctx, int fd); + +PHPAPI int php_poll_wait(php_poll_ctx *ctx, php_poll_event *events, int max_events, int timeout); + +PHPAPI const char *php_poll_backend_name(php_poll_ctx *ctx); +PHPAPI php_poll_backend_type php_poll_get_backend_type(php_poll_ctx *ctx); +PHPAPI bool php_poll_supports_et(php_poll_ctx *ctx); +PHPAPI php_poll_error php_poll_get_error(php_poll_ctx *ctx); + +/* Get suitable max_events for backend */ +PHPAPI int php_poll_get_suitable_max_events(php_poll_ctx *ctx); + +/* Backend registration */ +PHPAPI void php_poll_register_backends(void); + +/* Error string for the error */ +PHPAPI const char *php_poll_error_string(php_poll_error error); + +/* ----- Public extension API ----- */ + +typedef struct php_poll_handle_ops php_poll_handle_ops; +typedef struct php_poll_handle_object php_poll_handle_object; + +/* Handle operations structure - extensions can provide their own */ +struct php_poll_handle_ops { + /** + * Get file descriptor for this handle + * @param handle The handle object + * @return File descriptor or SOCK_ERR if invalid/not applicable + */ + php_socket_t (*get_fd)(php_poll_handle_object *handle); + + /** + * Check if handle is still valid + * @param handle The handle object + * @return true if valid, false if invalid + */ + int (*is_valid)(php_poll_handle_object *handle); + + /** + * Cleanup handle-specific data + * @param handle The handle object + */ + void (*cleanup)(php_poll_handle_object *handle); +}; + +/* Base poll handle object structure */ +struct php_poll_handle_object { + php_poll_handle_ops *ops; + void *handle_data; + zend_object std; +}; + +#define PHP_POLL_HANDLE_OBJ_FROM_ZOBJ(obj) \ + ((php_poll_handle_object *) ((char *) (obj) - XtOffsetOf(php_poll_handle_object, std))) + +#define PHP_POLL_HANDLE_OBJ_FROM_ZV(zv) PHP_POLL_HANDLE_OBJ_FROM_ZOBJ(Z_OBJ_P(zv)) + +/* Default operations */ +extern php_poll_handle_ops php_poll_handle_default_ops; + +/* Utility functions for extensions */ +PHPAPI php_poll_handle_object *php_poll_handle_object_create( + size_t obj_size, zend_class_entry *ce, php_poll_handle_ops *ops); +PHPAPI void php_poll_handle_object_free(zend_object *obj); + +/* Get file descriptor from any poll handle */ +PHPAPI php_socket_t php_poll_handle_get_fd(php_poll_handle_object *handle); + +#endif /* PHP_POLL_H */ diff --git a/main/poll/php_poll_internal.h b/main/poll/php_poll_internal.h new file mode 100644 index 0000000000000..c9e5e1d6dab27 --- /dev/null +++ b/main/poll/php_poll_internal.h @@ -0,0 +1,145 @@ +/* + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Authors: Jakub Zelenka | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_POLL_INTERNAL_H +#define PHP_POLL_INTERNAL_H + +#include "php_poll.h" +#include "php_network.h" + +/* Allocation macros */ +#define php_poll_calloc(nmemb, size, persistent) \ + ((persistent) ? calloc((nmemb), (size)) : ecalloc((nmemb), (size))) +#define php_poll_malloc(size, persistent) ((persistent) ? malloc((size)) : emalloc((size))) +#define php_poll_realloc(ptr, size, persistent) \ + ((persistent) ? realloc((ptr), (size)) : erealloc((ptr), (size))) + +/* Backend interface */ +typedef struct php_poll_backend_ops { + php_poll_backend_type type; + const char *name; + + /* Initialize backend */ + zend_result (*init)(php_poll_ctx *ctx); + + /* Cleanup backend */ + void (*cleanup)(php_poll_ctx *ctx); + + /* Add file descriptor */ + zend_result (*add)(php_poll_ctx *ctx, int fd, uint32_t events, void *data); + + /* Modify file descriptor */ + zend_result (*modify)(php_poll_ctx *ctx, int fd, uint32_t events, void *data); + + /* Remove file descriptor */ + zend_result (*remove)(php_poll_ctx *ctx, int fd); + + /* Wait for events */ + int (*wait)(php_poll_ctx *ctx, php_poll_event *events, int max_events, int timeout); + + /* Check if backend is available */ + bool (*is_available)(void); + + /* Get suitable max_events for this backend */ + int (*get_suitable_max_events)(php_poll_ctx *ctx); + + /* Backend supports edge triggering natively */ + bool supports_et; +} php_poll_backend_ops; + +/* Main poll context */ +struct php_poll_ctx { + const php_poll_backend_ops *backend_ops; + php_poll_backend_type backend_type; + php_poll_error last_error; + + /* Optional capacity hint for backends */ + int max_events_hint; + + /* Flags */ + uint32_t initialized : 1; + uint32_t persistent : 1; + uint32_t raw_events : 1; + + /* Backend-specific data */ + void *backend_data; +}; + +/* Generic FD entry structure */ +typedef struct php_poll_fd_entry { + int fd; + uint32_t events; + void *data; + bool active; + uint32_t last_revents; +} php_poll_fd_entry; + +/* FD tracking table */ +typedef struct php_poll_fd_table { + HashTable entries_ht; + bool persistent; +} php_poll_fd_table; + +/* Iterator callback function type */ +typedef bool (*php_poll_fd_iterator_func_t)(int fd, php_poll_fd_entry *entry, void *user_data); + +/* Poll FD helpers - clean API with accessor functions */ +php_poll_fd_table *php_poll_fd_table_init(int initial_capacity, bool persistent); +void php_poll_fd_table_cleanup(php_poll_fd_table *table); +php_poll_fd_entry *php_poll_fd_table_find(php_poll_fd_table *table, int fd); +php_poll_fd_entry *php_poll_fd_table_get(php_poll_fd_table *table, int fd); +void php_poll_fd_table_remove(php_poll_fd_table *table, int fd); + +/* Accessor functions for table properties */ +static inline int php_poll_fd_table_count(php_poll_fd_table *table) +{ + return zend_hash_num_elements(&table->entries_ht); +} + +static inline bool php_poll_fd_table_is_empty(php_poll_fd_table *table) +{ + return zend_hash_num_elements(&table->entries_ht) == 0; +} + +/* New helper functions for improved backend integration */ +void php_poll_fd_table_foreach( + php_poll_fd_table *table, php_poll_fd_iterator_func_t callback, void *user_data); +php_socket_t php_poll_fd_table_get_max_fd(php_poll_fd_table *table); +int php_poll_fd_table_collect_events( + php_poll_fd_table *table, php_poll_event *events, int max_events); + +/* Error helper functions */ +php_poll_error php_poll_errno_to_error(int err); + +static inline void php_poll_set_errno_error(php_poll_ctx *ctx, int err) +{ + ctx->last_error = php_poll_errno_to_error(err); +} + +static inline void php_poll_set_current_errno_error(php_poll_ctx *ctx) +{ + php_poll_set_errno_error(ctx, errno); +} + +static inline bool php_poll_is_not_found_error(void) +{ + return errno == ENOENT; +} + +static inline void php_poll_set_error(php_poll_ctx *ctx, php_poll_error error) +{ + ctx->last_error = error; +} + +#endif /* PHP_POLL_INTERNAL_H */ diff --git a/main/poll/poll_backend_epoll.c b/main/poll/poll_backend_epoll.c new file mode 100644 index 0000000000000..5e523e41f4bb3 --- /dev/null +++ b/main/poll/poll_backend_epoll.c @@ -0,0 +1,240 @@ +/* + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Authors: Jakub Zelenka | + +----------------------------------------------------------------------+ +*/ + +#include "php_poll_internal.h" + +#ifdef HAVE_EPOLL + +#include + +typedef struct { + int epoll_fd; + struct epoll_event *events; + int events_capacity; + int fd_count; +} epoll_backend_data_t; + +static uint32_t epoll_events_to_native(uint32_t events) +{ + uint32_t native = 0; + if (events & PHP_POLL_READ) { + native |= EPOLLIN; + } + if (events & PHP_POLL_WRITE) { + native |= EPOLLOUT; + } + if (events & PHP_POLL_ERROR) { + native |= EPOLLERR; + } + if (events & PHP_POLL_HUP) { + native |= EPOLLHUP; + } + if (events & PHP_POLL_RDHUP) { + native |= EPOLLRDHUP; + } + if (events & PHP_POLL_ONESHOT) { + native |= EPOLLONESHOT; + } + if (events & PHP_POLL_ET) { + native |= EPOLLET; + } + return native; +} + +static uint32_t epoll_events_from_native(uint32_t native) +{ + uint32_t events = 0; + if (native & EPOLLIN) { + events |= PHP_POLL_READ; + } + if (native & EPOLLOUT) { + events |= PHP_POLL_WRITE; + } + if (native & EPOLLERR) { + events |= PHP_POLL_ERROR; + } + if (native & EPOLLHUP) { + events |= PHP_POLL_HUP; + } + if (native & EPOLLRDHUP) { + events |= PHP_POLL_RDHUP; + } + return events; +} + +static zend_result epoll_backend_init(php_poll_ctx *ctx) +{ + epoll_backend_data_t *data = php_poll_calloc(1, sizeof(epoll_backend_data_t), ctx->persistent); + if (!data) { + php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM); + return FAILURE; + } + + data->epoll_fd = epoll_create1(EPOLL_CLOEXEC); + if (data->epoll_fd == -1) { + pefree(data, ctx->persistent); + php_poll_set_error(ctx, PHP_POLL_ERR_SYSTEM); + return FAILURE; + } + + /* Use hint for initial allocation if provided, otherwise start with reasonable default */ + int initial_capacity = ctx->max_events_hint > 0 ? ctx->max_events_hint : 64; + data->events = php_poll_calloc(initial_capacity, sizeof(struct epoll_event), ctx->persistent); + if (!data->events) { + close(data->epoll_fd); + pefree(data, ctx->persistent); + php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM); + return FAILURE; + } + data->events_capacity = initial_capacity; + + ctx->backend_data = data; + return SUCCESS; +} + +static void epoll_backend_cleanup(php_poll_ctx *ctx) +{ + epoll_backend_data_t *data = (epoll_backend_data_t *) ctx->backend_data; + if (data) { + if (data->epoll_fd >= 0) { + close(data->epoll_fd); + } + pefree(data->events, ctx->persistent); + pefree(data, ctx->persistent); + ctx->backend_data = NULL; + } +} + +static zend_result epoll_backend_add(php_poll_ctx *ctx, int fd, uint32_t events, void *data) +{ + epoll_backend_data_t *backend_data = (epoll_backend_data_t *) ctx->backend_data; + + struct epoll_event ev = { 0 }; + ev.events = epoll_events_to_native(events); + ev.data.ptr = data; + + if (epoll_ctl(backend_data->epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) { + php_poll_set_error(ctx, (errno == EEXIST) ? PHP_POLL_ERR_EXISTS : PHP_POLL_ERR_SYSTEM); + return FAILURE; + } + backend_data->fd_count++; + + return SUCCESS; +} + +static zend_result epoll_backend_modify(php_poll_ctx *ctx, int fd, uint32_t events, void *data) +{ + epoll_backend_data_t *backend_data = (epoll_backend_data_t *) ctx->backend_data; + + struct epoll_event ev = { 0 }; + ev.events = epoll_events_to_native(events); + ev.data.ptr = data; + + if (epoll_ctl(backend_data->epoll_fd, EPOLL_CTL_MOD, fd, &ev) == -1) { + php_poll_set_error(ctx, (errno == ENOENT) ? PHP_POLL_ERR_NOTFOUND : PHP_POLL_ERR_SYSTEM); + return FAILURE; + } + + return SUCCESS; +} + +static zend_result epoll_backend_remove(php_poll_ctx *ctx, int fd) +{ + epoll_backend_data_t *backend_data = (epoll_backend_data_t *) ctx->backend_data; + + if (epoll_ctl(backend_data->epoll_fd, EPOLL_CTL_DEL, fd, NULL) == -1) { + php_poll_set_error(ctx, (errno == ENOENT) ? PHP_POLL_ERR_NOTFOUND : PHP_POLL_ERR_SYSTEM); + return FAILURE; + } + backend_data->fd_count--; + + return SUCCESS; +} + +static int epoll_backend_wait( + php_poll_ctx *ctx, php_poll_event *events, int max_events, int timeout) +{ + epoll_backend_data_t *backend_data = (epoll_backend_data_t *) ctx->backend_data; + + /* Ensure we have enough space for the requested events */ + if (max_events > backend_data->events_capacity) { + struct epoll_event *new_events = php_poll_realloc( + backend_data->events, max_events * sizeof(struct epoll_event), ctx->persistent); + if (!new_events) { + php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM); + return -1; + } + backend_data->events = new_events; + backend_data->events_capacity = max_events; + } + + int nfds = epoll_wait(backend_data->epoll_fd, backend_data->events, max_events, timeout); + + if (nfds > 0) { + for (int i = 0; i < nfds; i++) { + events[i].fd = backend_data->events[i].data.fd; + events[i].events = 0; /* Not used in results */ + events[i].revents = epoll_events_from_native(backend_data->events[i].events); + events[i].data = backend_data->events[i].data.ptr; + } + } + + return nfds; +} + +static int epoll_backend_get_suitable_max_events(php_poll_ctx *ctx) +{ + epoll_backend_data_t *backend_data = (epoll_backend_data_t *) ctx->backend_data; + + if (!backend_data) { + return -1; + } + + /* For epoll, we now track exactly how many FDs are registered */ + int active_fds = backend_data->fd_count; + + if (active_fds == 0) { + return 1; + } + + /* Epoll can return exactly one event per registered FD, + * so the suitable max_events is exactly the number of registered FDs */ + return active_fds; +} + +static bool epoll_backend_is_available(void) +{ + int fd = epoll_create1(EPOLL_CLOEXEC); + if (fd >= 0) { + close(fd); + return true; + } + return false; +} + +const php_poll_backend_ops php_poll_backend_epoll_ops = { + .type = PHP_POLL_BACKEND_EPOLL, + .name = "epoll", + .init = epoll_backend_init, + .cleanup = epoll_backend_cleanup, + .add = epoll_backend_add, + .modify = epoll_backend_modify, + .remove = epoll_backend_remove, + .wait = epoll_backend_wait, + .is_available = epoll_backend_is_available, + .get_suitable_max_events = epoll_backend_get_suitable_max_events, + .supports_et = true, +}; + +#endif /* HAVE_EPOLL */ diff --git a/main/poll/poll_backend_eventport.c b/main/poll/poll_backend_eventport.c new file mode 100644 index 0000000000000..26ff374f9dab1 --- /dev/null +++ b/main/poll/poll_backend_eventport.c @@ -0,0 +1,407 @@ +/* + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Authors: Jakub Zelenka | + +----------------------------------------------------------------------+ +*/ + +#include "php_poll_internal.h" + +#ifdef HAVE_EVENT_PORTS + +#include +#include +#include +#include +#include + +typedef struct { + int port_fd; + port_event_t *events; + int events_capacity; + int active_associations; + php_poll_fd_table *fd_table; +} eventport_backend_data_t; + +/* Convert our event flags to event port flags */ +static int eventport_events_to_native(uint32_t events) +{ + int native = 0; + if (events & PHP_POLL_READ) { + native |= POLLIN; + } + if (events & PHP_POLL_WRITE) { + native |= POLLOUT; + } + if (events & PHP_POLL_ERROR) { + native |= POLLERR; + } + if (events & PHP_POLL_HUP) { + native |= POLLHUP; + } + if (events & PHP_POLL_RDHUP) { + native |= POLLHUP; /* Map RDHUP to HUP */ + } + return native; +} + +/* Convert event port flags back to our event flags */ +static uint32_t eventport_events_from_native(int native) +{ + uint32_t events = 0; + if (native & POLLIN) { + events |= PHP_POLL_READ; + } + if (native & POLLOUT) { + events |= PHP_POLL_WRITE; + } + if (native & POLLERR) { + events |= PHP_POLL_ERROR; + } + if (native & POLLHUP) { + events |= PHP_POLL_HUP; + } + if (native & POLLNVAL) { + events |= PHP_POLL_ERROR; + } + return events; +} + +/* Initialize event port backend */ +static zend_result eventport_backend_init(php_poll_ctx *ctx) +{ + eventport_backend_data_t *data + = php_poll_calloc(1, sizeof(eventport_backend_data_t), ctx->persistent); + if (!data) { + php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM); + return FAILURE; + } + + /* Create event port */ + data->port_fd = port_create(); + if (data->port_fd == -1) { + pefree(data, ctx->persistent); + php_poll_set_error(ctx, PHP_POLL_ERR_SYSTEM); + return FAILURE; + } + + data->active_associations = 0; + + /* Use hint for initial allocation if provided, otherwise start with reasonable default */ + int initial_capacity = ctx->max_events_hint > 0 ? ctx->max_events_hint : 64; + data->events = php_poll_calloc(initial_capacity, sizeof(port_event_t), ctx->persistent); + if (!data->events) { + close(data->port_fd); + pefree(data, ctx->persistent); + php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM); + return FAILURE; + } + data->events_capacity = initial_capacity; + + /* Initialize FD tracking using helper */ + data->fd_table = php_poll_fd_table_init(initial_capacity, ctx->persistent); + if (!data->fd_table) { + close(data->port_fd); + pefree(data->events, ctx->persistent); + pefree(data, ctx->persistent); + php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM); + return FAILURE; + } + + ctx->backend_data = data; + return SUCCESS; +} + +/* Cleanup event port backend */ +static void eventport_backend_cleanup(php_poll_ctx *ctx) +{ + eventport_backend_data_t *data = (eventport_backend_data_t *) ctx->backend_data; + if (data) { + if (data->port_fd >= 0) { + close(data->port_fd); + } + pefree(data->events, ctx->persistent); + php_poll_fd_table_cleanup(data->fd_table); + pefree(data, ctx->persistent); + ctx->backend_data = NULL; + } +} + +/* Add file descriptor to event port */ +static zend_result eventport_backend_add( + php_poll_ctx *ctx, int fd, uint32_t events, void *user_data) +{ + eventport_backend_data_t *backend_data = (eventport_backend_data_t *) ctx->backend_data; + + if (php_poll_fd_table_find(backend_data->fd_table, fd)) { + php_poll_set_error(ctx, PHP_POLL_ERR_EXISTS); + return FAILURE; + } + + php_poll_fd_entry *entry = php_poll_fd_table_get(backend_data->fd_table, fd); + if (!entry) { + php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM); + return FAILURE; + } + + entry->events = events; + entry->data = user_data; + + int native_events = eventport_events_to_native(events); + + /* Associate file descriptor with event port */ + if (port_associate(backend_data->port_fd, PORT_SOURCE_FD, fd, native_events, user_data) == -1) { + php_poll_fd_table_remove(backend_data->fd_table, fd); + switch (errno) { + case EEXIST: + php_poll_set_error(ctx, PHP_POLL_ERR_EXISTS); + break; + case ENOMEM: + php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM); + break; + case EBADF: + case EINVAL: + php_poll_set_error(ctx, PHP_POLL_ERR_INVALID); + break; + default: + php_poll_set_error(ctx, PHP_POLL_ERR_SYSTEM); + break; + } + return FAILURE; + } + + backend_data->active_associations++; + return SUCCESS; +} + +/* Modify file descriptor in event port */ +static zend_result eventport_backend_modify( + php_poll_ctx *ctx, int fd, uint32_t events, void *user_data) +{ + eventport_backend_data_t *backend_data = (eventport_backend_data_t *) ctx->backend_data; + + php_poll_fd_entry *entry = php_poll_fd_table_find(backend_data->fd_table, fd); + if (!entry) { + php_poll_set_error(ctx, PHP_POLL_ERR_NOTFOUND); + return FAILURE; + } + + /* Update entry */ + entry->events = events; + entry->data = user_data; + + /* For event ports, we need to dissociate and re-associate */ + /* Note: dissociate might fail if the fd was already fired and auto-dissociated */ + port_dissociate(backend_data->port_fd, PORT_SOURCE_FD, fd); + + int native_events = eventport_events_to_native(events); + if (port_associate(backend_data->port_fd, PORT_SOURCE_FD, fd, native_events, user_data) == -1) { + switch (errno) { + case ENOMEM: + php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM); + break; + case EBADF: + case EINVAL: + php_poll_set_error(ctx, PHP_POLL_ERR_INVALID); + break; + default: + php_poll_set_error(ctx, PHP_POLL_ERR_SYSTEM); + break; + } + return FAILURE; + } + + return SUCCESS; +} + +/* Remove file descriptor from event port */ +static zend_result eventport_backend_remove(php_poll_ctx *ctx, int fd) +{ + eventport_backend_data_t *backend_data = (eventport_backend_data_t *) ctx->backend_data; + + /* Check if exists using helper */ + if (!php_poll_fd_table_find(backend_data->fd_table, fd)) { + php_poll_set_error(ctx, PHP_POLL_ERR_NOTFOUND); + return FAILURE; + } + + if (port_dissociate(backend_data->port_fd, PORT_SOURCE_FD, fd) == -1) { + /* Only fail if it's not ENOENT (might already be dissociated) */ + if (!php_poll_is_not_found_error()) { + php_poll_set_current_errno_error(ctx); + return FAILURE; + } + } + + php_poll_fd_table_remove(backend_data->fd_table, fd); + backend_data->active_associations--; + return SUCCESS; +} + +/* Handle re-association after event */ +static void eventport_handle_reassociation( + eventport_backend_data_t *backend_data, int fd, uint32_t fired_events) +{ + php_poll_fd_entry *entry = php_poll_fd_table_find(backend_data->fd_table, fd); + if (!entry) { + return; + } + + if (entry->events & PHP_POLL_ONESHOT) { + /* Oneshot: remove from tracking */ + php_poll_fd_table_remove(backend_data->fd_table, fd); + backend_data->active_associations--; + return; + } + + /* Determine which events to re-associate with */ + uint32_t reassoc_events = entry->events; + if (entry->events & PHP_POLL_ET) { + /* Edge-triggered: don't re-associate with events that just fired */ + reassoc_events &= ~fired_events; + reassoc_events &= ~PHP_POLL_ET; /* Remove ET flag for port_associate */ + } + + if (reassoc_events != 0) { + /* Re-associate for continued monitoring */ + int native_events = eventport_events_to_native(reassoc_events); + if (port_associate(backend_data->port_fd, PORT_SOURCE_FD, fd, native_events, entry->data) + != 0) { + /* Re-association failed - might be due to fd being closed */ + php_poll_fd_table_remove(backend_data->fd_table, fd); + backend_data->active_associations--; + } + } else { + /* No events to re-associate with */ + php_poll_fd_table_remove(backend_data->fd_table, fd); + backend_data->active_associations--; + } +} + +/* Wait for events using event port */ +static int eventport_backend_wait( + php_poll_ctx *ctx, php_poll_event *events, int max_events, int timeout) +{ + eventport_backend_data_t *backend_data = (eventport_backend_data_t *) ctx->backend_data; + + if (backend_data->active_associations == 0) { + /* No active associations, but we still need to respect timeout */ + if (timeout > 0) { + struct timespec ts; + ts.tv_sec = timeout / 1000; + ts.tv_nsec = (timeout % 1000) * 1000000; + nanosleep(&ts, NULL); + } + return 0; + } + + /* Ensure we have enough space for the requested events */ + if (max_events > backend_data->events_capacity) { + port_event_t *new_events = php_poll_realloc( + backend_data->events, max_events * sizeof(port_event_t), ctx->persistent); + if (!new_events) { + php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM); + return -1; + } + backend_data->events = new_events; + backend_data->events_capacity = max_events; + } + + /* Setup timeout structure */ + struct timespec ts = { 0 }, *tsp = NULL; + if (timeout >= 0) { + ts.tv_sec = timeout / 1000; + ts.tv_nsec = (timeout % 1000) * 1000000; + tsp = &ts; + } + + /* Retrieve events from port */ + uint_t nget = 1; /* We want to get multiple events if available */ + int result = port_getn(backend_data->port_fd, backend_data->events, max_events, &nget, tsp); + + if (result == -1) { + php_poll_set_current_errno_error(ctx); + } + + int nfds = (int) nget; + + /* Process the events and handle re-association */ + for (int i = 0; i < nfds; i++) { + port_event_t *port_event = &backend_data->events[i]; + + /* Only handle PORT_SOURCE_FD events */ + if (port_event->portev_source == PORT_SOURCE_FD) { + int fd = (int) port_event->portev_object; + events[i].fd = fd; + events[i].events = 0; /* Not used in results */ + events[i].revents = eventport_events_from_native(port_event->portev_events); + events[i].data = port_event->portev_user; + + /* Handle re-association based on event type */ + eventport_handle_reassociation(backend_data, fd, events[i].revents); + } else { + /* Handle other event sources if needed (timers, user events, etc.) */ + events[i].fd = -1; + events[i].events = 0; + events[i].revents = 0; + events[i].data = port_event->portev_user; + } + } + + return nfds; +} + +/* Check if event port backend is available */ +static bool eventport_backend_is_available(void) +{ + int fd = port_create(); + if (fd >= 0) { + close(fd); + return true; + } + return false; +} + +static int eventport_backend_get_suitable_max_events(php_poll_ctx *ctx) +{ + eventport_backend_data_t *backend_data = (eventport_backend_data_t *) ctx->backend_data; + + if (!backend_data || !backend_data->fd_table) { + return -1; + } + + /* For event ports, we track exactly how many FD associations are active */ + int active_associations = backend_data->active_associations; + + if (active_associations == 0) { + return 1; + } + + /* Event ports can return exactly one event per association, + * so the suitable max_events is exactly the number of active associations */ + return active_associations; +} + +/* Event port backend operations structure */ +const php_poll_backend_ops php_poll_backend_eventport_ops = { + .type = PHP_POLL_BACKEND_EVENTPORT, + .name = "eventport", + .init = eventport_backend_init, + .cleanup = eventport_backend_cleanup, + .add = eventport_backend_add, + .modify = eventport_backend_modify, + .remove = eventport_backend_remove, + .wait = eventport_backend_wait, + .is_available = eventport_backend_is_available, + .get_suitable_max_events = eventport_backend_get_suitable_max_events, + .supports_et = true /* Supports both level and edge triggering */ +}; + +#endif /* HAVE_EVENT_PORTS */ diff --git a/main/poll/poll_backend_kqueue.c b/main/poll/poll_backend_kqueue.c new file mode 100644 index 0000000000000..23e888a9c2917 --- /dev/null +++ b/main/poll/poll_backend_kqueue.c @@ -0,0 +1,455 @@ +/* + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Authors: Jakub Zelenka | + +----------------------------------------------------------------------+ +*/ + +#include "php_poll_internal.h" + +#ifdef HAVE_KQUEUE + +#include +#include +#include + +typedef struct { + int kqueue_fd; + struct kevent *events; + int events_capacity; + int fd_count; /* Track number of unique FDs (not individual filters) */ + int filter_count; /* Track total number of filters for raw events */ + HashTable *complete_oneshot_fds; /* Track FDs with both read+write oneshot */ + HashTable *garbage_oneshot_fds; /* Pre-cached hash table for FDs to delete */ +} kqueue_backend_data_t; + +static zend_result kqueue_backend_init(php_poll_ctx *ctx) +{ + kqueue_backend_data_t *data + = php_poll_calloc(1, sizeof(kqueue_backend_data_t), ctx->persistent); + if (!data) { + php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM); + return FAILURE; + } + + data->kqueue_fd = kqueue(); + if (data->kqueue_fd == -1) { + pefree(data, ctx->persistent); + php_poll_set_error(ctx, PHP_POLL_ERR_SYSTEM); + return FAILURE; + } + + /* Use hint for initial allocation if provided, otherwise start with reasonable default */ + int initial_capacity = ctx->max_events_hint > 0 ? ctx->max_events_hint : 64; + data->events = php_poll_calloc(initial_capacity, sizeof(struct kevent), ctx->persistent); + if (!data->events) { + close(data->kqueue_fd); + pefree(data, ctx->persistent); + php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM); + return FAILURE; + } + data->events_capacity = initial_capacity; + data->fd_count = 0; /* Initialize FD counter */ + data->filter_count = 0; /* Initialize filter counter */ + + /* Only initialize oneshot related hash tables if not using raw events */ + if (!ctx->raw_events) { + data->complete_oneshot_fds = php_poll_malloc(sizeof(HashTable), ctx->persistent); + zend_hash_init(data->complete_oneshot_fds, 8, NULL, NULL, ctx->persistent); + data->garbage_oneshot_fds = php_poll_malloc(sizeof(HashTable), ctx->persistent); + zend_hash_init(data->garbage_oneshot_fds, 8, NULL, NULL, ctx->persistent); + } else { + data->complete_oneshot_fds = NULL; + data->garbage_oneshot_fds = NULL; + } + + ctx->backend_data = data; + return SUCCESS; +} + +static void kqueue_backend_cleanup(php_poll_ctx *ctx) +{ + kqueue_backend_data_t *data = (kqueue_backend_data_t *) ctx->backend_data; + if (data) { + if (data->kqueue_fd >= 0) { + close(data->kqueue_fd); + } + pefree(data->events, ctx->persistent); + + /* Only cleanup hash tables if they were initialized */ + if (data->complete_oneshot_fds) { + zend_hash_destroy(data->complete_oneshot_fds); + pefree(data->complete_oneshot_fds, ctx->persistent); + } + if (data->garbage_oneshot_fds) { + zend_hash_destroy(data->garbage_oneshot_fds); + pefree(data->garbage_oneshot_fds, ctx->persistent); + } + + pefree(data, ctx->persistent); + ctx->backend_data = NULL; + } +} + +static zend_result kqueue_backend_add(php_poll_ctx *ctx, int fd, uint32_t events, void *data) +{ + kqueue_backend_data_t *backend_data = (kqueue_backend_data_t *) ctx->backend_data; + + struct kevent changes[2]; /* Max 2 changes: read + write */ + int change_count = 0; + + uint16_t flags = EV_ADD | EV_ENABLE; + if (events & PHP_POLL_ONESHOT) { + flags |= EV_ONESHOT; + } + if (events & PHP_POLL_ET) { + flags |= EV_CLEAR; + } + + if (events & PHP_POLL_READ) { + EV_SET(&changes[change_count], fd, EVFILT_READ, flags, 0, 0, data); + change_count++; + } + + if (events & PHP_POLL_WRITE) { + EV_SET(&changes[change_count], fd, EVFILT_WRITE, flags, 0, 0, data); + change_count++; + } + + if (change_count > 0) { + int result = kevent(backend_data->kqueue_fd, changes, change_count, NULL, 0, NULL); + if (result == -1) { + php_poll_set_current_errno_error(ctx); + return FAILURE; + } + + /* Increment FD count only once per unique FD */ + backend_data->fd_count++; + /* Increment filter count by number of filters added */ + backend_data->filter_count += change_count; + + /* Track oneshot only if not using raw events */ + if (!ctx->raw_events + && (events & (PHP_POLL_READ | PHP_POLL_WRITE | PHP_POLL_ONESHOT)) + == (PHP_POLL_READ | PHP_POLL_WRITE | PHP_POLL_ONESHOT)) { + zend_hash_index_add_empty_element(backend_data->complete_oneshot_fds, fd); + } + } + + return SUCCESS; +} + +static zend_result kqueue_backend_modify(php_poll_ctx *ctx, int fd, uint32_t events, void *data) +{ + kqueue_backend_data_t *backend_data = (kqueue_backend_data_t *) ctx->backend_data; + + struct kevent deletes[2]; + struct kevent adds[2]; + int delete_count = 0; + int add_count = 0; + int successful_deletes = 0; + + uint16_t add_flags = EV_ADD | EV_ENABLE; + if (events & PHP_POLL_ONESHOT) { + add_flags |= EV_ONESHOT; + } + if (events & PHP_POLL_ET) { + add_flags |= EV_CLEAR; + } + + /* Delete existing filters that are not in the new events */ + if (!(events & PHP_POLL_READ)) { + EV_SET(&deletes[delete_count], fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); + delete_count++; + } + if (!(events & PHP_POLL_WRITE)) { + EV_SET(&deletes[delete_count], fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); + delete_count++; + } + + /* Prepare add operations for requested events */ + if (events & PHP_POLL_READ) { + EV_SET(&adds[add_count], fd, EVFILT_READ, add_flags, 0, 0, data); + add_count++; + } + if (events & PHP_POLL_WRITE) { + EV_SET(&adds[add_count], fd, EVFILT_WRITE, add_flags, 0, 0, data); + add_count++; + } + + /* Delete existing filters individually to count successes */ + for (int i = 0; i < delete_count; i++) { + int result = kevent(backend_data->kqueue_fd, &deletes[i], 1, NULL, 0, NULL); + if (result == 0) { + successful_deletes++; + } else if (!php_poll_is_not_found_error()) { + php_poll_set_current_errno_error(ctx); + return FAILURE; + } + /* ENOENT is ignored - filter didn't exist */ + } + + /* Add new filters */ + if (add_count > 0) { + int result = kevent(backend_data->kqueue_fd, adds, add_count, NULL, 0, NULL); + if (result == -1) { + php_poll_set_current_errno_error(ctx); + return FAILURE; + } + } + + /* Update counters and oneshot tracking */ + if (successful_deletes > 0 && add_count == 0) { + /* Removed all filters - FD is gone */ + backend_data->fd_count--; + backend_data->filter_count -= successful_deletes; + if (!ctx->raw_events) { + zend_hash_index_del(backend_data->complete_oneshot_fds, fd); + } + } else if (successful_deletes == 0 && add_count > 0) { + /* Added filters to previously empty FD */ + backend_data->fd_count++; + backend_data->filter_count += add_count; + if (!ctx->raw_events + && (events & (PHP_POLL_READ | PHP_POLL_WRITE | PHP_POLL_ONESHOT)) + == (PHP_POLL_READ | PHP_POLL_WRITE | PHP_POLL_ONESHOT)) { + zend_hash_index_add_empty_element(backend_data->complete_oneshot_fds, fd); + } + } else if (successful_deletes > 0 || add_count > 0) { + /* Mixed operation - update filter count */ + backend_data->filter_count = backend_data->filter_count - successful_deletes + add_count; + if (!ctx->raw_events) { + /* One of the filters was deleted so remove from oneshot tracking */ + zend_hash_index_del(backend_data->complete_oneshot_fds, fd); + } + } + + return SUCCESS; +} + +static zend_result kqueue_backend_remove(php_poll_ctx *ctx, int fd) +{ + kqueue_backend_data_t *backend_data = (kqueue_backend_data_t *) ctx->backend_data; + struct kevent change; + int successful_deletes = 0; + + /* Try to remove read filter */ + EV_SET(&change, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); + int result = kevent(backend_data->kqueue_fd, &change, 1, NULL, 0, NULL); + if (result == 0) { + successful_deletes++; + } else if (!php_poll_is_not_found_error()) { + php_poll_set_current_errno_error(ctx); + return FAILURE; + } + + /* Try to remove write filter */ + EV_SET(&change, fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); + result = kevent(backend_data->kqueue_fd, &change, 1, NULL, 0, NULL); + if (result == 0) { + successful_deletes++; + } else if (!php_poll_is_not_found_error()) { + php_poll_set_current_errno_error(ctx); + return FAILURE; + } + + /* If no filters were successfully deleted, that's an error */ + if (successful_deletes == 0) { + php_poll_set_error(ctx, PHP_POLL_ERR_NOTFOUND); + return FAILURE; + } + + /* Update counters - we removed all filters for this FD */ + backend_data->fd_count--; + backend_data->filter_count -= successful_deletes; + + /* Remove from complete oneshot tracking if not using raw events */ + if (!ctx->raw_events) { + zend_hash_index_del(backend_data->complete_oneshot_fds, fd); + } + + return SUCCESS; +} + +static int kqueue_backend_wait( + php_poll_ctx *ctx, php_poll_event *events, int max_events, int timeout) +{ + kqueue_backend_data_t *backend_data = (kqueue_backend_data_t *) ctx->backend_data; + + /* For raw events, we need capacity for max_events. + * For grouped events, kqueue can return up to 2 events per FD, so we need 2x capacity. */ + int required_capacity = ctx->raw_events ? max_events : (max_events * 2); + if (required_capacity > backend_data->events_capacity) { + struct kevent *new_events = php_poll_realloc( + backend_data->events, required_capacity * sizeof(struct kevent), ctx->persistent); + if (!new_events) { + php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM); + return -1; + } + backend_data->events = new_events; + backend_data->events_capacity = required_capacity; + } + + struct timespec ts = { 0 }, *tsp = NULL; + if (timeout >= 0) { + ts.tv_sec = timeout / 1000; + ts.tv_nsec = (timeout % 1000) * 1000000; + tsp = &ts; + } + + int nfds = kevent( + backend_data->kqueue_fd, NULL, 0, backend_data->events, required_capacity, tsp); + + if (nfds > 0) { + if (ctx->raw_events) { + /* Raw events mode - direct 1:1 mapping, no grouping */ + for (int i = 0; i < nfds && i < max_events; i++) { + events[i].fd = (int) backend_data->events[i].ident; + events[i].events = 0; /* Not used in raw mode */ + events[i].revents = 0; + events[i].data = backend_data->events[i].udata; + + /* Convert kqueue filter to poll event */ + if (backend_data->events[i].filter == EVFILT_READ) { + events[i].revents |= PHP_POLL_READ; + } else if (backend_data->events[i].filter == EVFILT_WRITE) { + events[i].revents |= PHP_POLL_WRITE; + } + + /* Convert kqueue flags to poll events */ + if (backend_data->events[i].flags & EV_EOF) { + events[i].revents |= PHP_POLL_HUP; + } + if (backend_data->events[i].flags & EV_ERROR) { + events[i].revents |= PHP_POLL_ERROR; + } + } + /* In raw mode, we might return fewer events than nfds if max_events < nfds */ + return nfds > max_events ? max_events : nfds; + } else { + /* Grouped events mode - existing complex logic */ + int unique_events = 0, fd; + zend_hash_clean(backend_data->garbage_oneshot_fds); + + for (int i = 0; i < nfds; i++) { + fd = (int) backend_data->events[i].ident; + uint32_t revents = 0; + void *data = backend_data->events[i].udata; + bool is_oneshot = (backend_data->events[i].flags & EV_ONESHOT) != 0; + + /* Convert this event */ + if (backend_data->events[i].filter == EVFILT_READ) { + revents |= PHP_POLL_READ; + } else if (backend_data->events[i].filter == EVFILT_WRITE) { + revents |= PHP_POLL_WRITE; + } + + if (backend_data->events[i].flags & EV_EOF) { + revents |= PHP_POLL_HUP; + } + if (backend_data->events[i].flags & EV_ERROR) { + revents |= PHP_POLL_ERROR; + } + + /* Look for existing event for this FD */ + bool found = false; + for (int j = 0; j < unique_events; j++) { + if (events[j].fd == fd) { + /* Combine with existing event */ + events[j].revents |= revents; + found = true; + break; + } + } + + if (!found) { + /* New FD, create new event */ + ZEND_ASSERT(unique_events < max_events); + events[unique_events].fd = fd; + events[unique_events].events = 0; + events[unique_events].revents = revents; + events[unique_events].data = data; + unique_events++; + + if (is_oneshot + && zend_hash_index_exists(backend_data->complete_oneshot_fds, fd)) { + zval dummy; + ZVAL_BOOL(&dummy, revents & PHP_POLL_READ); + zend_hash_index_add(backend_data->garbage_oneshot_fds, fd, &dummy); + zend_hash_index_del(backend_data->complete_oneshot_fds, fd); + backend_data->fd_count--; + } + } else if (is_oneshot) { + zend_hash_index_del(backend_data->garbage_oneshot_fds, fd); + } + } + + /* Clean up all the same FD filters for other read or write side */ + zval *item; + struct kevent cleanup_change; + ZEND_HASH_FOREACH_NUM_KEY_VAL(backend_data->garbage_oneshot_fds, fd, item) + { + int filter = Z_TYPE_P(item) == IS_TRUE ? EVFILT_WRITE : EVFILT_READ; + EV_SET(&cleanup_change, fd, filter, EV_DELETE, 0, 0, NULL); + kevent(backend_data->kqueue_fd, &cleanup_change, 1, NULL, 0, NULL); + } + ZEND_HASH_FOREACH_END(); + + return unique_events; + } + } + + return nfds; +} + +static bool kqueue_backend_is_available(void) +{ + int fd = kqueue(); + if (fd >= 0) { + close(fd); + return true; + } + return false; +} + +static int kqueue_backend_get_suitable_max_events(php_poll_ctx *ctx) +{ + kqueue_backend_data_t *backend_data = (kqueue_backend_data_t *) ctx->backend_data; + + if (!backend_data) { + return -1; + } + + if (ctx->raw_events) { + /* For raw events, return the total number of filters */ + int active_filters = backend_data->filter_count; + return active_filters == 0 ? 1 : active_filters; + } else { + /* For grouped events, return the number of unique FDs */ + int active_fds = backend_data->fd_count; + return active_fds == 0 ? 1 : active_fds; + } +} + +const php_poll_backend_ops php_poll_backend_kqueue_ops = { + .type = PHP_POLL_BACKEND_KQUEUE, + .name = "kqueue", + .init = kqueue_backend_init, + .cleanup = kqueue_backend_cleanup, + .add = kqueue_backend_add, + .modify = kqueue_backend_modify, + .remove = kqueue_backend_remove, + .wait = kqueue_backend_wait, + .is_available = kqueue_backend_is_available, + .get_suitable_max_events = kqueue_backend_get_suitable_max_events, + .supports_et = true /* kqueue supports EV_CLEAR for edge triggering */ +}; + +#endif /* HAVE_KQUEUE */ diff --git a/main/poll/poll_backend_poll.c b/main/poll/poll_backend_poll.c new file mode 100644 index 0000000000000..d31c227238652 --- /dev/null +++ b/main/poll/poll_backend_poll.c @@ -0,0 +1,296 @@ +/* + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Authors: Jakub Zelenka | + +----------------------------------------------------------------------+ +*/ + +#include "php_poll_internal.h" + +#ifndef PHP_WIN32 + +typedef struct { + php_poll_fd_table *fd_table; + struct pollfd *temp_fds; + int temp_fds_capacity; +} poll_backend_data_t; + +static uint32_t poll_events_to_native(uint32_t events) +{ + uint32_t native = 0; + if (events & PHP_POLL_READ) { + native |= POLLIN; + } + if (events & PHP_POLL_WRITE) { + native |= POLLOUT; + } + if (events & PHP_POLL_ERROR) { + native |= POLLERR; + } + if (events & PHP_POLL_HUP) { + native |= POLLHUP; + } + return native; +} + +static uint32_t poll_events_from_native(uint32_t native) +{ + uint32_t events = 0; + if (native & POLLIN) { + events |= PHP_POLL_READ; + } + if (native & POLLOUT) { + events |= PHP_POLL_WRITE; + } + if (native & POLLERR) { + events |= PHP_POLL_ERROR; + } + if (native & POLLHUP) { + events |= PHP_POLL_HUP; + } + if (native & POLLNVAL) { + events |= PHP_POLL_ERROR; /* Map invalid FD to error */ + } + return events; +} + +static zend_result poll_backend_init(php_poll_ctx *ctx) +{ + poll_backend_data_t *data = php_poll_calloc(1, sizeof(poll_backend_data_t), ctx->persistent); + if (!data) { + php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM); + return FAILURE; + } + + int initial_capacity = ctx->max_events_hint > 0 ? ctx->max_events_hint : 64; + + data->fd_table = php_poll_fd_table_init(initial_capacity, ctx->persistent); + if (!data->fd_table) { + pefree(data, ctx->persistent); + php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM); + return FAILURE; + } + + data->temp_fds = php_poll_calloc(initial_capacity, sizeof(struct pollfd), ctx->persistent); + if (!data->temp_fds) { + php_poll_fd_table_cleanup(data->fd_table); + pefree(data, ctx->persistent); + php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM); + return FAILURE; + } + data->temp_fds_capacity = initial_capacity; + + ctx->backend_data = data; + return SUCCESS; +} + +static void poll_backend_cleanup(php_poll_ctx *ctx) +{ + poll_backend_data_t *data = (poll_backend_data_t *) ctx->backend_data; + if (data) { + php_poll_fd_table_cleanup(data->fd_table); + pefree(data->temp_fds, ctx->persistent); + pefree(data, ctx->persistent); + ctx->backend_data = NULL; + } +} + +static zend_result poll_backend_add(php_poll_ctx *ctx, int fd, uint32_t events, void *user_data) +{ + poll_backend_data_t *backend_data = (poll_backend_data_t *) ctx->backend_data; + + if (events & PHP_POLL_ET) { + php_poll_set_error(ctx, PHP_POLL_ERR_NOSUPPORT); + return FAILURE; + } + + if (php_poll_fd_table_find(backend_data->fd_table, fd)) { + php_poll_set_error(ctx, PHP_POLL_ERR_EXISTS); + return FAILURE; + } + + php_poll_fd_entry *entry = php_poll_fd_table_get(backend_data->fd_table, fd); + if (!entry) { + php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM); + return FAILURE; + } + + entry->events = events; + entry->data = user_data; + + return SUCCESS; +} + +static zend_result poll_backend_modify(php_poll_ctx *ctx, int fd, uint32_t events, void *user_data) +{ + poll_backend_data_t *backend_data = (poll_backend_data_t *) ctx->backend_data; + + if (events & PHP_POLL_ET) { + php_poll_set_error(ctx, PHP_POLL_ERR_NOSUPPORT); + return FAILURE; + } + + php_poll_fd_entry *entry = php_poll_fd_table_find(backend_data->fd_table, fd); + if (!entry) { + php_poll_set_error(ctx, PHP_POLL_ERR_NOTFOUND); + return FAILURE; + } + + entry->events = events; + entry->data = user_data; + + return SUCCESS; +} + +static zend_result poll_backend_remove(php_poll_ctx *ctx, int fd) +{ + poll_backend_data_t *backend_data = (poll_backend_data_t *) ctx->backend_data; + + if (!php_poll_fd_table_find(backend_data->fd_table, fd)) { + php_poll_set_error(ctx, PHP_POLL_ERR_NOTFOUND); + return FAILURE; + } + + php_poll_fd_table_remove(backend_data->fd_table, fd); + return SUCCESS; +} + +/* Context for building struct pollfd array */ +typedef struct { + struct pollfd *fds; + int index; +} poll_build_context; + +/* Callback to build struct pollfd array from fd_table */ +static bool poll_build_fds_callback(int fd, php_poll_fd_entry *entry, void *user_data) +{ + poll_build_context *ctx = (poll_build_context *) user_data; + + ctx->fds[ctx->index].fd = fd; + ctx->fds[ctx->index].events + = poll_events_to_native(entry->events & ~(PHP_POLL_ET | PHP_POLL_ONESHOT)); + ctx->fds[ctx->index].revents = 0; + ctx->index++; + + return true; +} + +static void php_poll_msleep(int timeout_ms) +{ + if (timeout_ms <= 0) { + return; + } + + struct timespec ts; + ts.tv_sec = timeout_ms / 1000; + ts.tv_nsec = (timeout_ms % 1000) * 1000000; + nanosleep(&ts, NULL); +} + +static int poll_backend_wait(php_poll_ctx *ctx, php_poll_event *events, int max_events, int timeout) +{ + poll_backend_data_t *backend_data = (poll_backend_data_t *) ctx->backend_data; + + int fd_count = php_poll_fd_table_count(backend_data->fd_table); + if (fd_count == 0) { + if (timeout > 0) { + php_poll_msleep(timeout); + } + return 0; + } + + /* Ensure temp_fds array is large enough */ + if (fd_count > backend_data->temp_fds_capacity) { + struct pollfd *new_fds = php_poll_realloc( + backend_data->temp_fds, fd_count * sizeof(struct pollfd), ctx->persistent); + if (!new_fds) { + php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM); + return -1; + } + backend_data->temp_fds = new_fds; + backend_data->temp_fds_capacity = fd_count; + } + + /* Build struct pollfd array from fd_table */ + poll_build_context build_ctx = { .fds = backend_data->temp_fds, .index = 0 }; + php_poll_fd_table_foreach(backend_data->fd_table, poll_build_fds_callback, &build_ctx); + + int nfds = poll(backend_data->temp_fds, fd_count, timeout); + + if (nfds <= 0) { + return nfds; /* Return 0 for timeout, -1 for error */ + } + + /* Process results - iterate through struct pollfd array directly */ + int event_count = 0; + for (int i = 0; i < fd_count && event_count < max_events; i++) { + struct pollfd *pfd = &backend_data->temp_fds[i]; + + if (pfd->revents != 0) { + php_poll_fd_entry *entry = php_poll_fd_table_find(backend_data->fd_table, pfd->fd); + if (entry) { + /* Handle POLLNVAL by automatically removing the invalid FD */ + if (pfd->revents & POLLNVAL) { + php_poll_fd_table_remove(backend_data->fd_table, pfd->fd); + continue; /* Don't report this event */ + } + + events[event_count].fd = pfd->fd; + events[event_count].events = entry->events; + events[event_count].revents = poll_events_from_native(pfd->revents); + events[event_count].data = entry->data; + event_count++; + } + } + } + + /* Handle oneshot removals */ + for (int i = 0; i < event_count; i++) { + php_poll_fd_entry *entry = php_poll_fd_table_find(backend_data->fd_table, events[i].fd); + if (entry && (entry->events & PHP_POLL_ONESHOT) && events[i].revents != 0) { + php_poll_fd_table_remove(backend_data->fd_table, events[i].fd); + } + } + + return event_count; +} + +static bool poll_backend_is_available(void) +{ + return true; +} + +static int poll_backend_get_suitable_max_events(php_poll_ctx *ctx) +{ + poll_backend_data_t *backend_data = (poll_backend_data_t *) ctx->backend_data; + + if (UNEXPECTED(!backend_data || !backend_data->fd_table)) { + return -1; + } + + int active_fds = php_poll_fd_table_count(backend_data->fd_table); + return active_fds == 0 ? 1 : active_fds; +} + +const php_poll_backend_ops php_poll_backend_poll_ops = { + .type = PHP_POLL_BACKEND_POLL, + .name = "poll", + .init = poll_backend_init, + .cleanup = poll_backend_cleanup, + .add = poll_backend_add, + .modify = poll_backend_modify, + .remove = poll_backend_remove, + .wait = poll_backend_wait, + .is_available = poll_backend_is_available, + .get_suitable_max_events = poll_backend_get_suitable_max_events, + .supports_et = false, +}; + +#endif diff --git a/main/poll/poll_backend_wsapoll.c b/main/poll/poll_backend_wsapoll.c new file mode 100644 index 0000000000000..3b4ae3bafb3fb --- /dev/null +++ b/main/poll/poll_backend_wsapoll.c @@ -0,0 +1,328 @@ +/* ++----------------------------------------------------------------------+ +| Copyright © The PHP Group and Contributors. | ++----------------------------------------------------------------------+ +| This source file is subject to the Modified BSD License that is | +| bundled with this package in the file LICENSE, and is available | +| through the World Wide Web at . | +| | +| SPDX-License-Identifier: BSD-3-Clause | ++----------------------------------------------------------------------+ +| Authors: Jakub Zelenka | ++----------------------------------------------------------------------+ +*/ + +#include "php_poll_internal.h" + +#ifdef PHP_WIN32 + +#include +#include + +typedef struct { + php_poll_fd_table *fd_table; + WSAPOLLFD *temp_fds; + int temp_fds_capacity; +} wsapoll_backend_data_t; + +static uint32_t wsapoll_events_to_native(uint32_t events) +{ + uint32_t native = 0; + if (events & PHP_POLL_READ) { + native |= POLLRDNORM | POLLRDBAND; + } + if (events & PHP_POLL_WRITE) { + native |= POLLWRNORM; + } + if (events & PHP_POLL_ERROR) { + native |= POLLERR; + } + if (events & PHP_POLL_HUP) { + native |= POLLHUP; + } + return native; +} + +static uint32_t wsapoll_events_from_native(uint32_t native) +{ + uint32_t events = 0; + if (native & (POLLRDNORM | POLLRDBAND)) { + events |= PHP_POLL_READ; + } + if (native & POLLWRNORM) { + events |= PHP_POLL_WRITE; + } + if (native & POLLERR) { + events |= PHP_POLL_ERROR; + } + if (native & POLLHUP) { + events |= PHP_POLL_HUP; + } + if (native & POLLNVAL) { + events |= PHP_POLL_ERROR; /* Map invalid socket to error */ + } + return events; +} + +static zend_result wsapoll_backend_init(php_poll_ctx *ctx) +{ + wsapoll_backend_data_t *data + = php_poll_calloc(1, sizeof(wsapoll_backend_data_t), ctx->persistent); + if (!data) { + php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM); + return FAILURE; + } + + int initial_capacity = ctx->max_events_hint > 0 ? ctx->max_events_hint : 64; + + data->fd_table = php_poll_fd_table_init(initial_capacity, ctx->persistent); + if (!data->fd_table) { + pefree(data, ctx->persistent); + php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM); + return FAILURE; + } + + data->temp_fds = php_poll_calloc(initial_capacity, sizeof(WSAPOLLFD), ctx->persistent); + if (!data->temp_fds) { + php_poll_fd_table_cleanup(data->fd_table); + pefree(data, ctx->persistent); + php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM); + return FAILURE; + } + data->temp_fds_capacity = initial_capacity; + + ctx->backend_data = data; + return SUCCESS; +} + +static void wsapoll_backend_cleanup(php_poll_ctx *ctx) +{ + wsapoll_backend_data_t *data = (wsapoll_backend_data_t *) ctx->backend_data; + if (data) { + php_poll_fd_table_cleanup(data->fd_table); + pefree(data->temp_fds, ctx->persistent); + pefree(data, ctx->persistent); + ctx->backend_data = NULL; + } +} + +static zend_result wsapoll_backend_add(php_poll_ctx *ctx, int fd, uint32_t events, void *user_data) +{ + wsapoll_backend_data_t *backend_data = (wsapoll_backend_data_t *) ctx->backend_data; + + if (events & PHP_POLL_ET) { + php_poll_set_error(ctx, PHP_POLL_ERR_NOSUPPORT); + return FAILURE; + } + + if (php_poll_fd_table_find(backend_data->fd_table, fd)) { + php_poll_set_error(ctx, PHP_POLL_ERR_EXISTS); + return FAILURE; + } + + php_poll_fd_entry *entry = php_poll_fd_table_get(backend_data->fd_table, fd); + if (!entry) { + php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM); + return FAILURE; + } + + entry->events = events; + entry->data = user_data; + + return SUCCESS; +} + +static zend_result wsapoll_backend_modify( + php_poll_ctx *ctx, int fd, uint32_t events, void *user_data) +{ + wsapoll_backend_data_t *backend_data = (wsapoll_backend_data_t *) ctx->backend_data; + + if (events & PHP_POLL_ET) { + php_poll_set_error(ctx, PHP_POLL_ERR_NOSUPPORT); + return FAILURE; + } + + php_poll_fd_entry *entry = php_poll_fd_table_find(backend_data->fd_table, fd); + if (!entry) { + php_poll_set_error(ctx, PHP_POLL_ERR_NOTFOUND); + return FAILURE; + } + + entry->events = events; + entry->data = user_data; + + return SUCCESS; +} + +static zend_result wsapoll_backend_remove(php_poll_ctx *ctx, int fd) +{ + wsapoll_backend_data_t *backend_data = (wsapoll_backend_data_t *) ctx->backend_data; + + if (!php_poll_fd_table_find(backend_data->fd_table, fd)) { + php_poll_set_error(ctx, PHP_POLL_ERR_NOTFOUND); + return FAILURE; + } + + php_poll_fd_table_remove(backend_data->fd_table, fd); + return SUCCESS; +} + +/* Context for building WSAPOLLFD array */ +typedef struct { + WSAPOLLFD *fds; + int index; +} wsapoll_build_context; + +/* Callback to build WSAPOLLFD array from fd_table */ +static bool wsapoll_build_fds_callback(int fd, php_poll_fd_entry *entry, void *user_data) +{ + wsapoll_build_context *ctx = (wsapoll_build_context *) user_data; + + ctx->fds[ctx->index].fd = (SOCKET) fd; + ctx->fds[ctx->index].events + = (SHORT) wsapoll_events_to_native(entry->events & ~(PHP_POLL_ET | PHP_POLL_ONESHOT)); + ctx->fds[ctx->index].revents = 0; + ctx->index++; + + return true; +} + +static int wsapoll_backend_wait( + php_poll_ctx *ctx, php_poll_event *events, int max_events, int timeout) +{ + wsapoll_backend_data_t *backend_data = (wsapoll_backend_data_t *) ctx->backend_data; + + int fd_count = php_poll_fd_table_count(backend_data->fd_table); + if (fd_count == 0) { + if (timeout > 0) { + Sleep(timeout); + } + return 0; + } + + /* Ensure temp_fds array is large enough */ + if (fd_count > backend_data->temp_fds_capacity) { + WSAPOLLFD *new_fds = php_poll_realloc( + backend_data->temp_fds, fd_count * sizeof(WSAPOLLFD), ctx->persistent); + if (!new_fds) { + php_poll_set_error(ctx, PHP_POLL_ERR_NOMEM); + return -1; + } + backend_data->temp_fds = new_fds; + backend_data->temp_fds_capacity = fd_count; + } + + /* Build WSAPOLLFD array from fd_table */ + wsapoll_build_context build_ctx = { .fds = backend_data->temp_fds, .index = 0 }; + php_poll_fd_table_foreach(backend_data->fd_table, wsapoll_build_fds_callback, &build_ctx); + + /* Call WSAPoll */ + int nfds = WSAPoll(backend_data->temp_fds, fd_count, timeout); + + if (nfds == SOCKET_ERROR) { + /* WSAPoll specific error handling */ + int wsa_error = WSAGetLastError(); + php_poll_error error_code; + + switch (wsa_error) { + case WSAENOTSOCK: + /* Special case: all sockets in array are invalid + * WSAPoll fails entirely, but we should clean up and return 0 + * This differs from Unix poll() which would report POLLNVAL per socket */ + + /* Remove all invalid sockets from fd_table */ + for (int i = 0; i < fd_count; i++) { + int fd = (int) backend_data->temp_fds[i].fd; + php_poll_fd_table_remove(backend_data->fd_table, fd); + } + return 0; + case WSAENOBUFS: + error_code = PHP_POLL_ERR_NOMEM; + break; + case WSAEINVAL: + case WSAEFAULT: + error_code = PHP_POLL_ERR_INVALID; + break; + default: + error_code = PHP_POLL_ERR_SYSTEM; + break; + } + + php_poll_set_error(ctx, error_code); + return -1; + } + + if (nfds == 0) { + return 0; /* Timeout */ + } + + int event_count = 0; + for (int i = 0; i < fd_count && event_count < max_events; i++) { + WSAPOLLFD *pfd = &backend_data->temp_fds[i]; + + if (pfd->revents != 0) { + int fd = (int) pfd->fd; + php_poll_fd_entry *entry = php_poll_fd_table_find(backend_data->fd_table, fd); + if (entry) { + /* WSAPoll-specific handling of POLLNVAL */ + if (pfd->revents & POLLNVAL) { + php_poll_fd_table_remove(backend_data->fd_table, fd); + continue; /* Do not report this event */ + } + + /* Convert WSAPoll events to PHP poll events */ + uint32_t converted_events = wsapoll_events_from_native(pfd->revents); + + events[event_count].fd = fd; + events[event_count].events = entry->events; + events[event_count].revents = converted_events; + events[event_count].data = entry->data; + event_count++; + } + } + } + + /* Handle oneshot removals */ + for (int i = 0; i < event_count; i++) { + php_poll_fd_entry *entry = php_poll_fd_table_find(backend_data->fd_table, events[i].fd); + if (entry && (entry->events & PHP_POLL_ONESHOT) && events[i].revents != 0) { + php_poll_fd_table_remove(backend_data->fd_table, events[i].fd); + } + } + + return event_count; +} + +static bool wsapoll_backend_is_available(void) +{ + /* Always available on the currently supported Windows versions. */ + return true; +} + +static int wsapoll_backend_get_suitable_max_events(php_poll_ctx *ctx) +{ + wsapoll_backend_data_t *backend_data = (wsapoll_backend_data_t *) ctx->backend_data; + + if (UNEXPECTED(!backend_data || !backend_data->fd_table)) { + return -1; + } + + int active_fds = php_poll_fd_table_count(backend_data->fd_table); + return active_fds == 0 ? 1 : active_fds; +} + +const php_poll_backend_ops php_poll_backend_wsapoll_ops = { + .type = PHP_POLL_BACKEND_WSAPOLL, + .name = "wsapoll", + .init = wsapoll_backend_init, + .cleanup = wsapoll_backend_cleanup, + .add = wsapoll_backend_add, + .modify = wsapoll_backend_modify, + .remove = wsapoll_backend_remove, + .wait = wsapoll_backend_wait, + .is_available = wsapoll_backend_is_available, + .get_suitable_max_events = wsapoll_backend_get_suitable_max_events, + .supports_et = false, +}; + +#endif /* PHP_WIN32 */ diff --git a/main/poll/poll_core.c b/main/poll/poll_core.c new file mode 100644 index 0000000000000..dbb46a2a67a26 --- /dev/null +++ b/main/poll/poll_core.c @@ -0,0 +1,406 @@ +/* + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Authors: Jakub Zelenka | + +----------------------------------------------------------------------+ +*/ + +#include "php_poll_internal.h" + +/* Backend registry */ +static const php_poll_backend_ops *registered_backends[8]; +static int num_registered_backends = 0; + +/* Forward declarations for backend ops */ + +#ifdef HAVE_EPOLL +extern const php_poll_backend_ops php_poll_backend_epoll_ops; +#endif +#ifdef HAVE_KQUEUE +extern const php_poll_backend_ops php_poll_backend_kqueue_ops; +#endif +#ifdef HAVE_EVENT_PORTS +extern const php_poll_backend_ops php_poll_backend_eventport_ops; +#endif +#ifdef PHP_WIN32 +extern const php_poll_backend_ops php_poll_backend_wsapoll_ops; +#else +extern const php_poll_backend_ops php_poll_backend_poll_ops; +#endif + +/* Register all available backends */ +PHPAPI void php_poll_register_backends(void) +{ + num_registered_backends = 0; + +#ifdef HAVE_EVENT_PORTS + /* Event Ports are preferred on Solaris */ + if (php_poll_backend_eventport_ops.is_available()) { + registered_backends[num_registered_backends++] = &php_poll_backend_eventport_ops; + } +#endif + +#ifdef HAVE_KQUEUE + if (php_poll_backend_kqueue_ops.is_available()) { + registered_backends[num_registered_backends++] = &php_poll_backend_kqueue_ops; + } +#endif + +#ifdef HAVE_EPOLL + if (php_poll_backend_epoll_ops.is_available()) { + registered_backends[num_registered_backends++] = &php_poll_backend_epoll_ops; + } +#endif + +#ifdef PHP_WIN32 + registered_backends[num_registered_backends++] = &php_poll_backend_wsapoll_ops; +#else + registered_backends[num_registered_backends++] = &php_poll_backend_poll_ops; +#endif +} + +/* Get backend operations */ +static const php_poll_backend_ops *php_poll_get_backend_ops(php_poll_backend_type backend) +{ + if (backend == PHP_POLL_BACKEND_AUTO) { + /* Return the first (best) available backend */ + return num_registered_backends > 0 ? registered_backends[0] : NULL; + } + + for (int i = 0; i < num_registered_backends; i++) { + if (registered_backends[i] && registered_backends[i]->type == backend) { + return registered_backends[i]; + } + } + + return NULL; +} + +/* Get backend operations by backend name */ +static const php_poll_backend_ops *php_poll_get_backend_ops_by_name(const char *backend_name) +{ + if (!backend_name) { + return NULL; + } + + for (int i = 0; i < num_registered_backends; i++) { + if (registered_backends[i] && strcmp(registered_backends[i]->name, backend_name) == 0) { + return registered_backends[i]; + } + } + + return NULL; +} + +static php_poll_ctx *php_poll_create_context(uint32_t flags) +{ + bool persistent = flags & PHP_POLL_FLAG_PERSISTENT; + php_poll_ctx *ctx = php_poll_calloc(1, sizeof(php_poll_ctx), persistent); + if (!ctx) { + return NULL; + } + ctx->persistent = persistent; + ctx->raw_events = (flags & PHP_POLL_FLAG_RAW_EVENTS) != 0; + + return ctx; +} + +/* Create new poll context */ +PHPAPI php_poll_ctx *php_poll_create(php_poll_backend_type preferred_backend, uint32_t flags) +{ + php_poll_ctx *ctx = php_poll_create_context(flags); + if (ctx == NULL) { + return NULL; + } + + /* Get backend operations */ + ctx->backend_ops = php_poll_get_backend_ops(preferred_backend); + if (!ctx->backend_ops) { + pefree(ctx, ctx->persistent); + return NULL; + } + ctx->backend_type = preferred_backend; + + return ctx; +} + +/* Create new poll context */ +PHPAPI php_poll_ctx *php_poll_create_by_name(const char *preferred_backend, uint32_t flags) +{ + php_poll_ctx *ctx = php_poll_create_context(flags); + if (ctx == NULL) { + return NULL; + } + + /* Get backend operations */ + ctx->backend_ops = php_poll_get_backend_ops_by_name(preferred_backend); + if (!ctx->backend_ops) { + pefree(ctx, ctx->persistent); + return NULL; + } + ctx->backend_type = ctx->backend_ops->type; + + return ctx; +} + +/* Set event capacity hint (optional optimization) */ +PHPAPI zend_result php_poll_set_max_events_hint(php_poll_ctx *ctx, int max_events) +{ + if (UNEXPECTED(!ctx || max_events <= 0)) { + php_poll_set_error(ctx, PHP_POLL_ERR_INVALID); + return FAILURE; + } + + if (UNEXPECTED(ctx->initialized)) { + php_poll_set_error(ctx, PHP_POLL_ERR_INVALID); + return FAILURE; /* Cannot change after init */ + } + + ctx->max_events_hint = max_events; + return SUCCESS; +} + +/* Initialize poll context */ +PHPAPI zend_result php_poll_init(php_poll_ctx *ctx) +{ + if (UNEXPECTED(!ctx)) { + return FAILURE; + } + + if (UNEXPECTED(ctx->initialized)) { + return SUCCESS; + } + + /* Initialize backend - can use ctx->max_events_hint if helpful */ + if (EXPECTED(ctx->backend_ops->init(ctx) == SUCCESS)) { + ctx->initialized = true; + return SUCCESS; + } + + php_poll_set_current_errno_error(ctx); + return FAILURE; +} + +/* Destroy poll context */ +PHPAPI void php_poll_destroy(php_poll_ctx *ctx) +{ + if (!ctx) { + return; + } + + if (ctx->backend_ops && ctx->backend_ops->cleanup) { + ctx->backend_ops->cleanup(ctx); + } + + pefree(ctx, ctx->persistent); +} + +/* Add file descriptor */ +PHPAPI zend_result php_poll_add(php_poll_ctx *ctx, int fd, uint32_t events, void *data) +{ + if (UNEXPECTED(!ctx || !ctx->initialized || fd < 0)) { + php_poll_set_error(ctx, PHP_POLL_ERR_INVALID); + return FAILURE; + } + + /* Delegate to backend - it handles all validation and tracking */ + if (EXPECTED(ctx->backend_ops->add(ctx, fd, events, data) == SUCCESS)) { + return SUCCESS; + } + + php_poll_set_current_errno_error(ctx); + return FAILURE; +} + +/* Modify file descriptor */ +PHPAPI zend_result php_poll_modify(php_poll_ctx *ctx, int fd, uint32_t events, void *data) +{ + if (UNEXPECTED(!ctx || !ctx->initialized || fd < 0)) { + php_poll_set_error(ctx, PHP_POLL_ERR_INVALID); + return FAILURE; + } + + /* Delegate to backend - it handles validation */ + if (EXPECTED(ctx->backend_ops->modify(ctx, fd, events, data) == SUCCESS)) { + return SUCCESS; + } + + php_poll_set_current_errno_error(ctx); + return FAILURE; +} + +/* Remove file descriptor */ +PHPAPI zend_result php_poll_remove(php_poll_ctx *ctx, int fd) +{ + if (UNEXPECTED(!ctx || !ctx->initialized || fd < 0)) { + php_poll_set_error(ctx, PHP_POLL_ERR_INVALID); + return FAILURE; + } + + /* Delegate to backend - it handles validation */ + if (EXPECTED(ctx->backend_ops->remove(ctx, fd) == SUCCESS)) { + return SUCCESS; + } + + php_poll_set_current_errno_error(ctx); + return FAILURE; +} + +/* Wait for events */ +PHPAPI int php_poll_wait(php_poll_ctx *ctx, php_poll_event *events, int max_events, int timeout) +{ + if (UNEXPECTED(!ctx || !ctx->initialized || !events || max_events <= 0)) { + php_poll_set_error(ctx, PHP_POLL_ERR_INVALID); + return -1; + } + + /* Delegate to backend - it handles everything including ET simulation if needed */ + int nfds = ctx->backend_ops->wait(ctx, events, max_events, timeout); + + if (UNEXPECTED(nfds < 0)) { + php_poll_set_current_errno_error(ctx); + } + + return nfds; +} + +/* Get backend name */ +PHPAPI const char *php_poll_backend_name(php_poll_ctx *ctx) +{ + return ctx && ctx->backend_ops ? ctx->backend_ops->name : "unknown"; +} + +/* Get backend type */ +PHPAPI php_poll_backend_type php_poll_get_backend_type(php_poll_ctx *ctx) +{ + return ctx ? ctx->backend_type : PHP_POLL_BACKEND_AUTO; +} + +/* Check edge-triggering support */ +PHPAPI bool php_poll_supports_et(php_poll_ctx *ctx) +{ + return ctx && ctx->backend_ops && ctx->backend_ops->supports_et; +} + +/* Get suitable max_events for backend */ +PHPAPI int php_poll_get_suitable_max_events(php_poll_ctx *ctx) +{ + if (UNEXPECTED(!ctx || !ctx->backend_ops)) { + return -1; + } + + return ctx->backend_ops->get_suitable_max_events(ctx); +} + +/* Error retrieval */ +PHPAPI php_poll_error php_poll_get_error(php_poll_ctx *ctx) +{ + return ctx ? ctx->last_error : PHP_POLL_ERR_INVALID; +} + +/* Errno to php_poll_error mapping helper */ +php_poll_error php_poll_errno_to_error(int err) +{ + switch (err) { + case 0: + return PHP_POLL_ERR_NONE; + + case ENOMEM: + return PHP_POLL_ERR_NOMEM; + + case EINVAL: + case EBADF: + return PHP_POLL_ERR_INVALID; + + case EEXIST: + return PHP_POLL_ERR_EXISTS; + + case ENOENT: + return PHP_POLL_ERR_NOTFOUND; + +#ifdef ETIME + case ETIME: +#endif +#ifdef ETIMEDOUT + case ETIMEDOUT: +#endif + return PHP_POLL_ERR_TIMEOUT; + + case EINTR: + return PHP_POLL_ERR_INTERRUPTED; + + case EACCES: +#ifdef EPERM + case EPERM: +#endif + return PHP_POLL_ERR_PERMISSION; + +#ifdef EMFILE + case EMFILE: +#endif +#ifdef ENFILE + case ENFILE: +#endif + return PHP_POLL_ERR_TOOBIG; + + case EAGAIN: +#if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN + case EWOULDBLOCK: +#endif + return PHP_POLL_ERR_AGAIN; + +#ifdef ENOSYS + case ENOSYS: +#endif +#if ENOTSUP + case ENOTSUP: +#endif +#if defined(EOPNOTSUPP) && EOPNOTSUPP != ENOTSUP + case EOPNOTSUPP: +#endif + return PHP_POLL_ERR_NOSUPPORT; + + default: + return PHP_POLL_ERR_SYSTEM; + } +} + +/* Get human-readable error description */ +PHPAPI const char *php_poll_error_string(php_poll_error error) +{ + switch (error) { + case PHP_POLL_ERR_NONE: + return "No error"; + case PHP_POLL_ERR_SYSTEM: + return "System error"; + case PHP_POLL_ERR_NOMEM: + return "Out of memory"; + case PHP_POLL_ERR_INVALID: + return "Invalid argument"; + case PHP_POLL_ERR_EXISTS: + return "File descriptor already exists"; + case PHP_POLL_ERR_NOTFOUND: + return "File descriptor not found"; + case PHP_POLL_ERR_TIMEOUT: + return "Operation timed out"; + case PHP_POLL_ERR_INTERRUPTED: + return "Operation interrupted"; + case PHP_POLL_ERR_PERMISSION: + return "Permission denied"; + case PHP_POLL_ERR_TOOBIG: + return "Too many open files"; + case PHP_POLL_ERR_AGAIN: + return "Resource temporarily unavailable"; + case PHP_POLL_ERR_NOSUPPORT: + return "Operation not supported"; + default: + return "Unknown error"; + } +} diff --git a/main/poll/poll_fd_table.c b/main/poll/poll_fd_table.c new file mode 100644 index 0000000000000..841bfcca8912f --- /dev/null +++ b/main/poll/poll_fd_table.c @@ -0,0 +1,113 @@ +/* + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Authors: Jakub Zelenka | + +----------------------------------------------------------------------+ +*/ + +#include "php_poll_internal.h" + +php_poll_fd_table *php_poll_fd_table_init(int initial_capacity, bool persistent) +{ + php_poll_fd_table *table = php_poll_calloc(1, sizeof(php_poll_fd_table), persistent); + if (!table) { + return NULL; + } + + if (initial_capacity <= 0) { + initial_capacity = 64; + } + + _zend_hash_init(&table->entries_ht, initial_capacity, NULL, persistent); + table->persistent = persistent; + + return table; +} + +void php_poll_fd_table_cleanup(php_poll_fd_table *table) +{ + if (table) { + zval *zv; + + ZEND_HASH_FOREACH_VAL(&table->entries_ht, zv) + { + php_poll_fd_entry *entry = Z_PTR_P(zv); + pefree(entry, table->persistent); + } + ZEND_HASH_FOREACH_END(); + + zend_hash_destroy(&table->entries_ht); + pefree(table, table->persistent); + } +} + +php_poll_fd_entry *php_poll_fd_table_find(php_poll_fd_table *table, int fd) +{ + zval *zv = zend_hash_index_find(&table->entries_ht, (zend_ulong) fd); + return zv ? Z_PTR_P(zv) : NULL; +} + +php_poll_fd_entry *php_poll_fd_table_get(php_poll_fd_table *table, int fd) +{ + php_poll_fd_entry *entry = php_poll_fd_table_find(table, fd); + if (entry) { + return entry; + } + + entry = php_poll_calloc(1, sizeof(php_poll_fd_entry), table->persistent); + if (!entry) { + return NULL; + } + + entry->fd = fd; + entry->active = true; + entry->events = 0; + entry->data = NULL; + entry->last_revents = 0; + + zval zv; + ZVAL_PTR(&zv, entry); + if (!zend_hash_index_add(&table->entries_ht, (zend_ulong) fd, &zv)) { + pefree(entry, table->persistent); + return NULL; + } + + return entry; +} + +void php_poll_fd_table_remove(php_poll_fd_table *table, int fd) +{ + zval *zv = zend_hash_index_find(&table->entries_ht, (zend_ulong) fd); + if (zv) { + php_poll_fd_entry *entry = Z_PTR_P(zv); + pefree(entry, table->persistent); + zend_hash_index_del(&table->entries_ht, (zend_ulong) fd); + } +} + +/* Helper function for backends that need to iterate over all entries */ +typedef bool (*php_poll_fd_iterator_func_t)(int fd, php_poll_fd_entry *entry, void *user_data); + +/* Iterate over all active FD entries */ +void php_poll_fd_table_foreach( + php_poll_fd_table *table, php_poll_fd_iterator_func_t callback, void *user_data) +{ + zend_ulong fd; + zval *zv; + + ZEND_HASH_FOREACH_NUM_KEY_VAL(&table->entries_ht, fd, zv) + { + php_poll_fd_entry *entry = Z_PTR_P(zv); + if (entry->active && !callback((int) fd, entry, user_data)) { + break; /* Callback returned false, stop iteration */ + } + } + ZEND_HASH_FOREACH_END(); +} diff --git a/main/poll/poll_handle.c b/main/poll/poll_handle.c new file mode 100644 index 0000000000000..0c0628ac49dca --- /dev/null +++ b/main/poll/poll_handle.c @@ -0,0 +1,97 @@ +/* + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Authors: Jakub Zelenka | + +----------------------------------------------------------------------+ +*/ + +#include "php_poll.h" +#include "zend_exceptions.h" + +/* Default get_fd implementation - calls PHP method */ +static php_socket_t php_poll_handle_default_get_fd(php_poll_handle_object *handle) +{ + zval retval; + zval obj; + zval func_name; + + ZVAL_OBJ(&obj, &handle->std); + + /* Prepare function name as zval */ + ZVAL_STRING(&func_name, "getFileDescriptor"); + + /* Call getFileDescriptor() method */ + if (EXPECTED(call_user_function(NULL, &obj, &func_name, &retval, 0, NULL) == SUCCESS)) { + if (Z_TYPE(retval) == IS_LONG) { + php_socket_t fd = Z_LVAL(retval) < 0 ? SOCK_ERR : (php_socket_t) Z_LVAL(retval); + zval_ptr_dtor(&retval); + zval_ptr_dtor(&func_name); /* Clean up function name */ + return fd; + } + zval_ptr_dtor(&retval); + } + + zval_ptr_dtor(&func_name); /* Clean up function name */ + return SOCK_ERR; /* Invalid socket */ +} + +/* Default is_valid implementation - assume valid if we can get FD */ +static int php_poll_handle_default_is_valid(php_poll_handle_object *handle) +{ + return php_poll_handle_get_fd(handle) != SOCK_ERR; +} + +/* Default cleanup */ +static void php_poll_handle_default_cleanup(php_poll_handle_object *handle) +{ + /* Base implementation has no cleanup */ +} + +/* Default operations that call PHP userspace methods */ +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 }; + +/* Allocate a new poll handle object */ +PHPAPI php_poll_handle_object *php_poll_handle_object_create( + size_t obj_size, zend_class_entry *ce, php_poll_handle_ops *ops) +{ + php_poll_handle_object *intern = zend_object_alloc(obj_size, ce); + + zend_object_std_init(&intern->std, ce); + object_properties_init(&intern->std, ce); + + intern->ops = ops ? ops : &php_poll_handle_default_ops; + intern->handle_data = NULL; + + return intern; +} + +/* Free poll handle object */ +PHPAPI void php_poll_handle_object_free(zend_object *obj) +{ + php_poll_handle_object *intern = PHP_POLL_HANDLE_OBJ_FROM_ZOBJ(obj); + + if (intern->ops && intern->ops->cleanup) { + intern->ops->cleanup(intern); + } + + zend_object_std_dtor(&intern->std); +} + +/* Get file descriptor from handle using ops */ +PHPAPI php_socket_t php_poll_handle_get_fd(php_poll_handle_object *handle) +{ + if (!handle || !handle->ops || !handle->ops->get_fd) { + return SOCK_ERR; + } + + return handle->ops->get_fd(handle); +} diff --git a/sapi/fpm/config.m4 b/sapi/fpm/config.m4 index 4d4952eee86e7..89c53a0c4d284 100644 --- a/sapi/fpm/config.m4 +++ b/sapi/fpm/config.m4 @@ -262,100 +262,16 @@ AS_VAR_IF([php_cv_have_SO_LISTENQLEN], [yes], [Define to 1 if you have 'SO_LISTENQ*'.])]) ]) -AC_DEFUN([PHP_FPM_KQUEUE], -[AC_CACHE_CHECK([for kqueue], - [php_cv_have_kqueue], - [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([dnl - #include - #include - #include - ], [dnl - int kfd; - struct kevent k; - kfd = kqueue(); - /* 0 -> STDIN_FILENO */ - EV_SET(&k, 0, EVFILT_READ , EV_ADD | EV_CLEAR, 0, 0, NULL); - (void)kfd; - ])], - [php_cv_have_kqueue=yes], - [php_cv_have_kqueue=no])]) -AS_VAR_IF([php_cv_have_kqueue], [yes], - [AC_DEFINE([HAVE_KQUEUE], [1], - [Define to 1 if system has a working 'kqueue' function.])]) -]) - -AC_DEFUN([PHP_FPM_EPOLL], -[AC_CACHE_CHECK([for epoll], - [php_cv_have_epoll], - [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([#include ], [dnl - int epollfd; - struct epoll_event e; - - epollfd = epoll_create(1); - if (epollfd < 0) { - return 1; - } - - e.events = EPOLLIN | EPOLLET; - e.data.fd = 0; - - if (epoll_ctl(epollfd, EPOLL_CTL_ADD, 0, &e) == -1) { - return 1; - } - - e.events = 0; - if (epoll_wait(epollfd, &e, 1, 1) < 0) { - return 1; - } - ])], - [php_cv_have_epoll=yes], - [php_cv_have_epoll=no])]) -AS_VAR_IF([php_cv_have_epoll], [yes], - [AC_DEFINE([HAVE_EPOLL], [1], [Define to 1 if system has a working epoll.])]) -]) - -AC_DEFUN([PHP_FPM_SELECT], -[AC_CACHE_CHECK([for select], - [php_cv_have_select], - [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([dnl - /* According to POSIX.1-2001 */ - #include - - /* According to earlier standards */ - #include - #include - #include - ], [dnl - fd_set fds; - struct timeval t; - t.tv_sec = 0; - t.tv_usec = 42; - FD_ZERO(&fds); - /* 0 -> STDIN_FILENO */ - FD_SET(0, &fds); - select(FD_SETSIZE, &fds, NULL, NULL, &t); - ])], - [php_cv_have_select=yes], - [php_cv_have_select=no])]) -AS_VAR_IF([php_cv_have_select], [yes], - [AC_DEFINE([HAVE_SELECT], [1], - [Define to 1 if system has a working 'select' function.])]) -]) - if test "$PHP_FPM" != "no"; then PHP_FPM_CLOCK PHP_FPM_TRACE PHP_FPM_BUILTIN_ATOMIC PHP_FPM_LQ - PHP_FPM_KQUEUE - PHP_FPM_EPOLL - PHP_FPM_SELECT AC_CHECK_FUNCS([clearenv setproctitle setproctitle_fast]) AC_CHECK_HEADER([priv.h], [AC_CHECK_FUNCS([setpflags])]) AC_CHECK_HEADER([sys/times.h], [AC_CHECK_FUNCS([times])]) - AC_CHECK_HEADER([port.h], [AC_CHECK_FUNCS([port_create])]) PHP_ARG_WITH([fpm-user],, [AS_HELP_STRING([[--with-fpm-user[=USER]]], diff --git a/sapi/fpm/fpm/events/port.c b/sapi/fpm/fpm/events/port.c index 73cf24c82c2c2..7731b7f97f2e9 100644 --- a/sapi/fpm/fpm/events/port.c +++ b/sapi/fpm/fpm/events/port.c @@ -19,7 +19,7 @@ #include "../fpm.h" #include "../zlog.h" -#ifdef HAVE_PORT_CREATE +#ifdef HAVE_EVENT_PORTS #include #include @@ -45,19 +45,19 @@ port_event_t *events = NULL; int nevents = 0; static int pfd = -1; -#endif /* HAVE_PORT_CREATE */ +#endif /* HAVE_EVENT_PORTS */ struct fpm_event_module_s *fpm_event_port_module(void) /* {{{ */ { -#ifdef HAVE_PORT_CREATE +#ifdef HAVE_EVENT_PORTS return &port_module; #else return NULL; -#endif /* HAVE_PORT_CREATE */ +#endif /* HAVE_EVENT_PORTS */ } /* }}} */ -#ifdef HAVE_PORT_CREATE +#ifdef HAVE_EVENT_PORTS /* * Init the module @@ -196,4 +196,4 @@ static int fpm_event_port_remove(struct fpm_event_s *ev) /* {{{ */ } /* }}} */ -#endif /* HAVE_PORT_CREATE */ +#endif /* HAVE_EVENT_PORTS */ diff --git a/win32/build/config.w32 b/win32/build/config.w32 index 403f0aa6efbfe..9f4fbe074a554 100644 --- a/win32/build/config.w32 +++ b/win32/build/config.w32 @@ -298,6 +298,9 @@ AC_DEFINE('HAVE_STRNLEN', 1); AC_DEFINE('ZEND_CHECK_STACK_LIMIT', 1) +ADD_SOURCES("main/poll", "poll_backend_wsapoll.c poll_core.c poll_fd_table.c poll_handle.c"); +ADD_FLAG("CFLAGS_BD_MAIN_POLL", "/D ZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); + ADD_SOURCES("main/streams", "streams.c cast.c memory.c filter.c plain_wrapper.c \ userspace.c transports.c xp_socket.c mmap.c glob_wrapper.c"); ADD_FLAG("CFLAGS_BD_MAIN_STREAMS", "/D ZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); @@ -309,7 +312,7 @@ ADD_SOURCES("win32", "dllmain.c readdir.c \ ADD_FLAG("CFLAGS_BD_WIN32", "/D ZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); -PHP_INSTALL_HEADERS("", "Zend/ TSRM/ main/ main/streams/ win32/"); +PHP_INSTALL_HEADERS("", "Zend/ TSRM/ main/ main/poll/ main/streams/ win32/"); PHP_INSTALL_HEADERS("Zend/Optimizer", "zend_call_graph.h zend_cfg.h zend_dfg.h zend_dump.h zend_func_info.h zend_inference.h zend_optimizer.h zend_ssa.h zend_worklist.h"); STDOUT.WriteBlankLines(1);