diff --git a/src/AdvisoryLockServiceProvider.php b/src/AdvisoryLockServiceProvider.php index 976d9fa..afc3760 100644 --- a/src/AdvisoryLockServiceProvider.php +++ b/src/AdvisoryLockServiceProvider.php @@ -20,6 +20,6 @@ public function register(): void public function boot(TransactionEventHub $hub): void { - TransactionEventHub::setResolver(fn () => $hub); + TransactionEventHub::setResolver(static fn () => $hub); } } diff --git a/src/ConnectionServiceProvider.php b/src/ConnectionServiceProvider.php index 5998e13..7de71b0 100644 --- a/src/ConnectionServiceProvider.php +++ b/src/ConnectionServiceProvider.php @@ -16,7 +16,7 @@ final class ConnectionServiceProvider extends ServiceProvider */ public function register(): void { - Connection::resolverFor('mysql', fn (...$args) => new MySqlConnection(...$args)); - Connection::resolverFor('pgsql', fn (...$args) => new PostgresConnection(...$args)); + Connection::resolverFor('mysql', static fn (...$args) => new MySqlConnection(...$args)); + Connection::resolverFor('pgsql', static fn (...$args) => new PostgresConnection(...$args)); } } diff --git a/src/Contracts/InvalidTransactionLevelException.php b/src/Contracts/InvalidTransactionLevelException.php index 419a105..68f3721 100644 --- a/src/Contracts/InvalidTransactionLevelException.php +++ b/src/Contracts/InvalidTransactionLevelException.php @@ -11,6 +11,4 @@ * * You can't use TransactionLocker outside of transaction. */ -class InvalidTransactionLevelException extends BadMethodCallException -{ -} +class InvalidTransactionLevelException extends BadMethodCallException {} diff --git a/src/Contracts/UnsupportedDriverException.php b/src/Contracts/UnsupportedDriverException.php index 7a43143..a97b9ef 100644 --- a/src/Contracts/UnsupportedDriverException.php +++ b/src/Contracts/UnsupportedDriverException.php @@ -11,6 +11,4 @@ * * Requested operation is not supported on the driver. */ -class UnsupportedDriverException extends DomainException -{ -} +class UnsupportedDriverException extends DomainException {} diff --git a/src/LockerFactory.php b/src/LockerFactory.php index 9272f91..9261129 100644 --- a/src/LockerFactory.php +++ b/src/LockerFactory.php @@ -18,8 +18,7 @@ class LockerFactory implements Contracts\LockerFactory public function __construct( protected Connection $connection, - ) { - } + ) {} public function forTransaction(): TransactionLocker { @@ -40,6 +39,7 @@ public function forSession(): SessionLocker if ($this->connection instanceof PostgresConnection) { return $this->session ??= new PostgresSessionLocker($this->connection); } + // @codeCoverageIgnoreStart throw new UnsupportedDriverException('SessionLocker is not supported'); // @codeCoverageIgnoreEnd diff --git a/src/MySqlSessionLock.php b/src/MySqlSessionLock.php index 2b8f90a..d15677f 100644 --- a/src/MySqlSessionLock.php +++ b/src/MySqlSessionLock.php @@ -25,8 +25,7 @@ public function __construct( private MySqlConnection $connection, private WeakMap $locks, private string $key, - ) { - } + ) {} public function release(): bool { diff --git a/src/PostgresSessionLocker.php b/src/PostgresSessionLocker.php index a846fdf..8c68c79 100644 --- a/src/PostgresSessionLocker.php +++ b/src/PostgresSessionLocker.php @@ -9,7 +9,7 @@ use Mpyw\LaravelDatabaseAdvisoryLock\Contracts\LockFailedException; use Mpyw\LaravelDatabaseAdvisoryLock\Contracts\SessionLock; use Mpyw\LaravelDatabaseAdvisoryLock\Contracts\SessionLocker; -use Mpyw\LaravelDatabaseAdvisoryLock\Utilities\PostgresTryLockLoopEmulator; +use Mpyw\LaravelDatabaseAdvisoryLock\Utilities\PostgresTimeoutEmulator; use Mpyw\LaravelDatabaseAdvisoryLock\Utilities\Selector; use WeakMap; @@ -36,10 +36,10 @@ public function __construct( public function lockOrFail(string $key, int $timeout = 0): SessionLock { if ($timeout > 0) { - // Positive timeout can be emulated through repeating sleep and retry - $emulator = new PostgresTryLockLoopEmulator($this->connection); + // Positive timeout can be performed through temporary function + $emulator = new PostgresTimeoutEmulator($this->connection); $sql = $emulator->sql($timeout, false); - $result = $emulator->performTryLockLoop($key, $timeout); + $result = $emulator->performWithTimeout($key, $timeout); } else { // Negative timeout means infinite wait // Zero timeout means no wait diff --git a/src/PostgresTransactionLocker.php b/src/PostgresTransactionLocker.php index 78c2d58..53f8b16 100644 --- a/src/PostgresTransactionLocker.php +++ b/src/PostgresTransactionLocker.php @@ -9,7 +9,7 @@ use Mpyw\LaravelDatabaseAdvisoryLock\Contracts\InvalidTransactionLevelException; use Mpyw\LaravelDatabaseAdvisoryLock\Contracts\LockFailedException; use Mpyw\LaravelDatabaseAdvisoryLock\Contracts\TransactionLocker; -use Mpyw\LaravelDatabaseAdvisoryLock\Utilities\PostgresTryLockLoopEmulator; +use Mpyw\LaravelDatabaseAdvisoryLock\Utilities\PostgresTimeoutEmulator; use Mpyw\LaravelDatabaseAdvisoryLock\Utilities\Selector; final class PostgresTransactionLocker implements TransactionLocker @@ -18,8 +18,7 @@ final class PostgresTransactionLocker implements TransactionLocker public function __construct( protected PostgresConnection $connection, - ) { - } + ) {} public function lockOrFail(string $key, int $timeout = 0): void { @@ -28,10 +27,10 @@ public function lockOrFail(string $key, int $timeout = 0): void } if ($timeout > 0) { - // Positive timeout can be emulated through repeating sleep and retry - $emulator = new PostgresTryLockLoopEmulator($this->connection); + // Positive timeout can be performed through temporary function + $emulator = new PostgresTimeoutEmulator($this->connection); $sql = $emulator->sql($timeout, false); - $result = $emulator->performTryLockLoop($key, $timeout, true); + $result = $emulator->performWithTimeout($key, $timeout, true); } else { // Negative timeout means infinite wait // Zero timeout means no wait diff --git a/src/Utilities/PostgresTryLockLoopEmulator.php b/src/Utilities/PostgresTimeoutEmulator.php similarity index 61% rename from src/Utilities/PostgresTryLockLoopEmulator.php rename to src/Utilities/PostgresTimeoutEmulator.php index 16346a5..06dd573 100644 --- a/src/Utilities/PostgresTryLockLoopEmulator.php +++ b/src/Utilities/PostgresTimeoutEmulator.php @@ -10,16 +10,15 @@ use function preg_replace; /** - * class PostgresTryLockLoopEmulator + * class PostgresTimeoutEmulator * * @internal */ -final class PostgresTryLockLoopEmulator +final class PostgresTimeoutEmulator { public function __construct( private PostgresConnection $connection, - ) { - } + ) {} /** * Perform a time-limited lock acquisition. @@ -27,7 +26,7 @@ public function __construct( * @phpstan-param positive-int $timeout * @throws QueryException */ - public function performTryLockLoop(string $key, int $timeout, bool $forTransaction = false): bool + public function performWithTimeout(string $key, int $timeout, bool $forTransaction = false): bool { // Binding parameters to procedures is only allowed when PDOStatement emulation is enabled. return PDOStatementEmulator::emulated( @@ -45,33 +44,24 @@ public function performTryLockLoop(string $key, int $timeout, bool $forTransacti public function sql(int $timeout, bool $forTransaction): string { $suffix = $forTransaction ? '_xact' : ''; + $modifier = $forTransaction ? 'LOCAL' : 'SESSION'; $sql = << timeout THEN - RETURN false; - END IF; - PERFORM pg_sleep(0.5); - END LOOP; + EXECUTE format('SET {$modifier} lock_timeout TO %L;', timeout); + PERFORM pg_advisory{$suffix}_lock(hashtext(key)); + RETURN true; + EXCEPTION + WHEN lock_not_available OR deadlock_detected THEN RETURN false; END $$ LANGUAGE plpgsql; - SELECT pg_temp.laravel_pg_try_advisory{$suffix}_lock_timeout(?, interval '{$timeout} seconds'); + SELECT pg_temp.laravel_pg_try_advisory{$suffix}_lock_timeout(?, '{$timeout}s'); EOD; return (string)preg_replace('/\s++/', ' ', $sql); diff --git a/src/Utilities/Selector.php b/src/Utilities/Selector.php index c0cc6d3..37ab61a 100644 --- a/src/Utilities/Selector.php +++ b/src/Utilities/Selector.php @@ -20,8 +20,7 @@ final class Selector { public function __construct( private ConnectionInterface $connection, - ) { - } + ) {} /** * Run query to get a single value from the result. diff --git a/tests/ReconnectionToleranceTest.php b/tests/ReconnectionToleranceTest.php index 7529009..f42df68 100644 --- a/tests/ReconnectionToleranceTest.php +++ b/tests/ReconnectionToleranceTest.php @@ -85,7 +85,7 @@ public function testReconnectionWithoutActiveLocks(string $name): void DB::connection($name) ->advisoryLocker() ->forSession() - ->withLocking('', fn () => null); + ->withLocking('', static fn () => null); } catch (QueryException) { } $this->endListening(); @@ -113,7 +113,7 @@ public function testReconnectionWithActiveLocks(string $name): void $conn ->advisoryLocker() ->forSession() - ->withLocking('', fn () => null); + ->withLocking('', static fn () => null); } catch (QueryException) { } $this->endListening(); diff --git a/tests/SessionLockerTest.php b/tests/SessionLockerTest.php index 57a10e4..dad5e31 100644 --- a/tests/SessionLockerTest.php +++ b/tests/SessionLockerTest.php @@ -23,11 +23,11 @@ public function testDifferentKeysOnDifferentConnections(string $name): void DB::connection($name) ->advisoryLocker() ->forSession() - ->withLocking('foo', function () use ($name, &$passed): void { + ->withLocking('foo', static function () use ($name, &$passed): void { DB::connection("{$name}2") ->advisoryLocker() ->forSession() - ->withLocking('bar', function () use (&$passed): void { + ->withLocking('bar', static function () use (&$passed): void { $passed = true; }); }); @@ -50,7 +50,7 @@ public function testSameKeysOnDifferentConnections(string $name): void DB::connection("{$name}2") ->advisoryLocker() ->forSession() - ->withLocking('foo', function () use (&$passed): void { + ->withLocking('foo', static function () use (&$passed): void { $passed = true; }); }); @@ -68,11 +68,11 @@ public function testDifferentKeysOnSameConnections(string $name): void DB::connection($name) ->advisoryLocker() ->forSession() - ->withLocking('foo', function (ConnectionInterface $conn) use (&$passed): void { + ->withLocking('foo', static function (ConnectionInterface $conn) use (&$passed): void { $conn ->advisoryLocker() ->forSession() - ->withLocking('bar', function () use (&$passed): void { + ->withLocking('bar', static function () use (&$passed): void { $passed = true; }); }); @@ -90,11 +90,11 @@ public function testSameKeysOnSameConnections(string $name): void DB::connection($name) ->advisoryLocker() ->forSession() - ->withLocking('foo', function (ConnectionInterface $conn) use (&$passed): void { + ->withLocking('foo', static function (ConnectionInterface $conn) use (&$passed): void { $conn ->advisoryLocker() ->forSession() - ->withLocking('foo', function () use (&$passed): void { + ->withLocking('foo', static function () use (&$passed): void { $passed = true; }); }); @@ -190,7 +190,7 @@ public function testFiniteTimeoutSuccessConsecutive(string $name): void $conn->advisoryLocker()->forSession()->tryLock('baz', 1), $conn->advisoryLocker()->forSession()->tryLock('qux', 1), ]; - $result_booleans = array_map(fn ($result) => $result !== null, $results); + $result_booleans = array_map(static fn ($result) => $result !== null, $results); $this->assertSame(0, $proc1->wait()); $this->assertSame(0, $proc2->wait()); $this->assertSame([false, true, false, true], $result_booleans); diff --git a/tests/TableTestCase.php b/tests/TableTestCase.php index 05c02dd..48ee712 100644 --- a/tests/TableTestCase.php +++ b/tests/TableTestCase.php @@ -16,7 +16,7 @@ protected function setUp(): void $schema = Schema::connection('pgsql'); $schema->dropIfExists('users'); - $schema->create('users', function (Blueprint $table): void { + $schema->create('users', static function (Blueprint $table): void { $table->unsignedBigInteger('id')->unique(); }); } diff --git a/tests/TestCase.php b/tests/TestCase.php index aa9ea17..7b6c940 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -45,22 +45,22 @@ protected function getEnvironmentSetUp($app): void ]); } - public function connectionsAll(): array + public static function connectionsAll(): array { return ['postgres' => ['pgsql'], 'mysql' => ['mysql'], 'mariadb' => ['mariadb']]; } - public function connectionsMysql(): array + public static function connectionsMysql(): array { return ['mysql' => ['mysql']]; } - public function connectionsMysqlLike(): array + public static function connectionsMysqlLike(): array { return ['mysql' => ['mysql'], 'mariadb' => ['mariadb']]; } - public function connectionsPostgres(): array + public static function connectionsPostgres(): array { return ['postgres' => ['pgsql']]; } diff --git a/tests/TransactionLockerTest.php b/tests/TransactionLockerTest.php index ced918f..1f49730 100644 --- a/tests/TransactionLockerTest.php +++ b/tests/TransactionLockerTest.php @@ -22,13 +22,13 @@ public function testDifferentKeysOnDifferentConnections(string $name): void { $passed = false; - DB::connection($name)->transaction(function (ConnectionInterface $conn) use ($name, &$passed): void { + DB::connection($name)->transaction(static function (ConnectionInterface $conn) use ($name, &$passed): void { $conn ->advisoryLocker() ->forTransaction() ->lockOrFail('foo'); - DB::connection("{$name}2")->transaction(function (ConnectionInterface $conn): void { + DB::connection("{$name}2")->transaction(static function (ConnectionInterface $conn): void { $conn ->advisoryLocker() ->forTransaction() @@ -56,7 +56,7 @@ public function testSameKeysOnDifferentConnections(string $name): void $this->expectException(LockFailedException::class); $this->expectExceptionMessage('Failed to acquire lock: foo'); - DB::connection("{$name}2")->transaction(function (ConnectionInterface $conn): void { + DB::connection("{$name}2")->transaction(static function (ConnectionInterface $conn): void { $conn ->advisoryLocker() ->forTransaction() @@ -75,7 +75,7 @@ public function testDifferentKeysOnSameConnections(string $name): void { $passed = false; - DB::connection($name)->transaction(function (ConnectionInterface $conn) use (&$passed): void { + DB::connection($name)->transaction(static function (ConnectionInterface $conn) use (&$passed): void { $conn ->advisoryLocker() ->forTransaction() @@ -100,7 +100,7 @@ public function testSameKeysOnSameConnections(string $name): void { $passed = false; - DB::connection($name)->transaction(function (ConnectionInterface $conn) use (&$passed): void { + DB::connection($name)->transaction(static function (ConnectionInterface $conn) use (&$passed): void { $conn ->advisoryLocker() ->forTransaction() @@ -142,7 +142,7 @@ public function testFiniteTimeoutSuccess(string $name): void sleep(1); try { - $result = DB::connection($name)->transaction(function (ConnectionInterface $conn) { + $result = DB::connection($name)->transaction(static function (ConnectionInterface $conn) { return $conn->advisoryLocker()->forTransaction()->tryLock('foo', 3); }); @@ -164,7 +164,7 @@ public function testFinitePostgresTimeoutSuccessConsecutive(string $name): void sleep(1); try { - $result = DB::connection($name)->transaction(function (ConnectionInterface $conn) { + $result = DB::connection($name)->transaction(static function (ConnectionInterface $conn) { return [ $conn->advisoryLocker()->forTransaction()->tryLock('foo', 1), $conn->advisoryLocker()->forTransaction()->tryLock('bar', 1), @@ -191,7 +191,7 @@ public function testFinitePostgresTimeoutExceeded(string $name): void sleep(1); try { - $result = DB::connection($name)->transaction(function (ConnectionInterface $conn) { + $result = DB::connection($name)->transaction(static function (ConnectionInterface $conn) { return $conn->advisoryLocker()->forTransaction()->tryLock('foo', 1); }); @@ -212,7 +212,7 @@ public function testInfinitePostgresTimeoutSuccess(string $name): void sleep(1); try { - $result = DB::connection($name)->transaction(function (ConnectionInterface $conn) { + $result = DB::connection($name)->transaction(static function (ConnectionInterface $conn) { return $conn->advisoryLocker()->forTransaction()->tryLock('foo', -1); });