diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d874ab6 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,38 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + build: + name: PHP ${{ matrix.php }} - ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + php: ['8.1', '8.2', '8.3', '8.4', '8.5'] + + steps: + - uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + tools: none + + - name: Build extension + run: | + phpize + ./configure --enable-eventloop + make -j$(nproc 2>/dev/null || sysctl -n hw.ncpu) + + - name: Run tests + run: make test TESTS="-q --show-diff" + + - name: Verify extension loads + run: php -d extension=modules/eventloop.so -m | grep eventloop diff --git a/eventloop.c b/eventloop.c index 5fd87f1..7f52283 100644 --- a/eventloop.c +++ b/eventloop.c @@ -23,6 +23,7 @@ # include #endif +#include "php_eventloop_compat.h" #include "eventloop_arginfo.h" ZEND_DECLARE_MODULE_GLOBALS(eventloop) diff --git a/eventloop_cb.c b/eventloop_cb.c index 1b69ee0..c76ea8f 100644 --- a/eventloop_cb.c +++ b/eventloop_cb.c @@ -9,9 +9,9 @@ static zend_string *eventloop_generate_id(void) { char buf[24]; - int len; - len = snprintf(buf, sizeof(buf), "eL%" PRIx64, EVENTLOOP_G(next_id)++); + int len = snprintf(buf, sizeof(buf), "eL%" PRIx64, EVENTLOOP_G(next_id)++); + return zend_string_init(buf, len, 0); } diff --git a/eventloop_suspension.c b/eventloop_suspension.c index 9021f3b..52a313e 100644 --- a/eventloop_suspension.c +++ b/eventloop_suspension.c @@ -12,6 +12,7 @@ #ifdef HAVE_FIBERS #include "zend_exceptions.h" #include "zend_enum.h" +#include "php_eventloop_compat.h" #include "eventloop_arginfo.h" typedef struct _eventloop_suspension_obj { @@ -41,6 +42,8 @@ static zend_object *suspension_create_object(zend_class_entry *ce) zend_object_std_init(&suspension->std, ce); object_properties_init(&suspension->std, ce); + suspension->std.handlers = &suspension_object_handlers; + ZVAL_NULL(&suspension->fiber_zv); suspension->pending = false; suspension->suspended = false; @@ -207,7 +210,7 @@ void eventloop_suspension_init(void) { eventloop_suspension_ce = register_class_EventLoop_Suspension(); eventloop_suspension_ce->create_object = suspension_create_object; - eventloop_suspension_ce->default_object_handlers = &suspension_object_handlers; + EVENTLOOP_SET_DEFAULT_HANDLERS(eventloop_suspension_ce, &suspension_object_handlers); memcpy(&suspension_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); diff --git a/php_eventloop_compat.h b/php_eventloop_compat.h new file mode 100644 index 0000000..16d69d6 --- /dev/null +++ b/php_eventloop_compat.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2026 Aleksandr Cherednikov + * Licensed under the MIT License. See LICENSE for details. + */ + +#ifndef PHP_EVENTLOOP_COMPAT_H +#define PHP_EVENTLOOP_COMPAT_H + +#include "php.h" +#include "zend_API.h" + +/* + * Compatibility shims for building across PHP 8.1 – 8.5+. + * + * The extension targets PHP >= 8.1 (Fibers). Internal Zend APIs evolved + * between releases, so we polyfill the newer signatures here and let + * each call-site use a single, modern spelling. + */ + +/* ------------------------------------------------------------------ */ +/* zend_register_internal_class_with_flags() — added in PHP 8.4 */ +/* In 8.1-8.3 we register + set flags manually. */ +/* ------------------------------------------------------------------ */ +#if PHP_VERSION_ID < 80400 +static inline zend_class_entry *zend_register_internal_class_with_flags( + zend_class_entry *ce, zend_class_entry *parent, uint32_t flags) +{ + zend_class_entry *registered = zend_register_internal_class_ex(ce, parent); + registered->ce_flags |= flags; + return registered; +} +#endif + +/* ------------------------------------------------------------------ */ +/* ce->default_object_handlers — added in PHP 8.3 */ +/* ------------------------------------------------------------------ */ +#if PHP_VERSION_ID >= 80300 +# define EVENTLOOP_SET_DEFAULT_HANDLERS(ce, h) \ + (ce)->default_object_handlers = (h) +#else +# define EVENTLOOP_SET_DEFAULT_HANDLERS(ce, h) \ + /* not available before 8.3 — handlers set in create_object */ +#endif + +/* ------------------------------------------------------------------ */ +/* Fiber suspend / resume internal API */ +/* */ +/* PHP 8.4+ exposes zend_fiber_suspend() / zend_fiber_resume() with a */ +/* three-arg signature. Earlier versions do not export these symbols, */ +/* so we call the userland Fiber::suspend() / $fiber->resume() via */ +/* zend_call_method, which performs the same context switch. */ +/* ------------------------------------------------------------------ */ +#include "zend_fibers.h" +#include "zend_interfaces.h" + +#if PHP_VERSION_ID < 80400 + +static inline void eventloop_compat_fiber_suspend( + zend_fiber *fiber, zval *value, zval *return_value) +{ + (void)fiber; /* Fiber::suspend() operates on the active fiber */ + if (value && Z_TYPE_P(value) != IS_NULL) { + zend_call_method_with_1_params(NULL, zend_ce_fiber, NULL, "suspend", return_value, value); + } else { + zend_call_method_with_0_params(NULL, zend_ce_fiber, NULL, "suspend", return_value); + } +} + +static inline void eventloop_compat_fiber_resume( + zend_fiber *fiber, zval *value, zval *error) +{ + zval fiber_zv, retval; + (void)error; + ZVAL_OBJ(&fiber_zv, &fiber->std); + if (value && Z_TYPE_P(value) != IS_NULL) { + zend_call_method_with_1_params(&fiber_zv, zend_ce_fiber, NULL, "resume", &retval, value); + } else { + zend_call_method_with_0_params(&fiber_zv, zend_ce_fiber, NULL, "resume", &retval); + } + zval_ptr_dtor(&retval); +} + +# define zend_fiber_suspend(f, v, r) eventloop_compat_fiber_suspend((f), (v), (r)) +# define zend_fiber_resume(f, v, e) eventloop_compat_fiber_resume((f), (v), (e)) + +#endif /* PHP_VERSION_ID < 80400 */ + +#endif /* PHP_EVENTLOOP_COMPAT_H */