From 47c54dc81408542300b32d6d870f69f3bf203a8f Mon Sep 17 00:00:00 2001 From: michalsn Date: Tue, 24 Feb 2026 09:16:57 +0100 Subject: [PATCH] fix: worker mode events cleanup --- app/Config/WorkerMode.php | 12 +++++++++ .../Worker/Views/frankenphp-worker.php.tpl | 4 ++- system/Events/Events.php | 14 +++++++--- user_guide_src/source/changelogs/v4.7.1.rst | 9 +++++++ .../source/installation/upgrade_471.rst | 16 +++++++++-- .../source/installation/worker_mode.rst | 27 +++++++++++++++++++ .../source/installation/worker_mode/001.php | 8 ++++++ .../source/installation/worker_mode/002.php | 8 ++++++ 8 files changed, 92 insertions(+), 6 deletions(-) create mode 100644 user_guide_src/source/installation/worker_mode/001.php create mode 100644 user_guide_src/source/installation/worker_mode/002.php diff --git a/app/Config/WorkerMode.php b/app/Config/WorkerMode.php index 1c005f63a67a..fd8b49434332 100644 --- a/app/Config/WorkerMode.php +++ b/app/Config/WorkerMode.php @@ -40,6 +40,18 @@ class WorkerMode 'cache', ]; + /** + * Reset Event Listeners + * + * List of event names whose listeners should be removed between requests. + * Use this if you register event listeners inside other event callbacks + * (rather than at the top level of Config/Events.php), which would cause + * them to accumulate across requests in worker mode. + * + * @var list + */ + public array $resetEventListeners = []; + /** * Force Garbage Collection * diff --git a/system/Commands/Worker/Views/frankenphp-worker.php.tpl b/system/Commands/Worker/Views/frankenphp-worker.php.tpl index 4a3c4ecd90b1..067d9d33dc1f 100644 --- a/system/Commands/Worker/Views/frankenphp-worker.php.tpl +++ b/system/Commands/Worker/Views/frankenphp-worker.php.tpl @@ -122,8 +122,10 @@ while (frankenphp_handle_request($handler)) { // Reset services except persistent ones Services::resetForWorkerMode($workerConfig); + // Reset event listeners + Events::cleanupForWorkerMode($workerConfig->resetEventListeners); + if (CI_DEBUG) { - Events::cleanupForWorkerMode(); Services::toolbar()->reset(); } } diff --git a/system/Events/Events.php b/system/Events/Events.php index 979f531591fe..3cad30995f94 100644 --- a/system/Events/Events.php +++ b/system/Events/Events.php @@ -289,10 +289,18 @@ public static function getPerformanceLogs() * Cleanup performance log and request-specific listeners for worker mode. * * Called at the END of each request to clean up state. + * + * @param list $resetEventListeners Additional event names to reset. */ - public static function cleanupForWorkerMode(): void + public static function cleanupForWorkerMode(array $resetEventListeners = []): void { - static::$performanceLog = []; - static::removeAllListeners('DBQuery'); + if (CI_DEBUG) { + static::$performanceLog = []; + static::removeAllListeners('DBQuery'); + } + + foreach ($resetEventListeners as $event) { + static::removeAllListeners($event); + } } } diff --git a/user_guide_src/source/changelogs/v4.7.1.rst b/user_guide_src/source/changelogs/v4.7.1.rst index 1fa19241910f..4eeceecdb162 100644 --- a/user_guide_src/source/changelogs/v4.7.1.rst +++ b/user_guide_src/source/changelogs/v4.7.1.rst @@ -24,6 +24,15 @@ Message Changes Changes ******* +Events +====== + +- **Worker Mode:** :php:func:`Events::cleanupForWorkerMode()` now accepts an optional + ``$resetEventListeners`` array parameter, corresponding to the new + ``$resetEventListeners`` property in ``Config\WorkerMode``. This allows users to + declare event names that should be cleaned up between requests when listeners are + registered inside event callbacks. See :ref:`worker-mode-reset-event-listeners`. + Others ====== diff --git a/user_guide_src/source/installation/upgrade_471.rst b/user_guide_src/source/installation/upgrade_471.rst index 066fcf568964..695027d10a99 100644 --- a/user_guide_src/source/installation/upgrade_471.rst +++ b/user_guide_src/source/installation/upgrade_471.rst @@ -16,6 +16,16 @@ Please refer to the upgrade instructions corresponding to your installation meth Mandatory File Changes ********************** +Worker Mode +=========== + +If you are using Worker Mode, you must update **public/frankenphp-worker.php** after +upgrading. The easiest way is to re-run the install command: + +.. code-block:: console + + php spark worker:install --force + **************** Breaking Changes **************** @@ -44,7 +54,9 @@ and it is recommended that you merge the updated versions with your application: Config ------ -- @TODO +- app/Config/WorkerMode.php + - ``Config\WorkerMode::$resetEventListeners`` has been added, with a default + value set to ``[]``. See :ref:`worker-mode-reset-event-listeners` for details. All Changes =========== @@ -52,4 +64,4 @@ All Changes This is a list of all files in the **project space** that received changes; many will be simple comments or formatting that have no effect on the runtime: -- @TODO +- app/Config/WorkerMode.php diff --git a/user_guide_src/source/installation/worker_mode.rst b/user_guide_src/source/installation/worker_mode.rst index 4dea3d1aad32..6f71af593ee9 100644 --- a/user_guide_src/source/installation/worker_mode.rst +++ b/user_guide_src/source/installation/worker_mode.rst @@ -186,6 +186,10 @@ Option Type Description in this list are destroyed after each request to prevent state leakage. Default: ``['autoloader', 'locator', 'exceptions', 'commands', 'codeigniter', 'superglobals', 'routes', 'cache']`` +**$resetEventListeners** array Event names whose listeners are removed between requests. Use this + when you register event listeners inside other event callbacks rather + than at the top level of **Config/Events.php**, which would cause them + to accumulate across requests. Default: ``[]`` **$forceGarbageCollection** bool Whether to force garbage collection after each request. ``true`` (default, recommended): Prevents memory leaks. ``false``: Relies on PHP's automatic garbage collection. @@ -214,6 +218,29 @@ Service Purpose state management can cause data leakage between requests. Only persist services that are truly stateless or manage their own request isolation. +.. _worker-mode-reset-event-listeners: + +Reset Event Listeners +===================== + +.. versionadded:: 4.7.1 + +Event listeners registered at the top level of **Config/Events.php** are loaded once +at worker startup and persist correctly across requests. However, if you register a +listener inside another event's callback, it will be re-registered on every request +and accumulate: + +.. literalinclude:: worker_mode/001.php + +To clean up such listeners between requests, add the event name to +``$resetEventListeners`` in **app/Config/WorkerMode.php**: + +.. literalinclude:: worker_mode/002.php + +.. note:: The recommended approach is to register listeners at the top level of + **Config/Events.php** instead of inside callbacks. Use ``$resetEventListeners`` + only when registering inside a callback is unavoidable. + ********************** Optimize Configuration ********************** diff --git a/user_guide_src/source/installation/worker_mode/001.php b/user_guide_src/source/installation/worker_mode/001.php new file mode 100644 index 000000000000..3140208b9e95 --- /dev/null +++ b/user_guide_src/source/installation/worker_mode/001.php @@ -0,0 +1,8 @@ +