From 7a5932a2e7d904f727945b9ee4a3e3a55c12491b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20=C5=81ebkowski?= Date: Tue, 24 Feb 2026 10:01:01 +0100 Subject: [PATCH 1/5] add WorkerMemoryUsageSubscriber --- src/Messenger/WorkerMemoryUsageSubscriber.php | 61 +++++++++++++++++++ .../WorkerMemoryUsageSubscriberTest.php | 37 +++++++++++ 2 files changed, 98 insertions(+) create mode 100644 src/Messenger/WorkerMemoryUsageSubscriber.php create mode 100644 tests/Messenger/WorkerMemoryUsageSubscriberTest.php diff --git a/src/Messenger/WorkerMemoryUsageSubscriber.php b/src/Messenger/WorkerMemoryUsageSubscriber.php new file mode 100644 index 0000000..fb2c1d8 --- /dev/null +++ b/src/Messenger/WorkerMemoryUsageSubscriber.php @@ -0,0 +1,61 @@ + + */ + public static function getSubscribedEvents(): iterable { + return [ + WorkerRunningEvent::class => 'onWorkerRunning', + ]; + } + + public function __construct( + private readonly LoggerInterface $logger, + private readonly int $cutoff = 1024, + ) { + $this->memoryUsage = memory_get_usage(); + } + + public function onWorkerRunning(): void { + $currentUsage = memory_get_usage(); + + $difference = abs($this->memoryUsage - $currentUsage); + if ($difference < $this->cutoff) { + return; + } + + $this->logger->debug( + "Memory usage changed: {current} ({sign}{difference})", + [ + 'current' => $this->formatBytes($currentUsage), + 'sign' => ($this->memoryUsage > $currentUsage) ? "-" : "+", + 'difference' => $this->formatBytes($difference), + ], + ); + + $this->memoryUsage = $currentUsage; + } + + private function formatBytes(int $bytes): string { + $units = ['B', 'KB', 'MB', 'GB', 'TB']; + + $bytes = max($bytes, 0); + $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); + $pow = (int) min($pow, count($units) - 1); + + $bytes /= 1024 ** $pow; + + return sprintf("%s %s", round($bytes, 2), $units[$pow]); + } +} diff --git a/tests/Messenger/WorkerMemoryUsageSubscriberTest.php b/tests/Messenger/WorkerMemoryUsageSubscriberTest.php new file mode 100644 index 0000000..8c45daa --- /dev/null +++ b/tests/Messenger/WorkerMemoryUsageSubscriberTest.php @@ -0,0 +1,37 @@ +pushHandler($buffer); + $memoryLeak = []; + + $sut = new WorkerMemoryUsageSubscriber($logger, cutoff: 10 * 1024); + $sut->onWorkerRunning(); + self::assertEmpty($buffer->getRecords()); + + $memoryLeak[] = str_repeat(' ', 10 * 1024); + // triggers the first log + $sut->onWorkerRunning(); + self::assertCount(1, $buffer->getRecords()); + // the logs themselves take memory, so: + $sut->onWorkerRunning(); + self::assertCount(2, $buffer->getRecords()); + // no more logs: + $sut->onWorkerRunning(); + $sut->onWorkerRunning(); + $sut->onWorkerRunning(); + self::assertCount(2, $buffer->getRecords()); + + unset($memoryLeak); + } +} From b85988e86e398088c4f02e7171aa025ec9c11feb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20=C5=81ebkowski?= Date: Tue, 24 Feb 2026 10:02:35 +0100 Subject: [PATCH 2/5] cs fix --- src/Messenger/WorkerMemoryUsageSubscriber.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Messenger/WorkerMemoryUsageSubscriber.php b/src/Messenger/WorkerMemoryUsageSubscriber.php index fb2c1d8..9fe3b7b 100644 --- a/src/Messenger/WorkerMemoryUsageSubscriber.php +++ b/src/Messenger/WorkerMemoryUsageSubscriber.php @@ -31,6 +31,7 @@ public function onWorkerRunning(): void { $currentUsage = memory_get_usage(); $difference = abs($this->memoryUsage - $currentUsage); + if ($difference < $this->cutoff) { return; } From 53d10e3f358d09b76a0639dddb404c46f8b8683e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20=C5=81ebkowski?= Date: Tue, 24 Feb 2026 10:11:37 +0100 Subject: [PATCH 3/5] fake the actual memory usage --- src/Messenger/WorkerMemoryUsageSubscriber.php | 9 +++-- src/System/RealSystem.php | 11 +++++++ src/System/System.php | 9 +++++ .../WorkerMemoryUsageSubscriberTest.php | 33 ++++++++++++------- tests/System/FakeSystem.php | 17 ++++++++++ 5 files changed, 65 insertions(+), 14 deletions(-) create mode 100644 src/System/RealSystem.php create mode 100644 src/System/System.php create mode 100644 tests/System/FakeSystem.php diff --git a/src/Messenger/WorkerMemoryUsageSubscriber.php b/src/Messenger/WorkerMemoryUsageSubscriber.php index 9fe3b7b..6e2513a 100644 --- a/src/Messenger/WorkerMemoryUsageSubscriber.php +++ b/src/Messenger/WorkerMemoryUsageSubscriber.php @@ -7,9 +7,12 @@ use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Messenger\Event\WorkerRunningEvent; +use WonderNetwork\SlimKernel\System\RealSystem; +use WonderNetwork\SlimKernel\System\System; final class WorkerMemoryUsageSubscriber implements EventSubscriberInterface { private int $memoryUsage; + private readonly System $system; /** * @return iterable @@ -23,12 +26,14 @@ public static function getSubscribedEvents(): iterable { public function __construct( private readonly LoggerInterface $logger, private readonly int $cutoff = 1024, + System $system = null, ) { - $this->memoryUsage = memory_get_usage(); + $this->system = $system ?? new RealSystem(); + $this->memoryUsage = $this->system->memoryUsage(); } public function onWorkerRunning(): void { - $currentUsage = memory_get_usage(); + $currentUsage = $this->system->memoryUsage(); $difference = abs($this->memoryUsage - $currentUsage); diff --git a/src/System/RealSystem.php b/src/System/RealSystem.php new file mode 100644 index 0000000..839b087 --- /dev/null +++ b/src/System/RealSystem.php @@ -0,0 +1,11 @@ +pushHandler($buffer); - $memoryLeak = []; - $sut = new WorkerMemoryUsageSubscriber($logger, cutoff: 10 * 1024); + $system = new FakeSystem(); + + $sut = new WorkerMemoryUsageSubscriber( + logger: $logger, + cutoff: 10 * 1024, + system: $system, + ); $sut->onWorkerRunning(); self::assertEmpty($buffer->getRecords()); - $memoryLeak[] = str_repeat(' ', 10 * 1024); - // triggers the first log - $sut->onWorkerRunning(); - self::assertCount(1, $buffer->getRecords()); - // the logs themselves take memory, so: + $system->setMemoryUsage(10 * 1024 - 1); $sut->onWorkerRunning(); - self::assertCount(2, $buffer->getRecords()); - // no more logs: + self::assertCount(0, $buffer->getRecords()); + + $system->setMemoryUsage(10 * 1024); $sut->onWorkerRunning(); + self::assertCount(1, $buffer->getRecords()); + self::assertSame([ + 'current' => '10 KB', + 'sign' => '+', + 'difference' => '10 KB', + ], $buffer->getRecords()[0]->context); + $sut->onWorkerRunning(); + $system->setMemoryUsage(12 * 1024); $sut->onWorkerRunning(); - self::assertCount(2, $buffer->getRecords()); - - unset($memoryLeak); + self::assertCount(1, $buffer->getRecords()); } } diff --git a/tests/System/FakeSystem.php b/tests/System/FakeSystem.php new file mode 100644 index 0000000..3562efd --- /dev/null +++ b/tests/System/FakeSystem.php @@ -0,0 +1,17 @@ +memoryUsage = $memoryUsage; + } + + public function memoryUsage(): int { + return $this->memoryUsage; + } +} From 3ce3e83b656fde75767892f91db16c4df4c15b56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20=C5=81ebkowski?= Date: Thu, 26 Feb 2026 09:40:28 +0100 Subject: [PATCH 4/5] make `EventSubscribersCollection` implement `ServiceFactory` so it can be simply `register()`ed --- src/EventDispatcher/EventSubscribersCollection.php | 8 +++++++- tests/ConsoleLogger/ConsoleLoggerTest.php | 7 ++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/EventDispatcher/EventSubscribersCollection.php b/src/EventDispatcher/EventSubscribersCollection.php index 4c6133d..eb6520c 100644 --- a/src/EventDispatcher/EventSubscribersCollection.php +++ b/src/EventDispatcher/EventSubscribersCollection.php @@ -8,9 +8,11 @@ use RuntimeException; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use WonderNetwork\SlimKernel\ServiceFactory; +use WonderNetwork\SlimKernel\ServicesBuilder; use function DI\decorate; -final readonly class EventSubscribersCollection { +final readonly class EventSubscribersCollection implements ServiceFactory { public static function start(): self { return new self([]); } @@ -69,4 +71,8 @@ public function register(): iterable { ->addLazyListeners($dispatcher, $container), ); } + + public function __invoke(ServicesBuilder $builder): iterable { + yield from $this->register(); + } } diff --git a/tests/ConsoleLogger/ConsoleLoggerTest.php b/tests/ConsoleLogger/ConsoleLoggerTest.php index 6678d6d..310abd4 100644 --- a/tests/ConsoleLogger/ConsoleLoggerTest.php +++ b/tests/ConsoleLogger/ConsoleLoggerTest.php @@ -42,11 +42,12 @@ public function testConsoleLogger(): void { return $logger; }, ConsoleHandlerEventSubscriber::class => autowire(), - ...EventSubscribersCollection::start() - ->add(ConsoleHandlerEventSubscriber::class) - ->register(), ], ) + ->register( + EventSubscribersCollection::start() + ->add(ConsoleHandlerEventSubscriber::class), + ) ->register( new SymfonyConsoleServiceFactory( path: '/src/*Command.php', From 4abe84fcd076624b53b29c6f9e3f2aee1b2e1122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20=C5=81ebkowski?= Date: Thu, 26 Feb 2026 09:46:45 +0100 Subject: [PATCH 5/5] csfix --- src/EventDispatcher/EventSubscribersCollection.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/EventDispatcher/EventSubscribersCollection.php b/src/EventDispatcher/EventSubscribersCollection.php index eb6520c..212389d 100644 --- a/src/EventDispatcher/EventSubscribersCollection.php +++ b/src/EventDispatcher/EventSubscribersCollection.php @@ -23,6 +23,10 @@ public static function start(): self { public function __construct(private array $subscribers) { } + public function __invoke(ServicesBuilder $builder): iterable { + yield from $this->register(); + } + /** * @param class-string ...$subscribers */ @@ -71,8 +75,4 @@ public function register(): iterable { ->addLazyListeners($dispatcher, $container), ); } - - public function __invoke(ServicesBuilder $builder): iterable { - yield from $this->register(); - } }