From 14cfd1d30adccfe2284130e8c48840f6b030f94b Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Tue, 27 Jan 2026 19:01:28 +0530 Subject: [PATCH 1/7] added new locking mechanism which is coroutine safe --- src/Pools/Adapter/Swoole.php | 17 ++++++----------- tests/Pools/Adapter/SwooleTest.php | 5 +++++ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Pools/Adapter/Swoole.php b/src/Pools/Adapter/Swoole.php index 240c267..5664cb0 100644 --- a/src/Pools/Adapter/Swoole.php +++ b/src/Pools/Adapter/Swoole.php @@ -4,22 +4,18 @@ use Utopia\Pools\Adapter; use Swoole\Coroutine\Channel; +use Swoole\Coroutine\Lock; class Swoole extends Adapter { protected Channel $pool; - protected Channel $lock; + protected Lock $lock; public function initialize(int $size): static { - $this->pool = new Channel($size); - // With channels, the current coroutine suspends and yields control to the event loop, - // allowing other coroutines to continue executing. - // Using a blocking lock freezes the worker thread, causing all coroutines in that - // worker to stop making progress. - $this->lock = new Channel(1); - $this->lock->push(true); + $this->pool = new Channel($size); + $this->lock = new Lock(); return $this; } @@ -63,7 +59,7 @@ public function count(): int */ public function synchronized(callable $callback, int $timeout): mixed { - $acquired = $this->lock->pop($timeout); + $acquired = $this->lock->lock($timeout); if (!$acquired) { throw new \RuntimeException("Failed to acquire lock within {$timeout} seconds"); @@ -72,8 +68,7 @@ public function synchronized(callable $callback, int $timeout): mixed try { return $callback(); } finally { - // Guaranteed to have space here; avoid timeouts so the token isn't lost. - $this->lock->push(true); + $this->lock->unlock(); } } } diff --git a/tests/Pools/Adapter/SwooleTest.php b/tests/Pools/Adapter/SwooleTest.php index 2864272..8b6e83a 100644 --- a/tests/Pools/Adapter/SwooleTest.php +++ b/tests/Pools/Adapter/SwooleTest.php @@ -353,4 +353,9 @@ public function testSwooleCoroutineStressTest(): void $this->assertSame(10, $pool->count(), 'Pool should have all connections back'); }); } + public function testInitOutsideCoroutineNotThrowAnyError() + { + $pool = new Pool(new Swoole(), 'test', 1, fn () => 'x'); + $this->assertInstanceOf(Pool::class, $pool); + } } From 4a219ff73e3485f8c8f29a0e7496d47d915a3bcc Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Tue, 27 Jan 2026 19:03:26 +0530 Subject: [PATCH 2/7] updated signatures --- src/Pools/Adapter.php | 3 +-- src/Pools/Adapter/Stack.php | 4 +--- src/Pools/Adapter/Swoole.php | 7 +++---- src/Pools/Pool.php | 12 ++++++------ 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/Pools/Adapter.php b/src/Pools/Adapter.php index afd8f20..675d4c3 100644 --- a/src/Pools/Adapter.php +++ b/src/Pools/Adapter.php @@ -20,8 +20,7 @@ abstract public function count(): int; * Execute a callback with lock protection if the adapter supports it * * @param callable $callback - * @param int $timeout Timeout in seconds * @return mixed */ - abstract public function synchronized(callable $callback, int $timeout): mixed; + abstract public function synchronized(callable $callback): mixed; } diff --git a/src/Pools/Adapter/Stack.php b/src/Pools/Adapter/Stack.php index 60caabd..53edbd9 100644 --- a/src/Pools/Adapter/Stack.php +++ b/src/Pools/Adapter/Stack.php @@ -55,13 +55,11 @@ public function count(): int * Executes the callback without acquiring a lock. * * This implementation does not provide mutual exclusion. - * The `$timeout` parameter is ignored. * * @param callable $callback Callback to execute. - * @param int $timeout Ignored. * @return mixed The value returned by the callback. */ - public function synchronized(callable $callback, int $timeout): mixed + public function synchronized(callable $callback): mixed { return $callback(); } diff --git a/src/Pools/Adapter/Swoole.php b/src/Pools/Adapter/Swoole.php index 5664cb0..801f0fd 100644 --- a/src/Pools/Adapter/Swoole.php +++ b/src/Pools/Adapter/Swoole.php @@ -52,17 +52,16 @@ public function count(): int * afterward, even if the callback throws an exception. * * @param callable $callback Callback to execute within the critical section. - * @param int $timeout Maximum time (in seconds) to wait for the lock. * @return mixed The value returned by the callback. * * @throws \RuntimeException If the lock cannot be acquired within the timeout. */ - public function synchronized(callable $callback, int $timeout): mixed + public function synchronized(callable $callback): mixed { - $acquired = $this->lock->lock($timeout); + $acquired = $this->lock->lock(); if (!$acquired) { - throw new \RuntimeException("Failed to acquire lock within {$timeout} seconds"); + throw new \RuntimeException("Failed to acquire lock"); } try { diff --git a/src/Pools/Pool.php b/src/Pools/Pool.php index 917f616..b345f4a 100644 --- a/src/Pools/Pool.php +++ b/src/Pools/Pool.php @@ -266,19 +266,19 @@ public function pop(): Connection return true; } return false; - }, timeout: $this->getSynchronizationTimeout()); + }); if ($shouldCreateConnections) { try { $connection = $this->createConnection(); $this->pool->synchronized(function () use ($connection) { $this->active[$connection->getID()] = $connection; - }, timeout: $this->getSynchronizationTimeout()); + }); return $connection; } catch (\Exception $e) { $this->pool->synchronized(function () { $this->connectionsCreated--; - }, timeout: $this->getSynchronizationTimeout()); + }); throw $e; } } @@ -296,7 +296,7 @@ public function pop(): Connection if ($connection instanceof Connection) { $this->pool->synchronized(function () use ($connection) { $this->active[$connection->getID()] = $connection; - }, timeout: $this->getSynchronizationTimeout()); + }); return $connection; } } @@ -406,7 +406,7 @@ private function destroyConnection(?Connection $connection = null): static return true; }; return false; - }, timeout: $this->getSynchronizationTimeout()); + }); if ($shouldCreate) { try { @@ -414,7 +414,7 @@ private function destroyConnection(?Connection $connection = null): static } catch (Exception $e) { $this->pool->synchronized(function () { $this->connectionsCreated--; - }, timeout: $this->getSynchronizationTimeout()); + }); throw $e; } } From a03a5330a91a40c5157ca32bc9b231914a660e91 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Tue, 27 Jan 2026 19:06:46 +0530 Subject: [PATCH 3/7] linting --- tests/Pools/Adapter/SwooleTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Pools/Adapter/SwooleTest.php b/tests/Pools/Adapter/SwooleTest.php index 8b6e83a..e75528b 100644 --- a/tests/Pools/Adapter/SwooleTest.php +++ b/tests/Pools/Adapter/SwooleTest.php @@ -353,7 +353,7 @@ public function testSwooleCoroutineStressTest(): void $this->assertSame(10, $pool->count(), 'Pool should have all connections back'); }); } - public function testInitOutsideCoroutineNotThrowAnyError() + public function testInitOutsideCoroutineNotThrowAnyError(): void { $pool = new Pool(new Swoole(), 'test', 1, fn () => 'x'); $this->assertInstanceOf(Pool::class, $pool); From 12e3774a645737fdbcd397f4a8aaba7220ba2c90 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Tue, 27 Jan 2026 19:09:21 +0530 Subject: [PATCH 4/7] updated swoole helper --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 6f428f1..313c30d 100755 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "phpunit/phpunit": "11.*", "laravel/pint": "1.*", "phpstan/phpstan": "1.*", - "swoole/ide-helper": "5.1.2" + "swoole/ide-helper": "^6.0" }, "suggests": { "ext-mongodb": "Needed to support MongoDB database pools", diff --git a/composer.lock b/composer.lock index f6803a8..8c9f7da 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b3660d7c8a767d951cda9d13fc52576d", + "content-hash": "d0a862bb8c4e355860a810a500d274c3", "packages": [ { "name": "brick/math", @@ -3764,16 +3764,16 @@ }, { "name": "swoole/ide-helper", - "version": "5.1.2", + "version": "6.0.2", "source": { "type": "git", "url": "https://github.com/swoole/ide-helper.git", - "reference": "33ec7af9111b76d06a70dd31191cc74793551112" + "reference": "6f12243dce071714c5febe059578d909698f9a52" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swoole/ide-helper/zipball/33ec7af9111b76d06a70dd31191cc74793551112", - "reference": "33ec7af9111b76d06a70dd31191cc74793551112", + "url": "https://api.github.com/repos/swoole/ide-helper/zipball/6f12243dce071714c5febe059578d909698f9a52", + "reference": "6f12243dce071714c5febe059578d909698f9a52", "shasum": "" }, "type": "library", @@ -3790,9 +3790,9 @@ "description": "IDE help files for Swoole.", "support": { "issues": "https://github.com/swoole/ide-helper/issues", - "source": "https://github.com/swoole/ide-helper/tree/5.1.2" + "source": "https://github.com/swoole/ide-helper/tree/6.0.2" }, - "time": "2024-02-01T22:28:11+00:00" + "time": "2025-03-23T07:31:41+00:00" }, { "name": "theseer/tokenizer", From 55b5648ceda795aebe428e78abe893b2dcb63549 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Tue, 27 Jan 2026 19:22:04 +0530 Subject: [PATCH 5/7] added php stan ignore --- composer.json | 2 +- composer.lock | 2 +- src/Pools/Adapter/Swoole.php | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 313c30d..939f6ec 100755 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "phpunit/phpunit": "11.*", "laravel/pint": "1.*", "phpstan/phpstan": "1.*", - "swoole/ide-helper": "^6.0" + "swoole/ide-helper": "6.*" }, "suggests": { "ext-mongodb": "Needed to support MongoDB database pools", diff --git a/composer.lock b/composer.lock index 8c9f7da..ce70c94 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d0a862bb8c4e355860a810a500d274c3", + "content-hash": "77b7bf03b8321524d9cd35c38645baca", "packages": [ { "name": "brick/math", diff --git a/src/Pools/Adapter/Swoole.php b/src/Pools/Adapter/Swoole.php index 801f0fd..ea0334b 100644 --- a/src/Pools/Adapter/Swoole.php +++ b/src/Pools/Adapter/Swoole.php @@ -10,11 +10,13 @@ class Swoole extends Adapter { protected Channel $pool; + // @phpstan-ignore-next-line protected Lock $lock; public function initialize(int $size): static { $this->pool = new Channel($size); + // @phpstan-ignore-next-line` $this->lock = new Lock(); return $this; @@ -58,6 +60,7 @@ public function count(): int */ public function synchronized(callable $callback): mixed { + // @phpstan-ignore-next-line $acquired = $this->lock->lock(); if (!$acquired) { @@ -67,6 +70,7 @@ public function synchronized(callable $callback): mixed try { return $callback(); } finally { + // @phpstan-ignore-next-line $this->lock->unlock(); } } From 28c0a754222a47631527e11ec74533604dee3a7a Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Wed, 28 Jan 2026 18:30:15 +0530 Subject: [PATCH 6/7] added php nenon --- composer.json | 2 +- phpstan.neon | 10 ++++++++++ src/Pools/Adapter/Swoole.php | 4 ---- 3 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 phpstan.neon diff --git a/composer.json b/composer.json index 939f6ec..b5d78c0 100755 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "scripts": { "lint": "./vendor/bin/pint --preset psr12 --test", "format": "./vendor/bin/pint --preset psr12", - "check": "./vendor/bin/phpstan analyse --level max src tests", + "check": "./vendor/bin/phpstan analyse -c phpstan.neon", "test": "./vendor/bin/phpunit --configuration phpunit.xml --debug" }, "require": { diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..90e08ef --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,10 @@ +parameters: + level: 8 + + paths: + - src + - tests + + scanDirectories: + - vendor/swoole/ide-helper + diff --git a/src/Pools/Adapter/Swoole.php b/src/Pools/Adapter/Swoole.php index ea0334b..801f0fd 100644 --- a/src/Pools/Adapter/Swoole.php +++ b/src/Pools/Adapter/Swoole.php @@ -10,13 +10,11 @@ class Swoole extends Adapter { protected Channel $pool; - // @phpstan-ignore-next-line protected Lock $lock; public function initialize(int $size): static { $this->pool = new Channel($size); - // @phpstan-ignore-next-line` $this->lock = new Lock(); return $this; @@ -60,7 +58,6 @@ public function count(): int */ public function synchronized(callable $callback): mixed { - // @phpstan-ignore-next-line $acquired = $this->lock->lock(); if (!$acquired) { @@ -70,7 +67,6 @@ public function synchronized(callable $callback): mixed try { return $callback(); } finally { - // @phpstan-ignore-next-line $this->lock->unlock(); } } From d17458edf1a2977a2a65e74acf729c983a9e2148 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Wed, 28 Jan 2026 18:33:09 +0530 Subject: [PATCH 7/7] updated swoole --- src/Pools/Adapter/Swoole.php | 6 ++++-- tests/Pools/Adapter/SwooleTest.php | 6 ------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Pools/Adapter/Swoole.php b/src/Pools/Adapter/Swoole.php index 801f0fd..eaa6e7a 100644 --- a/src/Pools/Adapter/Swoole.php +++ b/src/Pools/Adapter/Swoole.php @@ -8,6 +8,9 @@ class Swoole extends Adapter { + /** + * @var Channel + */ protected Channel $pool; protected Lock $lock; @@ -41,8 +44,7 @@ public function pop(int $timeout): mixed public function count(): int { - $length = $this->pool->length(); - return is_int($length) ? $length : 0; + return (int) $this->pool->length(); } /** diff --git a/tests/Pools/Adapter/SwooleTest.php b/tests/Pools/Adapter/SwooleTest.php index e75528b..1a128df 100644 --- a/tests/Pools/Adapter/SwooleTest.php +++ b/tests/Pools/Adapter/SwooleTest.php @@ -20,7 +20,6 @@ protected function execute(callable $callback): mixed $result = null; $exception = null; - /** @phpstan-ignore-next-line */ Coroutine\run(function () use ($callback, &$result, &$exception): void { try { $result = $callback(); @@ -41,7 +40,6 @@ public function testSwooleCoroutineRaceCondition(): void $errors = []; $successCount = 0; - /** @phpstan-ignore-next-line */ \Swoole\Coroutine\run(function () use (&$errors, &$successCount) { // Create a pool with 5 connections inside coroutine context $connectionCounter = 0; @@ -123,7 +121,6 @@ public function testSwooleCoroutineHighConcurrency(): void $successCount = 0; $errorCount = 0; - /** @phpstan-ignore-next-line */ \Swoole\Coroutine\run(function () use ($totalRequests, &$successCount, &$errorCount) { // Create a pool with 3 connections inside coroutine context $connectionCounter = 0; @@ -183,7 +180,6 @@ public function testSwooleCoroutineConnectionUniqueness(): void $seenResources = []; $duplicateResources = []; - /** @phpstan-ignore-next-line */ \Swoole\Coroutine\run(function () use (&$seenResources, &$duplicateResources) { // Create a pool with 5 connections inside coroutine context $connectionCounter = 0; @@ -248,7 +244,6 @@ public function testSwooleCoroutineIdleConnectionReuse(): void $connectionIds = []; $connectionCounter = 0; - /** @phpstan-ignore-next-line */ \Swoole\Coroutine\run(function () use (&$connectionIds, &$connectionCounter) { // Create a pool with 3 connections inside coroutine context $pool = new Pool(new Swoole(), 'swoole-reuse', 3, function () use (&$connectionCounter) { @@ -308,7 +303,6 @@ public function testSwooleCoroutineStressTest(): void $errorCount = 0; $connectionCounter = 0; - /** @phpstan-ignore-next-line */ \Swoole\Coroutine\run(function () use ($totalRequests, &$successCount, &$errorCount, &$connectionCounter) { // Create a pool with 10 connections inside coroutine context $pool = new Pool(new Swoole(), 'swoole-stress', 10, function () use (&$connectionCounter) {