diff --git a/composer.json b/composer.json index 6f428f1..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": { @@ -31,7 +31,7 @@ "phpunit/phpunit": "11.*", "laravel/pint": "1.*", "phpstan/phpstan": "1.*", - "swoole/ide-helper": "5.1.2" + "swoole/ide-helper": "6.*" }, "suggests": { "ext-mongodb": "Needed to support MongoDB database pools", diff --git a/composer.lock b/composer.lock index f6803a8..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": "b3660d7c8a767d951cda9d13fc52576d", + "content-hash": "77b7bf03b8321524d9cd35c38645baca", "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", 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.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 240c267..eaa6e7a 100644 --- a/src/Pools/Adapter/Swoole.php +++ b/src/Pools/Adapter/Swoole.php @@ -4,22 +4,21 @@ use Utopia\Pools\Adapter; use Swoole\Coroutine\Channel; +use Swoole\Coroutine\Lock; class Swoole extends Adapter { + /** + * @var Channel + */ 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; } @@ -45,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(); } /** @@ -56,24 +54,22 @@ 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->pop($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 { 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/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; } } diff --git a/tests/Pools/Adapter/SwooleTest.php b/tests/Pools/Adapter/SwooleTest.php index 2864272..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) { @@ -353,4 +347,9 @@ public function testSwooleCoroutineStressTest(): void $this->assertSame(10, $pool->count(), 'Pool should have all connections back'); }); } + public function testInitOutsideCoroutineNotThrowAnyError(): void + { + $pool = new Pool(new Swoole(), 'test', 1, fn () => 'x'); + $this->assertInstanceOf(Pool::class, $pool); + } }