From 58e119f6a8619cc53254b4cf117291114be79b6f Mon Sep 17 00:00:00 2001
From: Anton Komarev
Date: Sat, 2 Aug 2025 11:07:42 +0300
Subject: [PATCH 01/15] Leave only *Handler API
---
README.md | 4 +-
src/Postgres/PostgresAdvisoryLocker.php | 45 +-------------
.../Postgres/PostgresAdvisoryLockerTest.php | 59 ++++++++++---------
3 files changed, 34 insertions(+), 74 deletions(-)
diff --git a/README.md b/README.md
index 4ef4186..254c59a 100644
--- a/README.md
+++ b/README.md
@@ -38,7 +38,7 @@ $locker = new \Cog\DbLocker\Postgres\PostgresAdvisoryLocker();
$lockId = \Cog\DbLocker\Postgres\PostgresLockKey::create('user', '4');
$dbConnection->beginTransaction();
-$lock = $locker->acquireSessionLevelLockHandler(
+$lock = $locker->acquireSessionLevelLock(
$dbConnection,
$lockId,
\Cog\DbLocker\Postgres\Enum\PostgresLockWaitModeEnum::NonBlocking,
@@ -61,7 +61,7 @@ $locker = new \Cog\DbLocker\Postgres\PostgresAdvisoryLocker();
$lockId = \Cog\DbLocker\Postgres\PostgresLockKey::create('user', '4');
try {
- $lock = $locker->acquireSessionLevelLockHandler(
+ $lock = $locker->acquireSessionLevelLock(
$dbConnection,
$lockId,
\Cog\DbLocker\Postgres\Enum\PostgresLockWaitModeEnum::NonBlocking,
diff --git a/src/Postgres/PostgresAdvisoryLocker.php b/src/Postgres/PostgresAdvisoryLocker.php
index 6ab3dc0..69a30d1 100644
--- a/src/Postgres/PostgresAdvisoryLocker.php
+++ b/src/Postgres/PostgresAdvisoryLocker.php
@@ -28,7 +28,7 @@ final class PostgresAdvisoryLocker
*
* TODO: Cover with tests
*/
- public function acquireTransactionLevelLockHandler(
+ public function acquireTransactionLevelLock(
PDO $dbConnection,
PostgresLockKey $postgresLockId,
PostgresLockWaitModeEnum $waitMode = PostgresLockWaitModeEnum::NonBlocking,
@@ -45,33 +45,13 @@ public function acquireTransactionLevelLockHandler(
);
}
- /**
- * Acquire a transaction-level advisory lock with configurable wait and access modes.
- *
- * TODO: Do we need low-level API?
- */
- public function acquireTransactionLevelLock(
- PDO $dbConnection,
- PostgresLockKey $postgresLockId,
- PostgresLockWaitModeEnum $waitMode = PostgresLockWaitModeEnum::NonBlocking,
- PostgresLockAccessModeEnum $accessMode = PostgresLockAccessModeEnum::Exclusive,
- ): bool {
- return $this->acquireLock(
- $dbConnection,
- $postgresLockId,
- PostgresLockLevelEnum::Transaction,
- $waitMode,
- $accessMode,
- );
- }
-
/**
* Acquire a session-level advisory lock with configurable wait and access modes.
*
* TODO: Write that transaction-level is recommended.
* TODO: Cover with tests
*/
- public function acquireSessionLevelLockHandler(
+ public function acquireSessionLevelLock(
PDO $dbConnection,
PostgresLockKey $postgresLockId,
PostgresLockWaitModeEnum $waitMode = PostgresLockWaitModeEnum::NonBlocking,
@@ -92,27 +72,6 @@ public function acquireSessionLevelLockHandler(
);
}
- /**
- * Acquire a session-level advisory lock with configurable wait and access modes.
- *
- * TODO: Write that transaction-level is recommended.
- * TODO: Do we need low-level API?
- */
- public function acquireSessionLevelLock(
- PDO $dbConnection,
- PostgresLockKey $postgresLockId,
- PostgresLockWaitModeEnum $waitMode = PostgresLockWaitModeEnum::NonBlocking,
- PostgresLockAccessModeEnum $accessMode = PostgresLockAccessModeEnum::Exclusive,
- ): bool {
- return $this->acquireLock(
- $dbConnection,
- $postgresLockId,
- PostgresLockLevelEnum::Session,
- $waitMode,
- $accessMode,
- );
- }
-
/**
* Release session level advisory lock.
*/
diff --git a/test/Integration/Postgres/PostgresAdvisoryLockerTest.php b/test/Integration/Postgres/PostgresAdvisoryLockerTest.php
index c7ec210..7beeef0 100644
--- a/test/Integration/Postgres/PostgresAdvisoryLockerTest.php
+++ b/test/Integration/Postgres/PostgresAdvisoryLockerTest.php
@@ -39,7 +39,7 @@ public function testItCanTryAcquireLockWithinSession(
accessMode: $accessMode,
);
- $this->assertTrue($isLockAcquired);
+ $this->assertTrue($isLockAcquired->wasAcquired);
$this->assertPgAdvisoryLocksCount(1);
$this->assertPgAdvisoryLockExistsInConnection($dbConnection, $postgresLockId, $accessMode);
}
@@ -71,7 +71,7 @@ public function testItCanTryAcquireLockWithinTransaction(
accessMode: $accessMode,
);
- $this->assertTrue($isLockAcquired);
+ $this->assertTrue($isLockAcquired->wasAcquired);
$this->assertPgAdvisoryLocksCount(1);
$this->assertPgAdvisoryLockExistsInConnection($dbConnection, $postgresLockId, $accessMode);
}
@@ -100,7 +100,7 @@ public function testItCanTryAcquireLockFromIntKeysCornerCases(): void
$postgresLockId,
);
- $this->assertTrue($isLockAcquired);
+ $this->assertTrue($isLockAcquired->wasAcquired);
$this->assertPgAdvisoryLocksCount(1);
$this->assertPgAdvisoryLockExistsInConnection($dbConnection, $postgresLockId);
}
@@ -133,17 +133,17 @@ public function testItCanTryAcquireLockInSameConnectionOnlyOnce(): void
$dbConnection = $this->initPostgresPdoConnection();
$postgresLockId = PostgresLockKey::create('test');
- $isLockAcquired1 = $locker->acquireSessionLevelLock(
+ $isLock1Acquired = $locker->acquireSessionLevelLock(
$dbConnection,
$postgresLockId,
);
- $isLockAcquired2 = $locker->acquireSessionLevelLock(
+ $isLock2Acquired = $locker->acquireSessionLevelLock(
$dbConnection,
$postgresLockId,
);
- $this->assertTrue($isLockAcquired1);
- $this->assertTrue($isLockAcquired2);
+ $this->assertTrue($isLock1Acquired->wasAcquired);
+ $this->assertTrue($isLock2Acquired->wasAcquired);
$this->assertPgAdvisoryLocksCount(1);
$this->assertPgAdvisoryLockExistsInConnection($dbConnection, $postgresLockId);
}
@@ -164,8 +164,8 @@ public function testItCanTryAcquireMultipleLocksInOneConnection(): void
$postgresLockId2,
);
- $this->assertTrue($isLock1Acquired);
- $this->assertTrue($isLock2Acquired);
+ $this->assertTrue($isLock1Acquired->wasAcquired);
+ $this->assertTrue($isLock2Acquired->wasAcquired);
$this->assertPgAdvisoryLocksCount(2);
$this->assertPgAdvisoryLockExistsInConnection($dbConnection, $postgresLockId1);
$this->assertPgAdvisoryLockExistsInConnection($dbConnection, $postgresLockId2);
@@ -177,17 +177,18 @@ public function testItCannotAcquireSameLockInTwoConnections(): void
$dbConnection1 = $this->initPostgresPdoConnection();
$dbConnection2 = $this->initPostgresPdoConnection();
$postgresLockId = PostgresLockKey::create('test');
- $locker->acquireSessionLevelLock(
+ // TODO: Fix corner-case: without variable its self-destructing instantly
+ $connection1Lock = $locker->acquireSessionLevelLock(
$dbConnection1,
$postgresLockId,
);
- $isLockAcquired = $locker->acquireSessionLevelLock(
+ $connection2Lock = $locker->acquireSessionLevelLock(
$dbConnection2,
$postgresLockId,
);
- $this->assertFalse($isLockAcquired);
+ $this->assertFalse($connection2Lock->wasAcquired);
$this->assertPgAdvisoryLocksCount(1);
$this->assertPgAdvisoryLockMissingInConnection($dbConnection2, $postgresLockId);
}
@@ -199,7 +200,7 @@ public function testItCanReleaseLock(
$locker = $this->initLocker();
$dbConnection = $this->initPostgresPdoConnection();
$postgresLockId = PostgresLockKey::create('test');
- $locker->acquireSessionLevelLock(
+ $connectionLock = $locker->acquireSessionLevelLock(
$dbConnection,
$postgresLockId,
accessMode: $accessMode,
@@ -235,7 +236,7 @@ public function testItCanNotReleaseLockOfDifferentModes(
$locker = $this->initLocker();
$dbConnection = $this->initPostgresPdoConnection();
$postgresLockId = PostgresLockKey::create('test');
- $locker->acquireSessionLevelLock(
+ $connectionLock = $locker->acquireSessionLevelLock(
$dbConnection,
$postgresLockId,
accessMode: $acquireMode,
@@ -271,11 +272,11 @@ public function testItCanReleaseLockTwiceIfAcquiredTwice(): void
$locker = $this->initLocker();
$dbConnection = $this->initPostgresPdoConnection();
$postgresLockId = PostgresLockKey::create('test');
- $locker->acquireSessionLevelLock(
+ $connectionLock1 = $locker->acquireSessionLevelLock(
$dbConnection,
$postgresLockId,
);
- $locker->acquireSessionLevelLock(
+ $connectionLock2 = $locker->acquireSessionLevelLock(
$dbConnection,
$postgresLockId,
);
@@ -308,7 +309,7 @@ public function testItCanTryAcquireLockInSecondConnectionAfterRelease(): void
$postgresLockId,
);
- $this->assertTrue($isLockAcquired);
+ $this->assertTrue($isLockAcquired->wasAcquired);
$this->assertPgAdvisoryLocksCount(1);
$this->assertPgAdvisoryLockExistsInConnection($dbConnection2, $postgresLockId);
}
@@ -319,23 +320,23 @@ public function testItCannotAcquireLockInSecondConnectionAfterOneReleaseTwiceLoc
$dbConnection1 = $this->initPostgresPdoConnection();
$dbConnection2 = $this->initPostgresPdoConnection();
$postgresLockId = PostgresLockKey::create('test');
- $locker->acquireSessionLevelLock(
+ $connection1Lock1 = $locker->acquireSessionLevelLock(
$dbConnection1,
$postgresLockId,
);
- $locker->acquireSessionLevelLock(
+ $connection1Lock2 = $locker->acquireSessionLevelLock(
$dbConnection1,
$postgresLockId,
);
$isLockReleased = $locker->releaseSessionLevelLock($dbConnection1, $postgresLockId);
- $isLockAcquired = $locker->acquireSessionLevelLock(
+ $connection2Lock = $locker->acquireSessionLevelLock(
$dbConnection2,
$postgresLockId,
);
$this->assertTrue($isLockReleased);
- $this->assertFalse($isLockAcquired);
+ $this->assertFalse($connection2Lock->wasAcquired);
$this->assertPgAdvisoryLocksCount(1);
$this->assertPgAdvisoryLockExistsInConnection($dbConnection1, $postgresLockId);
$this->assertPgAdvisoryLockMissingInConnection($dbConnection2, $postgresLockId);
@@ -359,7 +360,7 @@ public function testItCannotReleaseLockIfAcquiredInOtherConnection(): void
$dbConnection1 = $this->initPostgresPdoConnection();
$dbConnection2 = $this->initPostgresPdoConnection();
$postgresLockId = PostgresLockKey::create('test');
- $locker->acquireSessionLevelLock(
+ $connection1Lock = $locker->acquireSessionLevelLock(
$dbConnection1,
$postgresLockId,
);
@@ -412,19 +413,19 @@ public function testItCanReleaseAllLocksInConnectionButKeepsOtherConnectionLocks
$postgresLockId2 = PostgresLockKey::create('test2');
$postgresLockId3 = PostgresLockKey::create('test3');
$postgresLockId4 = PostgresLockKey::create('test4');
- $locker->acquireSessionLevelLock(
+ $connect1Lock1 = $locker->acquireSessionLevelLock(
$dbConnection1,
$postgresLockId1,
);
- $locker->acquireSessionLevelLock(
+ $connect1Lock2 = $locker->acquireSessionLevelLock(
$dbConnection1,
$postgresLockId2,
);
- $locker->acquireSessionLevelLock(
+ $connect2Lock3 = $locker->acquireSessionLevelLock(
$dbConnection2,
$postgresLockId3,
);
- $locker->acquireSessionLevelLock(
+ $connect2Lock4 = $locker->acquireSessionLevelLock(
$dbConnection2,
$postgresLockId4,
);
@@ -460,17 +461,17 @@ public function testItCannotAcquireLockInSecondConnectionIfTakenWithinTransactio
$dbConnection2 = $this->initPostgresPdoConnection();
$postgresLockId = PostgresLockKey::create('test');
$dbConnection1->beginTransaction();
- $locker->acquireSessionLevelLock(
+ $connection1Lock = $locker->acquireSessionLevelLock(
$dbConnection1,
$postgresLockId,
);
- $isLockAcquired = $locker->acquireSessionLevelLock(
+ $connection2Lock = $locker->acquireSessionLevelLock(
$dbConnection2,
$postgresLockId,
);
- $this->assertFalse($isLockAcquired);
+ $this->assertFalse($connection2Lock->wasAcquired);
$this->assertPgAdvisoryLocksCount(1);
$this->assertPgAdvisoryLockExistsInConnection($dbConnection1, $postgresLockId);
}
From b72f27b765ef17bf12ff06810980e8c7652ca314 Mon Sep 17 00:00:00 2001
From: Anton Komarev
Date: Sun, 3 Aug 2025 08:15:52 +0300
Subject: [PATCH 02/15] Leave only *Handler API
---
src/Postgres/PostgresAdvisoryLocker.php | 22 ++++++++++++++++++----
1 file changed, 18 insertions(+), 4 deletions(-)
diff --git a/src/Postgres/PostgresAdvisoryLocker.php b/src/Postgres/PostgresAdvisoryLocker.php
index 69a30d1..9d1a0d1 100644
--- a/src/Postgres/PostgresAdvisoryLocker.php
+++ b/src/Postgres/PostgresAdvisoryLocker.php
@@ -25,8 +25,6 @@ final class PostgresAdvisoryLocker
{
/**
* Acquire a transaction-level advisory lock with configurable wait and access modes.
- *
- * TODO: Cover with tests
*/
public function acquireTransactionLevelLock(
PDO $dbConnection,
@@ -48,8 +46,24 @@ public function acquireTransactionLevelLock(
/**
* Acquire a session-level advisory lock with configurable wait and access modes.
*
- * TODO: Write that transaction-level is recommended.
- * TODO: Cover with tests
+ * ⚠️ You MUST retain the returned handle in a variable.
+ * If the handle is not stored and is immediately garbage collected,
+ * the lock will be released in the lock handle __destruct method.
+ *
+ * @example
+ * $handle = $locker->acquireSessionLevelLock(...); // ✅ Lock held
+ *
+ * $locker->acquireSessionLevelLock(...); // ❌ Lock immediately released
+ *
+ * ⚠️ Transaction-level advisory locks are strongly preferred over session-level locks.
+ * Session-level locks persist beyond transactions and may lead to deadlocks
+ * or require manual cleanup (e.g. `pg_advisory_unlock_all()`).
+ *
+ * Use session-level locks only when transactional locks are not suitable
+ * (transactions are not possible or redundant).
+ *
+ * @see SessionLevelLockHandle::__destruct
+ * @see acquireTransactionLevelLock() for preferred locking strategy.
*/
public function acquireSessionLevelLock(
PDO $dbConnection,
From a9e0c84363c493a4e3d3532d7b4881fe1c506be8 Mon Sep 17 00:00:00 2001
From: Anton Komarev
Date: Sun, 3 Aug 2025 08:34:43 +0300
Subject: [PATCH 03/15] Leave only *Handler API
---
src/Postgres/PostgresAdvisoryLocker.php | 87 ++++++++++++++++++-------
1 file changed, 65 insertions(+), 22 deletions(-)
diff --git a/src/Postgres/PostgresAdvisoryLocker.php b/src/Postgres/PostgresAdvisoryLocker.php
index 9d1a0d1..dd201f3 100644
--- a/src/Postgres/PostgresAdvisoryLocker.php
+++ b/src/Postgres/PostgresAdvisoryLocker.php
@@ -28,14 +28,14 @@ final class PostgresAdvisoryLocker
*/
public function acquireTransactionLevelLock(
PDO $dbConnection,
- PostgresLockKey $postgresLockId,
+ PostgresLockKey $postgresLockKey,
PostgresLockWaitModeEnum $waitMode = PostgresLockWaitModeEnum::NonBlocking,
PostgresLockAccessModeEnum $accessMode = PostgresLockAccessModeEnum::Exclusive,
): TransactionLevelLockHandle {
return new TransactionLevelLockHandle(
wasAcquired: $this->acquireLock(
$dbConnection,
- $postgresLockId,
+ $postgresLockKey,
PostgresLockLevelEnum::Transaction,
$waitMode,
$accessMode,
@@ -49,36 +49,32 @@ public function acquireTransactionLevelLock(
* ⚠️ You MUST retain the returned handle in a variable.
* If the handle is not stored and is immediately garbage collected,
* the lock will be released in the lock handle __destruct method.
+ * @see SessionLevelLockHandle::__destruct
*
* @example
* $handle = $locker->acquireSessionLevelLock(...); // ✅ Lock held
*
* $locker->acquireSessionLevelLock(...); // ❌ Lock immediately released
*
- * ⚠️ Transaction-level advisory locks are strongly preferred over session-level locks.
- * Session-level locks persist beyond transactions and may lead to deadlocks
- * or require manual cleanup (e.g. `pg_advisory_unlock_all()`).
- *
- * Use session-level locks only when transactional locks are not suitable
- * (transactions are not possible or redundant).
- *
- * @see SessionLevelLockHandle::__destruct
+ * ⚠️ Transaction-level advisory locks are strongly preferred whenever possible,
+ * as they are automatically released at the end of a transaction and are less error-prone.
+ * Use session-level locks only when transactional context is not available.
* @see acquireTransactionLevelLock() for preferred locking strategy.
*/
public function acquireSessionLevelLock(
PDO $dbConnection,
- PostgresLockKey $postgresLockId,
+ PostgresLockKey $postgresLockKey,
PostgresLockWaitModeEnum $waitMode = PostgresLockWaitModeEnum::NonBlocking,
PostgresLockAccessModeEnum $accessMode = PostgresLockAccessModeEnum::Exclusive,
): SessionLevelLockHandle {
return new SessionLevelLockHandle(
$dbConnection,
$this,
- $postgresLockId,
+ $postgresLockKey,
$accessMode,
wasAcquired: $this->acquireLock(
$dbConnection,
- $postgresLockId,
+ $postgresLockKey,
PostgresLockLevelEnum::Session,
$waitMode,
$accessMode,
@@ -86,25 +82,72 @@ public function acquireSessionLevelLock(
);
}
+ /**
+ * Acquires a session-level advisory lock and ensures its release after executing the callback.
+ *
+ * This method guarantees that the lock is released even if an exception is thrown during execution.
+ * Useful for safely wrapping critical sections that require locking.
+ *
+ * If the lock was not acquired (i.e., `wasAcquired` is `false`), it is up to the callback
+ * to decide how to handle the situation (e.g., retry, throw, log, or silently skip).
+ *
+ * ⚠️ Transaction-level advisory locks are strongly preferred whenever possible,
+ * as they are automatically released at the end of a transaction and are less error-prone.
+ * Use session-level locks only when transactional context is not available.
+ * @see acquireTransactionLevelLock() for preferred locking strategy.
+ *
+ * @param PDO $dbConnection Active database connection.
+ * @param PostgresLockKey $postgresLockKey Lock key to be acquired.
+ * @param callable(SessionLevelLockHandle): TReturn $callback A callback that receives the lock handle.
+ * @param PostgresLockWaitModeEnum $waitMode Whether to wait for the lock or fail immediately. Default is non-blocking.
+ * @param PostgresLockAccessModeEnum $accessMode Whether to acquire a shared or exclusive lock. Default is exclusive.
+ * @return TReturn The return value of the callback.
+ *
+ * @template TReturn
+ *
+ * TODO: Cover with tests
+ */
+ public function withSessionLevelLock(
+ PDO $dbConnection,
+ PostgresLockKey $postgresLockKey,
+ callable $callback,
+ PostgresLockWaitModeEnum $waitMode = PostgresLockWaitModeEnum::NonBlocking,
+ PostgresLockAccessModeEnum $accessMode = PostgresLockAccessModeEnum::Exclusive,
+ ): mixed {
+ $lockHandle = $this->acquireSessionLevelLock(
+ $dbConnection,
+ $postgresLockKey,
+ $waitMode,
+ $accessMode,
+ );
+
+ try {
+ return $callback($lockHandle);
+ }
+ finally {
+ $lockHandle->release();
+ }
+ }
+
/**
* Release session level advisory lock.
*/
public function releaseSessionLevelLock(
PDO $dbConnection,
- PostgresLockKey $postgresLockId,
+ PostgresLockKey $postgresLockKey,
PostgresLockAccessModeEnum $accessMode = PostgresLockAccessModeEnum::Exclusive,
): bool {
$sql = match ($accessMode) {
PostgresLockAccessModeEnum::Exclusive => 'SELECT PG_ADVISORY_UNLOCK(:class_id, :object_id);',
PostgresLockAccessModeEnum::Share => 'SELECT PG_ADVISORY_UNLOCK_SHARED(:class_id, :object_id);',
};
- $sql .= " -- $postgresLockId->humanReadableValue";
+ $sql .= " -- $postgresLockKey->humanReadableValue";
$statement = $dbConnection->prepare($sql);
$statement->execute(
[
- 'class_id' => $postgresLockId->classId,
- 'object_id' => $postgresLockId->objectId,
+ 'class_id' => $postgresLockKey->classId,
+ 'object_id' => $postgresLockKey->objectId,
],
);
@@ -127,14 +170,14 @@ public function releaseAllSessionLevelLocks(
private function acquireLock(
PDO $dbConnection,
- PostgresLockKey $postgresLockId,
+ PostgresLockKey $postgresLockKey,
PostgresLockLevelEnum $level,
PostgresLockWaitModeEnum $waitMode = PostgresLockWaitModeEnum::NonBlocking,
PostgresLockAccessModeEnum $accessMode = PostgresLockAccessModeEnum::Exclusive,
): bool {
if ($level === PostgresLockLevelEnum::Transaction && $dbConnection->inTransaction() === false) {
throw new LogicException(
- "Transaction-level advisory lock `$postgresLockId->humanReadableValue` cannot be acquired outside of transaction",
+ "Transaction-level advisory lock `$postgresLockKey->humanReadableValue` cannot be acquired outside of transaction",
);
}
@@ -180,13 +223,13 @@ private function acquireLock(
PostgresLockAccessModeEnum::Share,
] => 'SELECT PG_ADVISORY_LOCK_SHARED(:class_id, :object_id);',
};
- $sql .= " -- $postgresLockId->humanReadableValue";
+ $sql .= " -- $postgresLockKey->humanReadableValue";
$statement = $dbConnection->prepare($sql);
$statement->execute(
[
- 'class_id' => $postgresLockId->classId,
- 'object_id' => $postgresLockId->objectId,
+ 'class_id' => $postgresLockKey->classId,
+ 'object_id' => $postgresLockKey->objectId,
],
);
From e37960f6bb99d6d5067054686d09ca881d861e1a Mon Sep 17 00:00:00 2001
From: Anton Komarev
Date: Sun, 3 Aug 2025 08:39:14 +0300
Subject: [PATCH 04/15] Leave only *Handler API
---
src/Postgres/PostgresAdvisoryLocker.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Postgres/PostgresAdvisoryLocker.php b/src/Postgres/PostgresAdvisoryLocker.php
index dd201f3..4f9c4b4 100644
--- a/src/Postgres/PostgresAdvisoryLocker.php
+++ b/src/Postgres/PostgresAdvisoryLocker.php
@@ -107,7 +107,7 @@ public function acquireSessionLevelLock(
*
* TODO: Cover with tests
*/
- public function withSessionLevelLock(
+ public function withinSessionLevelLock(
PDO $dbConnection,
PostgresLockKey $postgresLockKey,
callable $callback,
From 0205b4b83f8364af0c70bcb9984565d07feed58b Mon Sep 17 00:00:00 2001
From: Anton Komarev
Date: Sun, 3 Aug 2025 08:46:43 +0300
Subject: [PATCH 05/15] Leave only *Handler API
---
.../LockHandle/SessionLevelLockHandle.php | 4 +-
.../AbstractIntegrationTestCase.php | 22 +--
.../Postgres/PostgresAdvisoryLockerTest.php | 174 +++++++++---------
test/Unit/Postgres/PostgresLockKeyTest.php | 36 ++--
4 files changed, 118 insertions(+), 118 deletions(-)
diff --git a/src/Postgres/LockHandle/SessionLevelLockHandle.php b/src/Postgres/LockHandle/SessionLevelLockHandle.php
index e3298c3..23b0fdc 100644
--- a/src/Postgres/LockHandle/SessionLevelLockHandle.php
+++ b/src/Postgres/LockHandle/SessionLevelLockHandle.php
@@ -28,7 +28,7 @@ final class SessionLevelLockHandle
public function __construct(
private readonly PDO $dbConnection,
private readonly PostgresAdvisoryLocker $locker,
- public readonly PostgresLockKey $lockId,
+ public readonly PostgresLockKey $lockKey,
public readonly PostgresLockAccessModeEnum $accessMode,
public readonly bool $wasAcquired,
) {}
@@ -49,7 +49,7 @@ public function release(): bool
$wasReleased = $this->locker->releaseSessionLevelLock(
$this->dbConnection,
- $this->lockId,
+ $this->lockKey,
);
if ($wasReleased) {
diff --git a/test/Integration/AbstractIntegrationTestCase.php b/test/Integration/AbstractIntegrationTestCase.php
index cbc1e73..914b7a2 100644
--- a/test/Integration/AbstractIntegrationTestCase.php
+++ b/test/Integration/AbstractIntegrationTestCase.php
@@ -44,39 +44,39 @@ protected function initPostgresPdoConnection(): PDO
protected function assertPgAdvisoryLockExistsInConnection(
PDO $dbConnection,
- PostgresLockKey $postgresLockId,
+ PostgresLockKey $postgresLockKey,
PostgresLockAccessModeEnum $mode = PostgresLockAccessModeEnum::Exclusive,
): void {
$row = $this->findPostgresAdvisoryLockInConnection(
$dbConnection,
- $postgresLockId,
+ $postgresLockKey,
$mode,
);
- $lockIdString = $postgresLockId->humanReadableValue;
+ $lockKeyString = $postgresLockKey->humanReadableValue;
$this->assertTrue(
$row !== null,
- "Lock id `$lockIdString` does not exists",
+ "Lock id `$lockKeyString` does not exists",
);
}
protected function assertPgAdvisoryLockMissingInConnection(
PDO $dbConnection,
- PostgresLockKey $postgresLockId,
+ PostgresLockKey $postgresLockKey,
PostgresLockAccessModeEnum $mode = PostgresLockAccessModeEnum::Exclusive,
): void {
$row = $this->findPostgresAdvisoryLockInConnection(
$dbConnection,
- $postgresLockId,
+ $postgresLockKey,
$mode,
);
- $lockIdString = $postgresLockId->humanReadableValue;
+ $lockKeyString = $postgresLockKey->humanReadableValue;
$this->assertTrue(
$row === null,
- "Lock id `$lockIdString` is present",
+ "Lock id `$lockKeyString` is present",
);
}
@@ -95,7 +95,7 @@ protected function assertPgAdvisoryLocksCount(
private function findPostgresAdvisoryLockInConnection(
PDO $dbConnection,
- PostgresLockKey $postgresLockId,
+ PostgresLockKey $postgresLockKey,
PostgresLockAccessModeEnum $mode,
): object | null {
$statement = $dbConnection->prepare(
@@ -112,8 +112,8 @@ private function findPostgresAdvisoryLockInConnection(
);
$statement->execute(
[
- 'lock_class_id' => $postgresLockId->classId,
- 'lock_object_id' => $postgresLockId->objectId,
+ 'lock_class_id' => $postgresLockKey->classId,
+ 'lock_object_id' => $postgresLockKey->objectId,
'lock_object_subid' => 2, // Using two keyed locks
'connection_pid' => $dbConnection->pgsqlGetPid(),
'mode' => $mode->value,
diff --git a/test/Integration/Postgres/PostgresAdvisoryLockerTest.php b/test/Integration/Postgres/PostgresAdvisoryLockerTest.php
index 7beeef0..c67dc31 100644
--- a/test/Integration/Postgres/PostgresAdvisoryLockerTest.php
+++ b/test/Integration/Postgres/PostgresAdvisoryLockerTest.php
@@ -31,17 +31,17 @@ public function testItCanTryAcquireLockWithinSession(
): void {
$locker = $this->initLocker();
$dbConnection = $this->initPostgresPdoConnection();
- $postgresLockId = PostgresLockKey::create('test');
+ $lockKey = PostgresLockKey::create('test');
$isLockAcquired = $locker->acquireSessionLevelLock(
$dbConnection,
- $postgresLockId,
+ $lockKey,
accessMode: $accessMode,
);
$this->assertTrue($isLockAcquired->wasAcquired);
$this->assertPgAdvisoryLocksCount(1);
- $this->assertPgAdvisoryLockExistsInConnection($dbConnection, $postgresLockId, $accessMode);
+ $this->assertPgAdvisoryLockExistsInConnection($dbConnection, $lockKey, $accessMode);
}
public static function provideItCanTryAcquireLockWithinSessionData(): array
@@ -62,18 +62,18 @@ public function testItCanTryAcquireLockWithinTransaction(
): void {
$locker = $this->initLocker();
$dbConnection = $this->initPostgresPdoConnection();
- $postgresLockId = PostgresLockKey::create('test');
+ $lockKey = PostgresLockKey::create('test');
$dbConnection->beginTransaction();
$isLockAcquired = $locker->acquireTransactionLevelLock(
$dbConnection,
- $postgresLockId,
+ $lockKey,
accessMode: $accessMode,
);
$this->assertTrue($isLockAcquired->wasAcquired);
$this->assertPgAdvisoryLocksCount(1);
- $this->assertPgAdvisoryLockExistsInConnection($dbConnection, $postgresLockId, $accessMode);
+ $this->assertPgAdvisoryLockExistsInConnection($dbConnection, $lockKey, $accessMode);
}
public static function provideItCanTryAcquireLockWithinTransactionData(): array
@@ -93,16 +93,16 @@ public function testItCanTryAcquireLockFromIntKeysCornerCases(): void
{
$locker = $this->initLocker();
$dbConnection = $this->initPostgresPdoConnection();
- $postgresLockId = PostgresLockKey::createFromInternalIds(self::DB_INT32_VALUE_MIN, 0);
+ $lockKey = PostgresLockKey::createFromInternalIds(self::DB_INT32_VALUE_MIN, 0);
$isLockAcquired = $locker->acquireSessionLevelLock(
$dbConnection,
- $postgresLockId,
+ $lockKey,
);
$this->assertTrue($isLockAcquired->wasAcquired);
$this->assertPgAdvisoryLocksCount(1);
- $this->assertPgAdvisoryLockExistsInConnection($dbConnection, $postgresLockId);
+ $this->assertPgAdvisoryLockExistsInConnection($dbConnection, $lockKey);
}
public static function provideItCanTryAcquireLockFromIntKeysCornerCasesData(): array
@@ -131,44 +131,44 @@ public function testItCanTryAcquireLockInSameConnectionOnlyOnce(): void
{
$locker = $this->initLocker();
$dbConnection = $this->initPostgresPdoConnection();
- $postgresLockId = PostgresLockKey::create('test');
+ $lockKey = PostgresLockKey::create('test');
$isLock1Acquired = $locker->acquireSessionLevelLock(
$dbConnection,
- $postgresLockId,
+ $lockKey,
);
$isLock2Acquired = $locker->acquireSessionLevelLock(
$dbConnection,
- $postgresLockId,
+ $lockKey,
);
$this->assertTrue($isLock1Acquired->wasAcquired);
$this->assertTrue($isLock2Acquired->wasAcquired);
$this->assertPgAdvisoryLocksCount(1);
- $this->assertPgAdvisoryLockExistsInConnection($dbConnection, $postgresLockId);
+ $this->assertPgAdvisoryLockExistsInConnection($dbConnection, $lockKey);
}
public function testItCanTryAcquireMultipleLocksInOneConnection(): void
{
$locker = $this->initLocker();
$dbConnection = $this->initPostgresPdoConnection();
- $postgresLockId1 = PostgresLockKey::create('test1');
- $postgresLockId2 = PostgresLockKey::create('test2');
+ $lockKey1 = PostgresLockKey::create('test1');
+ $lockKey2 = PostgresLockKey::create('test2');
$isLock1Acquired = $locker->acquireSessionLevelLock(
$dbConnection,
- $postgresLockId1,
+ $lockKey1,
);
$isLock2Acquired = $locker->acquireSessionLevelLock(
$dbConnection,
- $postgresLockId2,
+ $lockKey2,
);
$this->assertTrue($isLock1Acquired->wasAcquired);
$this->assertTrue($isLock2Acquired->wasAcquired);
$this->assertPgAdvisoryLocksCount(2);
- $this->assertPgAdvisoryLockExistsInConnection($dbConnection, $postgresLockId1);
- $this->assertPgAdvisoryLockExistsInConnection($dbConnection, $postgresLockId2);
+ $this->assertPgAdvisoryLockExistsInConnection($dbConnection, $lockKey1);
+ $this->assertPgAdvisoryLockExistsInConnection($dbConnection, $lockKey2);
}
public function testItCannotAcquireSameLockInTwoConnections(): void
@@ -176,21 +176,21 @@ public function testItCannotAcquireSameLockInTwoConnections(): void
$locker = $this->initLocker();
$dbConnection1 = $this->initPostgresPdoConnection();
$dbConnection2 = $this->initPostgresPdoConnection();
- $postgresLockId = PostgresLockKey::create('test');
+ $lockKey = PostgresLockKey::create('test');
// TODO: Fix corner-case: without variable its self-destructing instantly
$connection1Lock = $locker->acquireSessionLevelLock(
$dbConnection1,
- $postgresLockId,
+ $lockKey,
);
$connection2Lock = $locker->acquireSessionLevelLock(
$dbConnection2,
- $postgresLockId,
+ $lockKey,
);
$this->assertFalse($connection2Lock->wasAcquired);
$this->assertPgAdvisoryLocksCount(1);
- $this->assertPgAdvisoryLockMissingInConnection($dbConnection2, $postgresLockId);
+ $this->assertPgAdvisoryLockMissingInConnection($dbConnection2, $lockKey);
}
#[DataProvider('provideItCanReleaseLockData')]
@@ -199,16 +199,16 @@ public function testItCanReleaseLock(
): void {
$locker = $this->initLocker();
$dbConnection = $this->initPostgresPdoConnection();
- $postgresLockId = PostgresLockKey::create('test');
+ $lockKey = PostgresLockKey::create('test');
$connectionLock = $locker->acquireSessionLevelLock(
$dbConnection,
- $postgresLockId,
+ $lockKey,
accessMode: $accessMode,
);
$isLockReleased = $locker->releaseSessionLevelLock(
$dbConnection,
- $postgresLockId,
+ $lockKey,
accessMode: $accessMode,
);
@@ -235,22 +235,22 @@ public function testItCanNotReleaseLockOfDifferentModes(
): void {
$locker = $this->initLocker();
$dbConnection = $this->initPostgresPdoConnection();
- $postgresLockId = PostgresLockKey::create('test');
+ $lockKey = PostgresLockKey::create('test');
$connectionLock = $locker->acquireSessionLevelLock(
$dbConnection,
- $postgresLockId,
+ $lockKey,
accessMode: $acquireMode,
);
$isLockReleased = $locker->releaseSessionLevelLock(
$dbConnection,
- $postgresLockId,
+ $lockKey,
accessMode: $releaseMode,
);
$this->assertFalse($isLockReleased);
$this->assertPgAdvisoryLocksCount(1);
- $this->assertPgAdvisoryLockExistsInConnection($dbConnection, $postgresLockId, $acquireMode);
+ $this->assertPgAdvisoryLockExistsInConnection($dbConnection, $lockKey, $acquireMode);
}
public static function provideItCanNotReleaseLockOfDifferentModesData(): array
@@ -271,18 +271,18 @@ public function testItCanReleaseLockTwiceIfAcquiredTwice(): void
{
$locker = $this->initLocker();
$dbConnection = $this->initPostgresPdoConnection();
- $postgresLockId = PostgresLockKey::create('test');
+ $lockKey = PostgresLockKey::create('test');
$connectionLock1 = $locker->acquireSessionLevelLock(
$dbConnection,
- $postgresLockId,
+ $lockKey,
);
$connectionLock2 = $locker->acquireSessionLevelLock(
$dbConnection,
- $postgresLockId,
+ $lockKey,
);
- $isLockReleased1 = $locker->releaseSessionLevelLock($dbConnection, $postgresLockId);
- $isLockReleased2 = $locker->releaseSessionLevelLock($dbConnection, $postgresLockId);
+ $isLockReleased1 = $locker->releaseSessionLevelLock($dbConnection, $lockKey);
+ $isLockReleased2 = $locker->releaseSessionLevelLock($dbConnection, $lockKey);
$this->assertTrue($isLockReleased1);
$this->assertTrue($isLockReleased2);
@@ -294,24 +294,24 @@ public function testItCanTryAcquireLockInSecondConnectionAfterRelease(): void
$locker = $this->initLocker();
$dbConnection1 = $this->initPostgresPdoConnection();
$dbConnection2 = $this->initPostgresPdoConnection();
- $postgresLockId = PostgresLockKey::create('test');
+ $lockKey = PostgresLockKey::create('test');
$locker->acquireSessionLevelLock(
$dbConnection1,
- $postgresLockId,
+ $lockKey,
);
$locker->releaseSessionLevelLock(
$dbConnection1,
- $postgresLockId,
+ $lockKey,
);
$isLockAcquired = $locker->acquireSessionLevelLock(
$dbConnection2,
- $postgresLockId,
+ $lockKey,
);
$this->assertTrue($isLockAcquired->wasAcquired);
$this->assertPgAdvisoryLocksCount(1);
- $this->assertPgAdvisoryLockExistsInConnection($dbConnection2, $postgresLockId);
+ $this->assertPgAdvisoryLockExistsInConnection($dbConnection2, $lockKey);
}
public function testItCannotAcquireLockInSecondConnectionAfterOneReleaseTwiceLocked(): void
@@ -319,36 +319,36 @@ public function testItCannotAcquireLockInSecondConnectionAfterOneReleaseTwiceLoc
$locker = $this->initLocker();
$dbConnection1 = $this->initPostgresPdoConnection();
$dbConnection2 = $this->initPostgresPdoConnection();
- $postgresLockId = PostgresLockKey::create('test');
+ $lockKey = PostgresLockKey::create('test');
$connection1Lock1 = $locker->acquireSessionLevelLock(
$dbConnection1,
- $postgresLockId,
+ $lockKey,
);
$connection1Lock2 = $locker->acquireSessionLevelLock(
$dbConnection1,
- $postgresLockId,
+ $lockKey,
);
- $isLockReleased = $locker->releaseSessionLevelLock($dbConnection1, $postgresLockId);
+ $isLockReleased = $locker->releaseSessionLevelLock($dbConnection1, $lockKey);
$connection2Lock = $locker->acquireSessionLevelLock(
$dbConnection2,
- $postgresLockId,
+ $lockKey,
);
$this->assertTrue($isLockReleased);
$this->assertFalse($connection2Lock->wasAcquired);
$this->assertPgAdvisoryLocksCount(1);
- $this->assertPgAdvisoryLockExistsInConnection($dbConnection1, $postgresLockId);
- $this->assertPgAdvisoryLockMissingInConnection($dbConnection2, $postgresLockId);
+ $this->assertPgAdvisoryLockExistsInConnection($dbConnection1, $lockKey);
+ $this->assertPgAdvisoryLockMissingInConnection($dbConnection2, $lockKey);
}
public function testItCannotReleaseLockIfNotAcquired(): void
{
$locker = $this->initLocker();
$dbConnection = $this->initPostgresPdoConnection();
- $postgresLockId = PostgresLockKey::create('test');
+ $lockKey = PostgresLockKey::create('test');
- $isLockReleased = $locker->releaseSessionLevelLock($dbConnection, $postgresLockId);
+ $isLockReleased = $locker->releaseSessionLevelLock($dbConnection, $lockKey);
$this->assertFalse($isLockReleased);
$this->assertPgAdvisoryLocksCount(0);
@@ -359,17 +359,17 @@ public function testItCannotReleaseLockIfAcquiredInOtherConnection(): void
$locker = $this->initLocker();
$dbConnection1 = $this->initPostgresPdoConnection();
$dbConnection2 = $this->initPostgresPdoConnection();
- $postgresLockId = PostgresLockKey::create('test');
+ $lockKey = PostgresLockKey::create('test');
$connection1Lock = $locker->acquireSessionLevelLock(
$dbConnection1,
- $postgresLockId,
+ $lockKey,
);
- $isLockReleased = $locker->releaseSessionLevelLock($dbConnection2, $postgresLockId);
+ $isLockReleased = $locker->releaseSessionLevelLock($dbConnection2, $lockKey);
$this->assertFalse($isLockReleased);
$this->assertPgAdvisoryLocksCount(1);
- $this->assertPgAdvisoryLockExistsInConnection($dbConnection1, $postgresLockId);
+ $this->assertPgAdvisoryLockExistsInConnection($dbConnection1, $lockKey);
}
public function testItCanReleaseAllLocksInConnection(): void
@@ -409,32 +409,32 @@ public function testItCanReleaseAllLocksInConnectionButKeepsOtherConnectionLocks
$locker = $this->initLocker();
$dbConnection1 = $this->initPostgresPdoConnection();
$dbConnection2 = $this->initPostgresPdoConnection();
- $postgresLockId1 = PostgresLockKey::create('test');
- $postgresLockId2 = PostgresLockKey::create('test2');
- $postgresLockId3 = PostgresLockKey::create('test3');
- $postgresLockId4 = PostgresLockKey::create('test4');
+ $lockKey1 = PostgresLockKey::create('test');
+ $lockKey2 = PostgresLockKey::create('test2');
+ $lockKey3 = PostgresLockKey::create('test3');
+ $lockKey4 = PostgresLockKey::create('test4');
$connect1Lock1 = $locker->acquireSessionLevelLock(
$dbConnection1,
- $postgresLockId1,
+ $lockKey1,
);
$connect1Lock2 = $locker->acquireSessionLevelLock(
$dbConnection1,
- $postgresLockId2,
+ $lockKey2,
);
$connect2Lock3 = $locker->acquireSessionLevelLock(
$dbConnection2,
- $postgresLockId3,
+ $lockKey3,
);
$connect2Lock4 = $locker->acquireSessionLevelLock(
$dbConnection2,
- $postgresLockId4,
+ $lockKey4,
);
$locker->releaseAllSessionLevelLocks($dbConnection1);
$this->assertPgAdvisoryLocksCount(2);
- $this->assertPgAdvisoryLockExistsInConnection($dbConnection2, $postgresLockId3);
- $this->assertPgAdvisoryLockExistsInConnection($dbConnection2, $postgresLockId4);
+ $this->assertPgAdvisoryLockExistsInConnection($dbConnection2, $lockKey3);
+ $this->assertPgAdvisoryLockExistsInConnection($dbConnection2, $lockKey4);
}
public function testItCannotAcquireLockWithinTransactionNotInTransaction(): void
@@ -446,11 +446,11 @@ public function testItCannotAcquireLockWithinTransactionNotInTransaction(): void
$locker = $this->initLocker();
$dbConnection = $this->initPostgresPdoConnection();
- $postgresLockId = PostgresLockKey::create('test');
+ $lockKey = PostgresLockKey::create('test');
$locker->acquireTransactionLevelLock(
$dbConnection,
- $postgresLockId,
+ $lockKey,
);
}
@@ -459,66 +459,66 @@ public function testItCannotAcquireLockInSecondConnectionIfTakenWithinTransactio
$locker = $this->initLocker();
$dbConnection1 = $this->initPostgresPdoConnection();
$dbConnection2 = $this->initPostgresPdoConnection();
- $postgresLockId = PostgresLockKey::create('test');
+ $lockKey = PostgresLockKey::create('test');
$dbConnection1->beginTransaction();
$connection1Lock = $locker->acquireSessionLevelLock(
$dbConnection1,
- $postgresLockId,
+ $lockKey,
);
$connection2Lock = $locker->acquireSessionLevelLock(
$dbConnection2,
- $postgresLockId,
+ $lockKey,
);
$this->assertFalse($connection2Lock->wasAcquired);
$this->assertPgAdvisoryLocksCount(1);
- $this->assertPgAdvisoryLockExistsInConnection($dbConnection1, $postgresLockId);
+ $this->assertPgAdvisoryLockExistsInConnection($dbConnection1, $lockKey);
}
public function testItCanAutoReleaseLockAcquiredWithinTransactionOnCommit(): void
{
$locker = $this->initLocker();
$dbConnection = $this->initPostgresPdoConnection();
- $postgresLockId = PostgresLockKey::create('test');
+ $lockKey = PostgresLockKey::create('test');
$dbConnection->beginTransaction();
$locker->acquireTransactionLevelLock(
$dbConnection,
- $postgresLockId,
+ $lockKey,
);
$dbConnection->commit();
$this->assertPgAdvisoryLocksCount(0);
- $this->assertPgAdvisoryLockMissingInConnection($dbConnection, $postgresLockId);
+ $this->assertPgAdvisoryLockMissingInConnection($dbConnection, $lockKey);
}
public function testItCanAutoReleaseLockAcquiredWithinTransactionOnRollback(): void
{
$locker = $this->initLocker();
$dbConnection = $this->initPostgresPdoConnection();
- $postgresLockId = PostgresLockKey::create('test');
+ $lockKey = PostgresLockKey::create('test');
$dbConnection->beginTransaction();
$locker->acquireTransactionLevelLock(
$dbConnection,
- $postgresLockId,
+ $lockKey,
);
$dbConnection->rollBack();
$this->assertPgAdvisoryLocksCount(0);
- $this->assertPgAdvisoryLockMissingInConnection($dbConnection, $postgresLockId);
+ $this->assertPgAdvisoryLockMissingInConnection($dbConnection, $lockKey);
}
public function testItCanAutoReleaseLockAcquiredWithinTransactionOnConnectionKill(): void
{
$locker = $this->initLocker();
$dbConnection = $this->initPostgresPdoConnection();
- $postgresLockId = PostgresLockKey::create('test');
+ $lockKey = PostgresLockKey::create('test');
$dbConnection->beginTransaction();
$locker->acquireTransactionLevelLock(
$dbConnection,
- $postgresLockId,
+ $lockKey,
);
$dbConnection = null;
@@ -530,41 +530,41 @@ public function testItCannotReleaseLockAcquiredWithinTransaction(): void
{
$locker = $this->initLocker();
$dbConnection = $this->initPostgresPdoConnection();
- $postgresLockId = PostgresLockKey::create('test');
+ $lockKey = PostgresLockKey::create('test');
$dbConnection->beginTransaction();
$locker->acquireTransactionLevelLock(
$dbConnection,
- $postgresLockId,
+ $lockKey,
);
- $isLockReleased = $locker->releaseSessionLevelLock($dbConnection, $postgresLockId);
+ $isLockReleased = $locker->releaseSessionLevelLock($dbConnection, $lockKey);
$this->assertFalse($isLockReleased);
$this->assertPgAdvisoryLocksCount(1);
- $this->assertPgAdvisoryLockExistsInConnection($dbConnection, $postgresLockId);
+ $this->assertPgAdvisoryLockExistsInConnection($dbConnection, $lockKey);
}
public function testItCannotReleaseAllLocksAcquiredWithinTransaction(): void
{
$locker = $this->initLocker();
$dbConnection = $this->initPostgresPdoConnection();
- $postgresLockId1 = PostgresLockKey::create('test');
- $postgresLockId2 = PostgresLockKey::create('test2');
+ $lockKey1 = PostgresLockKey::create('test');
+ $lockKey2 = PostgresLockKey::create('test2');
$locker->acquireSessionLevelLock(
$dbConnection,
- $postgresLockId1,
+ $lockKey1,
);
$dbConnection->beginTransaction();
$locker->acquireTransactionLevelLock(
$dbConnection,
- $postgresLockId2,
+ $lockKey2,
);
$locker->releaseAllSessionLevelLocks($dbConnection);
$this->assertPgAdvisoryLocksCount(1);
- $this->assertPgAdvisoryLockMissingInConnection($dbConnection, $postgresLockId1);
- $this->assertPgAdvisoryLockExistsInConnection($dbConnection, $postgresLockId2);
+ $this->assertPgAdvisoryLockMissingInConnection($dbConnection, $lockKey1);
+ $this->assertPgAdvisoryLockExistsInConnection($dbConnection, $lockKey2);
}
private function initLocker(): PostgresAdvisoryLocker
diff --git a/test/Unit/Postgres/PostgresLockKeyTest.php b/test/Unit/Postgres/PostgresLockKeyTest.php
index 4adb626..93d3d85 100644
--- a/test/Unit/Postgres/PostgresLockKeyTest.php
+++ b/test/Unit/Postgres/PostgresLockKeyTest.php
@@ -22,20 +22,20 @@ final class PostgresLockKeyTest extends AbstractUnitTestCase
private const DB_INT32_VALUE_MIN = -2_147_483_648;
private const DB_INT32_VALUE_MAX = 2_147_483_647;
- #[DataProvider('provideItCanCreatePostgresLockIdFromKeyValueData')]
- public function testItCanCreatePostgresLockIdFromKeyValue(
+ #[DataProvider('provideItCanCreatePostgresLockKeyFromNamespaceValueData')]
+ public function testItCanCreatePostgresLockKeyFromNamespaceValue(
string $key,
string $value,
int $expectedClassId,
int $expectedObjectId,
): void {
- $postgresLockId = PostgresLockKey::create($key, $value);
+ $lockKey = PostgresLockKey::create($key, $value);
- $this->assertSame($expectedClassId, $postgresLockId->classId);
- $this->assertSame($expectedObjectId, $postgresLockId->objectId);
+ $this->assertSame($expectedClassId, $lockKey->classId);
+ $this->assertSame($expectedObjectId, $lockKey->objectId);
}
- public static function provideItCanCreatePostgresLockIdFromKeyValueData(): array
+ public static function provideItCanCreatePostgresLockKeyFromNamespaceValueData(): array
{
return [
'key + empty value' => [
@@ -53,18 +53,18 @@ public static function provideItCanCreatePostgresLockIdFromKeyValueData(): array
];
}
- #[DataProvider('provideItCanCreatePostgresLockIdFromIntKeysData')]
- public function testItCanCreatePostgresLockIdFromIntKeys(
+ #[DataProvider('provideItCanCreatePostgresLockKeyFromIntKeysData')]
+ public function testItCanCreatePostgresLockKeyFromIntKeys(
int $classId,
int $objectId,
): void {
- $lockId = PostgresLockKey::createFromInternalIds($classId, $objectId);
+ $lockKey = PostgresLockKey::createFromInternalIds($classId, $objectId);
- $this->assertSame($classId, $lockId->classId);
- $this->assertSame($objectId, $lockId->objectId);
+ $this->assertSame($classId, $lockKey->classId);
+ $this->assertSame($objectId, $lockKey->objectId);
}
- public static function provideItCanCreatePostgresLockIdFromIntKeysData(): array
+ public static function provideItCanCreatePostgresLockKeyFromIntKeysData(): array
{
return [
'min class_id' => [
@@ -86,8 +86,8 @@ public static function provideItCanCreatePostgresLockIdFromIntKeysData(): array
];
}
- #[DataProvider('provideItCanCreatePostgresLockIdFromOutOfRangeIntKeysData')]
- public function testItCanNotCreatePostgresLockIdFromOutOfRangeIntKeys(
+ #[DataProvider('provideItCanCreatePostgresLockKeyFromOutOfRangeIntKeysData')]
+ public function testItCanNotCreatePostgresLockKeyFromOutOfRangeIntKeys(
int $classId,
int $objectId,
string $expectedExceptionMessage,
@@ -95,13 +95,13 @@ public function testItCanNotCreatePostgresLockIdFromOutOfRangeIntKeys(
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage($expectedExceptionMessage);
- $lockId = PostgresLockKey::createFromInternalIds($classId, $objectId);
+ $lockKey = PostgresLockKey::createFromInternalIds($classId, $objectId);
- $this->assertSame($classId, $lockId->classId);
- $this->assertSame($objectId, $lockId->objectId);
+ $this->assertSame($classId, $lockKey->classId);
+ $this->assertSame($objectId, $lockKey->objectId);
}
- public static function provideItCanCreatePostgresLockIdFromOutOfRangeIntKeysData(): array
+ public static function provideItCanCreatePostgresLockKeyFromOutOfRangeIntKeysData(): array
{
return [
'min class_id' => [
From 8cf80142aff7cd63ec844d6d78fb2a09412c5b21 Mon Sep 17 00:00:00 2001
From: Anton Komarev
Date: Mon, 4 Aug 2025 09:23:11 +0300
Subject: [PATCH 06/15] Leave only *Handler API
---
README.md | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/README.md b/README.md
index 254c59a..6c48ef5 100644
--- a/README.md
+++ b/README.md
@@ -54,6 +54,30 @@ $dbConnection->commit();
#### Session-level advisory lock
+Callback API
+
+```php
+$dbConnection = new PDO($dsn, $username, $password);
+
+$locker = new \Cog\DbLocker\Postgres\PostgresAdvisoryLocker();
+$lockId = \Cog\DbLocker\Postgres\PostgresLockKey::create('user', '4');
+
+$lock = $locker->withinSessionLevelLock(
+ $dbConnection,
+ $lockId,
+ function (\Cog\DbLocker\Postgres\LockHandle\SessionLevelLockHandle $lock): Payment {
+ if ($lock->wasAcquired) {
+ // Execute logic if lock was successful
+ } else {
+ // Execute logic if lock acquisition has been failed
+ }
+ }
+ \Cog\DbLocker\Postgres\Enum\PostgresLockWaitModeEnum::NonBlocking,
+ \Cog\DbLocker\Postgres\Enum\PostgresLockAccessModeEnum::Exclusive,
+);
+```
+
+Low-level API
```php
$dbConnection = new PDO($dsn, $username, $password);
From 0a477c33a34c43944f8e0229ebf992171120e249 Mon Sep 17 00:00:00 2001
From: Anton Komarev
Date: Mon, 4 Aug 2025 09:26:30 +0300
Subject: [PATCH 07/15] Leave only *Handler API
---
README.md | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 6c48ef5..64bd582 100644
--- a/README.md
+++ b/README.md
@@ -62,16 +62,18 @@ $dbConnection = new PDO($dsn, $username, $password);
$locker = new \Cog\DbLocker\Postgres\PostgresAdvisoryLocker();
$lockId = \Cog\DbLocker\Postgres\PostgresLockKey::create('user', '4');
-$lock = $locker->withinSessionLevelLock(
+$payment = $locker->withinSessionLevelLock(
$dbConnection,
$lockId,
- function (\Cog\DbLocker\Postgres\LockHandle\SessionLevelLockHandle $lock): Payment {
+ function (
+ \Cog\DbLocker\Postgres\LockHandle\SessionLevelLockHandle $lock,
+ ): Payment { // Define a type of $payment variable, and it will be resolved by analyzers
if ($lock->wasAcquired) {
// Execute logic if lock was successful
} else {
// Execute logic if lock acquisition has been failed
}
- }
+ },
\Cog\DbLocker\Postgres\Enum\PostgresLockWaitModeEnum::NonBlocking,
\Cog\DbLocker\Postgres\Enum\PostgresLockAccessModeEnum::Exclusive,
);
From 67e8dff7b190e177803feb78880e939b08dc98f6 Mon Sep 17 00:00:00 2001
From: Anton Komarev
Date: Mon, 4 Aug 2025 09:28:37 +0300
Subject: [PATCH 08/15] Leave only *Handler API
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 64bd582..71a6793 100644
--- a/README.md
+++ b/README.md
@@ -67,7 +67,7 @@ $payment = $locker->withinSessionLevelLock(
$lockId,
function (
\Cog\DbLocker\Postgres\LockHandle\SessionLevelLockHandle $lock,
- ): Payment { // Define a type of $payment variable, and it will be resolved by analyzers
+ ): Payment { // Define a type of $payment variable, so it will be resolved by analyzers
if ($lock->wasAcquired) {
// Execute logic if lock was successful
} else {
From 256adaad694de7ac936c341b29d8582fc41be16e Mon Sep 17 00:00:00 2001
From: Anton Komarev
Date: Tue, 5 Aug 2025 12:17:09 +0300
Subject: [PATCH 09/15] Leave only *Handler API
---
README.md | 23 ++++++------
.../Enum/PostgresLockAccessModeEnum.php | 8 ++--
src/Postgres/Enum/PostgresLockLevelEnum.php | 12 +++---
.../Enum/PostgresLockWaitModeEnum.php | 10 ++---
src/Postgres/PostgresAdvisoryLocker.php | 37 ++++++++++---------
.../AbstractIntegrationTestCase.php | 6 ++-
6 files changed, 51 insertions(+), 45 deletions(-)
diff --git a/README.md b/README.md
index 71a6793..f7431a3 100644
--- a/README.md
+++ b/README.md
@@ -60,12 +60,12 @@ Callback API
$dbConnection = new PDO($dsn, $username, $password);
$locker = new \Cog\DbLocker\Postgres\PostgresAdvisoryLocker();
-$lockId = \Cog\DbLocker\Postgres\PostgresLockKey::create('user', '4');
+$lockKey = \Cog\DbLocker\Postgres\PostgresLockKey::create('user', '4');
$payment = $locker->withinSessionLevelLock(
- $dbConnection,
- $lockId,
- function (
+ dbConnection: $dbConnection,
+ key: $lockKey,
+ callback: function (
\Cog\DbLocker\Postgres\LockHandle\SessionLevelLockHandle $lock,
): Payment { // Define a type of $payment variable, so it will be resolved by analyzers
if ($lock->wasAcquired) {
@@ -74,24 +74,25 @@ $payment = $locker->withinSessionLevelLock(
// Execute logic if lock acquisition has been failed
}
},
- \Cog\DbLocker\Postgres\Enum\PostgresLockWaitModeEnum::NonBlocking,
- \Cog\DbLocker\Postgres\Enum\PostgresLockAccessModeEnum::Exclusive,
+ waitMode: \Cog\DbLocker\Postgres\Enum\PostgresLockWaitModeEnum::NonBlocking,
+ accessMode: \Cog\DbLocker\Postgres\Enum\PostgresLockAccessModeEnum::Exclusive,
);
```
Low-level API
+
```php
$dbConnection = new PDO($dsn, $username, $password);
$locker = new \Cog\DbLocker\Postgres\PostgresAdvisoryLocker();
-$lockId = \Cog\DbLocker\Postgres\PostgresLockKey::create('user', '4');
+$lockKey = \Cog\DbLocker\Postgres\PostgresLockKey::create('user', '4');
try {
$lock = $locker->acquireSessionLevelLock(
- $dbConnection,
- $lockId,
- \Cog\DbLocker\Postgres\Enum\PostgresLockWaitModeEnum::NonBlocking,
- \Cog\DbLocker\Postgres\Enum\PostgresLockAccessModeEnum::Exclusive,
+ dbConnection: $dbConnection,
+ key: $lockKey,
+ waitMode: \Cog\DbLocker\Postgres\Enum\PostgresLockWaitModeEnum::NonBlocking,
+ accessMode: \Cog\DbLocker\Postgres\Enum\PostgresLockAccessModeEnum::Exclusive,
);
if ($lock->wasAcquired) {
// Execute logic if lock was successful
diff --git a/src/Postgres/Enum/PostgresLockAccessModeEnum.php b/src/Postgres/Enum/PostgresLockAccessModeEnum.php
index 8319ead..f6bbe4e 100644
--- a/src/Postgres/Enum/PostgresLockAccessModeEnum.php
+++ b/src/Postgres/Enum/PostgresLockAccessModeEnum.php
@@ -7,10 +7,10 @@
/**
* PostgresLockAccessModeEnum defines the access mode of advisory lock acquisition.
*
- * TODO: Need string values only for tests, should add match to tests instead.
+ * TODO: Write details about access mode.
*/
-enum PostgresLockAccessModeEnum: string
+enum PostgresLockAccessModeEnum
{
- case Exclusive = 'ExclusiveLock';
- case Share = 'ShareLock';
+ case Exclusive;
+ case Share;
}
diff --git a/src/Postgres/Enum/PostgresLockLevelEnum.php b/src/Postgres/Enum/PostgresLockLevelEnum.php
index 3b983c2..1e99efc 100644
--- a/src/Postgres/Enum/PostgresLockLevelEnum.php
+++ b/src/Postgres/Enum/PostgresLockLevelEnum.php
@@ -7,19 +7,19 @@
/**
* PostgresLockLevelEnum defines the level of advisory lock acquisition.
*
+ * - Transaction. Transaction-level (recommended) advisory lock (with _XACT_):
+ * - PG_ADVISORY_XACT_LOCK
+ * - PG_ADVISORY_XACT_LOCK_SHARED
+ * - PG_TRY_ADVISORY_XACT_LOCK
+ * - PG_TRY_ADVISORY_XACT_LOCK_SHARED
* - Session. Session-level advisory lock (without _XACT_):
* - PG_ADVISORY_LOCK
* - PG_ADVISORY_LOCK_SHARED
* - PG_TRY_ADVISORY_LOCK
* - PG_TRY_ADVISORY_LOCK_SHARED
- * - Transaction. Transaction-level advisory lock (with _XACT_):
- * - PG_ADVISORY_XACT_LOCK
- * - PG_ADVISORY_XACT_LOCK_SHARED
- * - PG_TRY_ADVISORY_XACT_LOCK
- * - PG_TRY_ADVISORY_XACT_LOCK_SHARED
*/
enum PostgresLockLevelEnum
{
- case Session;
case Transaction;
+ case Session;
}
diff --git a/src/Postgres/Enum/PostgresLockWaitModeEnum.php b/src/Postgres/Enum/PostgresLockWaitModeEnum.php
index 401ac89..41e3488 100644
--- a/src/Postgres/Enum/PostgresLockWaitModeEnum.php
+++ b/src/Postgres/Enum/PostgresLockWaitModeEnum.php
@@ -7,16 +7,16 @@
/**
* PostgresLockWaitModeEnum defines the type of advisory lock acquisition.
*
- * - NonBlocking. Attempt to acquire the lock without blocking (with _TRY_):
- * - PG_TRY_ADVISORY_LOCK
- * - PG_TRY_ADVISORY_LOCK_SHARED
- * - PG_TRY_ADVISORY_XACT_LOCK
- * - PG_TRY_ADVISORY_XACT_LOCK_SHARED
* - Blocking. Acquire the lock, blocking until it becomes available (without _TRY_):
* - PG_ADVISORY_LOCK
* - PG_ADVISORY_LOCK_SHARED
* - PG_ADVISORY_XACT_LOCK
* - PG_ADVISORY_XACT_LOCK_SHARED
+ * - NonBlocking. Attempt to acquire the lock without blocking (with _TRY_):
+ * - PG_TRY_ADVISORY_LOCK
+ * - PG_TRY_ADVISORY_LOCK_SHARED
+ * - PG_TRY_ADVISORY_XACT_LOCK
+ * - PG_TRY_ADVISORY_XACT_LOCK_SHARED
*/
enum PostgresLockWaitModeEnum
{
diff --git a/src/Postgres/PostgresAdvisoryLocker.php b/src/Postgres/PostgresAdvisoryLocker.php
index 4f9c4b4..ca1d753 100644
--- a/src/Postgres/PostgresAdvisoryLocker.php
+++ b/src/Postgres/PostgresAdvisoryLocker.php
@@ -28,14 +28,14 @@ final class PostgresAdvisoryLocker
*/
public function acquireTransactionLevelLock(
PDO $dbConnection,
- PostgresLockKey $postgresLockKey,
+ PostgresLockKey $key,
PostgresLockWaitModeEnum $waitMode = PostgresLockWaitModeEnum::NonBlocking,
PostgresLockAccessModeEnum $accessMode = PostgresLockAccessModeEnum::Exclusive,
): TransactionLevelLockHandle {
return new TransactionLevelLockHandle(
wasAcquired: $this->acquireLock(
$dbConnection,
- $postgresLockKey,
+ $key,
PostgresLockLevelEnum::Transaction,
$waitMode,
$accessMode,
@@ -63,18 +63,18 @@ public function acquireTransactionLevelLock(
*/
public function acquireSessionLevelLock(
PDO $dbConnection,
- PostgresLockKey $postgresLockKey,
+ PostgresLockKey $key,
PostgresLockWaitModeEnum $waitMode = PostgresLockWaitModeEnum::NonBlocking,
PostgresLockAccessModeEnum $accessMode = PostgresLockAccessModeEnum::Exclusive,
): SessionLevelLockHandle {
return new SessionLevelLockHandle(
$dbConnection,
$this,
- $postgresLockKey,
+ $key,
$accessMode,
wasAcquired: $this->acquireLock(
$dbConnection,
- $postgresLockKey,
+ $key,
PostgresLockLevelEnum::Session,
$waitMode,
$accessMode,
@@ -94,10 +94,9 @@ public function acquireSessionLevelLock(
* ⚠️ Transaction-level advisory locks are strongly preferred whenever possible,
* as they are automatically released at the end of a transaction and are less error-prone.
* Use session-level locks only when transactional context is not available.
- * @see acquireTransactionLevelLock() for preferred locking strategy.
*
* @param PDO $dbConnection Active database connection.
- * @param PostgresLockKey $postgresLockKey Lock key to be acquired.
+ * @param PostgresLockKey $key Lock key to be acquired.
* @param callable(SessionLevelLockHandle): TReturn $callback A callback that receives the lock handle.
* @param PostgresLockWaitModeEnum $waitMode Whether to wait for the lock or fail immediately. Default is non-blocking.
* @param PostgresLockAccessModeEnum $accessMode Whether to acquire a shared or exclusive lock. Default is exclusive.
@@ -106,17 +105,19 @@ public function acquireSessionLevelLock(
* @template TReturn
*
* TODO: Cover with tests
+ *@see acquireTransactionLevelLock() for preferred locking strategy.
+ *
*/
public function withinSessionLevelLock(
PDO $dbConnection,
- PostgresLockKey $postgresLockKey,
+ PostgresLockKey $key,
callable $callback,
PostgresLockWaitModeEnum $waitMode = PostgresLockWaitModeEnum::NonBlocking,
PostgresLockAccessModeEnum $accessMode = PostgresLockAccessModeEnum::Exclusive,
): mixed {
$lockHandle = $this->acquireSessionLevelLock(
$dbConnection,
- $postgresLockKey,
+ $key,
$waitMode,
$accessMode,
);
@@ -134,20 +135,20 @@ public function withinSessionLevelLock(
*/
public function releaseSessionLevelLock(
PDO $dbConnection,
- PostgresLockKey $postgresLockKey,
+ PostgresLockKey $key,
PostgresLockAccessModeEnum $accessMode = PostgresLockAccessModeEnum::Exclusive,
): bool {
$sql = match ($accessMode) {
PostgresLockAccessModeEnum::Exclusive => 'SELECT PG_ADVISORY_UNLOCK(:class_id, :object_id);',
PostgresLockAccessModeEnum::Share => 'SELECT PG_ADVISORY_UNLOCK_SHARED(:class_id, :object_id);',
};
- $sql .= " -- $postgresLockKey->humanReadableValue";
+ $sql .= " -- $key->humanReadableValue";
$statement = $dbConnection->prepare($sql);
$statement->execute(
[
- 'class_id' => $postgresLockKey->classId,
- 'object_id' => $postgresLockKey->objectId,
+ 'class_id' => $key->classId,
+ 'object_id' => $key->objectId,
],
);
@@ -170,14 +171,14 @@ public function releaseAllSessionLevelLocks(
private function acquireLock(
PDO $dbConnection,
- PostgresLockKey $postgresLockKey,
+ PostgresLockKey $key,
PostgresLockLevelEnum $level,
PostgresLockWaitModeEnum $waitMode = PostgresLockWaitModeEnum::NonBlocking,
PostgresLockAccessModeEnum $accessMode = PostgresLockAccessModeEnum::Exclusive,
): bool {
if ($level === PostgresLockLevelEnum::Transaction && $dbConnection->inTransaction() === false) {
throw new LogicException(
- "Transaction-level advisory lock `$postgresLockKey->humanReadableValue` cannot be acquired outside of transaction",
+ "Transaction-level advisory lock `$key->humanReadableValue` cannot be acquired outside of transaction",
);
}
@@ -223,13 +224,13 @@ private function acquireLock(
PostgresLockAccessModeEnum::Share,
] => 'SELECT PG_ADVISORY_LOCK_SHARED(:class_id, :object_id);',
};
- $sql .= " -- $postgresLockKey->humanReadableValue";
+ $sql .= " -- $key->humanReadableValue";
$statement = $dbConnection->prepare($sql);
$statement->execute(
[
- 'class_id' => $postgresLockKey->classId,
- 'object_id' => $postgresLockKey->objectId,
+ 'class_id' => $key->classId,
+ 'object_id' => $key->objectId,
],
);
diff --git a/test/Integration/AbstractIntegrationTestCase.php b/test/Integration/AbstractIntegrationTestCase.php
index 914b7a2..d948e64 100644
--- a/test/Integration/AbstractIntegrationTestCase.php
+++ b/test/Integration/AbstractIntegrationTestCase.php
@@ -116,7 +116,11 @@ private function findPostgresAdvisoryLockInConnection(
'lock_object_id' => $postgresLockKey->objectId,
'lock_object_subid' => 2, // Using two keyed locks
'connection_pid' => $dbConnection->pgsqlGetPid(),
- 'mode' => $mode->value,
+ 'mode' => match ($mode) {
+ PostgresLockAccessModeEnum::Exclusive => 'ExclusiveLock',
+ PostgresLockAccessModeEnum::Share => 'ShareLock',
+ default => throw new \LogicException("Unknown mode $mode->name"),
+ },
],
);
From 3a489f184c7adfa4a7ea2c207231e240ef03fdd7 Mon Sep 17 00:00:00 2001
From: Anton Komarev
Date: Tue, 5 Aug 2025 12:20:55 +0300
Subject: [PATCH 10/15] Leave only *Handler API
---
README.md | 6 ++++++
src/Postgres/PostgresAdvisoryLocker.php | 6 +++++-
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index f7431a3..377b448 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,12 @@
+## Things to decide
+
+- [ ] Do we need handle methods? Or leave simple bool for transaction and callback only for session lock
+- [ ] Should wait mode be blocking or non-blocking by default?
+- [ ] Should callback for session lock be at the end of the params?
+
## Introduction
> WARNING! This library is currently under development and may not be stable. Use in your services at your own risk.
diff --git a/src/Postgres/PostgresAdvisoryLocker.php b/src/Postgres/PostgresAdvisoryLocker.php
index ca1d753..cdd4986 100644
--- a/src/Postgres/PostgresAdvisoryLocker.php
+++ b/src/Postgres/PostgresAdvisoryLocker.php
@@ -126,7 +126,11 @@ public function withinSessionLevelLock(
return $callback($lockHandle);
}
finally {
- $lockHandle->release();
+ $this->releaseSessionLevelLock(
+ $dbConnection,
+ $key,
+ $accessMode,
+ );
}
}
From a2a211820a45b6990894c0b69245b427c5c51c17 Mon Sep 17 00:00:00 2001
From: Anton Komarev
Date: Tue, 5 Aug 2025 12:40:06 +0300
Subject: [PATCH 11/15] Leave only *Handler API
---
README.md | 2 +-
src/Postgres/PostgresAdvisoryLocker.php | 3 +--
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 377b448..61290cd 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@
- [ ] Do we need handle methods? Or leave simple bool for transaction and callback only for session lock
- [ ] Should wait mode be blocking or non-blocking by default?
-- [ ] Should callback for session lock be at the end of the params?
+- [ ] Should callback for session lock be at the end of the params (after optional ones)?
## Introduction
diff --git a/src/Postgres/PostgresAdvisoryLocker.php b/src/Postgres/PostgresAdvisoryLocker.php
index cdd4986..91de6dd 100644
--- a/src/Postgres/PostgresAdvisoryLocker.php
+++ b/src/Postgres/PostgresAdvisoryLocker.php
@@ -94,6 +94,7 @@ public function acquireSessionLevelLock(
* ⚠️ Transaction-level advisory locks are strongly preferred whenever possible,
* as they are automatically released at the end of a transaction and are less error-prone.
* Use session-level locks only when transactional context is not available.
+ * @see acquireTransactionLevelLock() for preferred locking strategy.
*
* @param PDO $dbConnection Active database connection.
* @param PostgresLockKey $key Lock key to be acquired.
@@ -105,8 +106,6 @@ public function acquireSessionLevelLock(
* @template TReturn
*
* TODO: Cover with tests
- *@see acquireTransactionLevelLock() for preferred locking strategy.
- *
*/
public function withinSessionLevelLock(
PDO $dbConnection,
From 9c08b96379f06f40f1ab2fba10484471238c6dee Mon Sep 17 00:00:00 2001
From: Anton Komarev
Date: Sat, 9 Aug 2025 09:29:55 +0300
Subject: [PATCH 12/15] Leave only *Handler API
---
.../LockHandle/SessionLevelLockHandle.php | 9 ------
.../LockHandle/TransactionLevelLockHandle.php | 24 ---------------
src/Postgres/PostgresAdvisoryLocker.php | 30 ++++++-------------
.../Postgres/PostgresAdvisoryLockerTest.php | 29 +++++++++---------
4 files changed, 23 insertions(+), 69 deletions(-)
delete mode 100644 src/Postgres/LockHandle/TransactionLevelLockHandle.php
diff --git a/src/Postgres/LockHandle/SessionLevelLockHandle.php b/src/Postgres/LockHandle/SessionLevelLockHandle.php
index 23b0fdc..ff13f8e 100644
--- a/src/Postgres/LockHandle/SessionLevelLockHandle.php
+++ b/src/Postgres/LockHandle/SessionLevelLockHandle.php
@@ -58,13 +58,4 @@ public function release(): bool
return $wasReleased;
}
-
- /**
- * Automatically release the lock when the handle is destroyed.
- */
- public function __destruct()
- {
- // TODO: Do we need to
- $this->release();
- }
}
\ No newline at end of file
diff --git a/src/Postgres/LockHandle/TransactionLevelLockHandle.php b/src/Postgres/LockHandle/TransactionLevelLockHandle.php
deleted file mode 100644
index 999b12f..0000000
--- a/src/Postgres/LockHandle/TransactionLevelLockHandle.php
+++ /dev/null
@@ -1,24 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-declare(strict_types=1);
-
-namespace Cog\DbLocker\Postgres\LockHandle;
-
-/**
- * @internal
- */
-final class TransactionLevelLockHandle
-{
- public function __construct(
- public readonly bool $wasAcquired,
- ) {}
-}
\ No newline at end of file
diff --git a/src/Postgres/PostgresAdvisoryLocker.php b/src/Postgres/PostgresAdvisoryLocker.php
index 91de6dd..0acdde6 100644
--- a/src/Postgres/PostgresAdvisoryLocker.php
+++ b/src/Postgres/PostgresAdvisoryLocker.php
@@ -17,7 +17,6 @@
use Cog\DbLocker\Postgres\Enum\PostgresLockLevelEnum;
use Cog\DbLocker\Postgres\Enum\PostgresLockWaitModeEnum;
use Cog\DbLocker\Postgres\LockHandle\SessionLevelLockHandle;
-use Cog\DbLocker\Postgres\LockHandle\TransactionLevelLockHandle;
use LogicException;
use PDO;
@@ -31,31 +30,19 @@ public function acquireTransactionLevelLock(
PostgresLockKey $key,
PostgresLockWaitModeEnum $waitMode = PostgresLockWaitModeEnum::NonBlocking,
PostgresLockAccessModeEnum $accessMode = PostgresLockAccessModeEnum::Exclusive,
- ): TransactionLevelLockHandle {
- return new TransactionLevelLockHandle(
- wasAcquired: $this->acquireLock(
- $dbConnection,
- $key,
- PostgresLockLevelEnum::Transaction,
- $waitMode,
- $accessMode,
- ),
+ ): bool {
+ return $this->acquireLock(
+ $dbConnection,
+ $key,
+ PostgresLockLevelEnum::Transaction,
+ $waitMode,
+ $accessMode,
);
}
/**
* Acquire a session-level advisory lock with configurable wait and access modes.
*
- * ⚠️ You MUST retain the returned handle in a variable.
- * If the handle is not stored and is immediately garbage collected,
- * the lock will be released in the lock handle __destruct method.
- * @see SessionLevelLockHandle::__destruct
- *
- * @example
- * $handle = $locker->acquireSessionLevelLock(...); // ✅ Lock held
- *
- * $locker->acquireSessionLevelLock(...); // ❌ Lock immediately released
- *
* ⚠️ Transaction-level advisory locks are strongly preferred whenever possible,
* as they are automatically released at the end of a transaction and are less error-prone.
* Use session-level locks only when transactional context is not available.
@@ -94,7 +81,6 @@ public function acquireSessionLevelLock(
* ⚠️ Transaction-level advisory locks are strongly preferred whenever possible,
* as they are automatically released at the end of a transaction and are less error-prone.
* Use session-level locks only when transactional context is not available.
- * @see acquireTransactionLevelLock() for preferred locking strategy.
*
* @param PDO $dbConnection Active database connection.
* @param PostgresLockKey $key Lock key to be acquired.
@@ -106,6 +92,8 @@ public function acquireSessionLevelLock(
* @template TReturn
*
* TODO: Cover with tests
+ * @see acquireTransactionLevelLock() for preferred locking strategy.
+ *
*/
public function withinSessionLevelLock(
PDO $dbConnection,
diff --git a/test/Integration/Postgres/PostgresAdvisoryLockerTest.php b/test/Integration/Postgres/PostgresAdvisoryLockerTest.php
index c67dc31..659def6 100644
--- a/test/Integration/Postgres/PostgresAdvisoryLockerTest.php
+++ b/test/Integration/Postgres/PostgresAdvisoryLockerTest.php
@@ -71,7 +71,7 @@ public function testItCanTryAcquireLockWithinTransaction(
accessMode: $accessMode,
);
- $this->assertTrue($isLockAcquired->wasAcquired);
+ $this->assertTrue($isLockAcquired);
$this->assertPgAdvisoryLocksCount(1);
$this->assertPgAdvisoryLockExistsInConnection($dbConnection, $lockKey, $accessMode);
}
@@ -177,8 +177,7 @@ public function testItCannotAcquireSameLockInTwoConnections(): void
$dbConnection1 = $this->initPostgresPdoConnection();
$dbConnection2 = $this->initPostgresPdoConnection();
$lockKey = PostgresLockKey::create('test');
- // TODO: Fix corner-case: without variable its self-destructing instantly
- $connection1Lock = $locker->acquireSessionLevelLock(
+ $locker->acquireSessionLevelLock(
$dbConnection1,
$lockKey,
);
@@ -200,7 +199,7 @@ public function testItCanReleaseLock(
$locker = $this->initLocker();
$dbConnection = $this->initPostgresPdoConnection();
$lockKey = PostgresLockKey::create('test');
- $connectionLock = $locker->acquireSessionLevelLock(
+ $locker->acquireSessionLevelLock(
$dbConnection,
$lockKey,
accessMode: $accessMode,
@@ -236,7 +235,7 @@ public function testItCanNotReleaseLockOfDifferentModes(
$locker = $this->initLocker();
$dbConnection = $this->initPostgresPdoConnection();
$lockKey = PostgresLockKey::create('test');
- $connectionLock = $locker->acquireSessionLevelLock(
+ $locker->acquireSessionLevelLock(
$dbConnection,
$lockKey,
accessMode: $acquireMode,
@@ -272,11 +271,11 @@ public function testItCanReleaseLockTwiceIfAcquiredTwice(): void
$locker = $this->initLocker();
$dbConnection = $this->initPostgresPdoConnection();
$lockKey = PostgresLockKey::create('test');
- $connectionLock1 = $locker->acquireSessionLevelLock(
+ $locker->acquireSessionLevelLock(
$dbConnection,
$lockKey,
);
- $connectionLock2 = $locker->acquireSessionLevelLock(
+ $locker->acquireSessionLevelLock(
$dbConnection,
$lockKey,
);
@@ -320,11 +319,11 @@ public function testItCannotAcquireLockInSecondConnectionAfterOneReleaseTwiceLoc
$dbConnection1 = $this->initPostgresPdoConnection();
$dbConnection2 = $this->initPostgresPdoConnection();
$lockKey = PostgresLockKey::create('test');
- $connection1Lock1 = $locker->acquireSessionLevelLock(
+ $locker->acquireSessionLevelLock(
$dbConnection1,
$lockKey,
);
- $connection1Lock2 = $locker->acquireSessionLevelLock(
+ $locker->acquireSessionLevelLock(
$dbConnection1,
$lockKey,
);
@@ -360,7 +359,7 @@ public function testItCannotReleaseLockIfAcquiredInOtherConnection(): void
$dbConnection1 = $this->initPostgresPdoConnection();
$dbConnection2 = $this->initPostgresPdoConnection();
$lockKey = PostgresLockKey::create('test');
- $connection1Lock = $locker->acquireSessionLevelLock(
+ $locker->acquireSessionLevelLock(
$dbConnection1,
$lockKey,
);
@@ -413,19 +412,19 @@ public function testItCanReleaseAllLocksInConnectionButKeepsOtherConnectionLocks
$lockKey2 = PostgresLockKey::create('test2');
$lockKey3 = PostgresLockKey::create('test3');
$lockKey4 = PostgresLockKey::create('test4');
- $connect1Lock1 = $locker->acquireSessionLevelLock(
+ $locker->acquireSessionLevelLock(
$dbConnection1,
$lockKey1,
);
- $connect1Lock2 = $locker->acquireSessionLevelLock(
+ $locker->acquireSessionLevelLock(
$dbConnection1,
$lockKey2,
);
- $connect2Lock3 = $locker->acquireSessionLevelLock(
+ $locker->acquireSessionLevelLock(
$dbConnection2,
$lockKey3,
);
- $connect2Lock4 = $locker->acquireSessionLevelLock(
+ $locker->acquireSessionLevelLock(
$dbConnection2,
$lockKey4,
);
@@ -461,7 +460,7 @@ public function testItCannotAcquireLockInSecondConnectionIfTakenWithinTransactio
$dbConnection2 = $this->initPostgresPdoConnection();
$lockKey = PostgresLockKey::create('test');
$dbConnection1->beginTransaction();
- $connection1Lock = $locker->acquireSessionLevelLock(
+ $locker->acquireSessionLevelLock(
$dbConnection1,
$lockKey,
);
From b7fe0a0113419b02f6a3a37b8ca0b1eaa3ead246 Mon Sep 17 00:00:00 2001
From: Anton Komarev
Date: Sat, 9 Aug 2025 09:52:21 +0300
Subject: [PATCH 13/15] Leave only *Handler API
---
src/Postgres/PostgresAdvisoryLocker.php | 9 +++----
.../Postgres/PostgresAdvisoryLockerTest.php | 24 +++++++++++++++++++
2 files changed, 27 insertions(+), 6 deletions(-)
diff --git a/src/Postgres/PostgresAdvisoryLocker.php b/src/Postgres/PostgresAdvisoryLocker.php
index 0acdde6..484e8ca 100644
--- a/src/Postgres/PostgresAdvisoryLocker.php
+++ b/src/Postgres/PostgresAdvisoryLocker.php
@@ -81,6 +81,9 @@ public function acquireSessionLevelLock(
* ⚠️ Transaction-level advisory locks are strongly preferred whenever possible,
* as they are automatically released at the end of a transaction and are less error-prone.
* Use session-level locks only when transactional context is not available.
+ * @see acquireTransactionLevelLock() for preferred locking strategy.
+ *
+ * @template TReturn
*
* @param PDO $dbConnection Active database connection.
* @param PostgresLockKey $key Lock key to be acquired.
@@ -88,12 +91,6 @@ public function acquireSessionLevelLock(
* @param PostgresLockWaitModeEnum $waitMode Whether to wait for the lock or fail immediately. Default is non-blocking.
* @param PostgresLockAccessModeEnum $accessMode Whether to acquire a shared or exclusive lock. Default is exclusive.
* @return TReturn The return value of the callback.
- *
- * @template TReturn
- *
- * TODO: Cover with tests
- * @see acquireTransactionLevelLock() for preferred locking strategy.
- *
*/
public function withinSessionLevelLock(
PDO $dbConnection,
diff --git a/test/Integration/Postgres/PostgresAdvisoryLockerTest.php b/test/Integration/Postgres/PostgresAdvisoryLockerTest.php
index 659def6..e355fa1 100644
--- a/test/Integration/Postgres/PostgresAdvisoryLockerTest.php
+++ b/test/Integration/Postgres/PostgresAdvisoryLockerTest.php
@@ -566,6 +566,30 @@ public function testItCannotReleaseAllLocksAcquiredWithinTransaction(): void
$this->assertPgAdvisoryLockExistsInConnection($dbConnection, $lockKey2);
}
+ public function testItCanExecuteCodeWithinSessionLock(): void
+ {
+ $locker = $this->initLocker();
+ $dbConnection = $this->initPostgresPdoConnection();
+ $lockKey = PostgresLockKey::create('test');
+ $x = 2;
+ $y = 3;
+
+ $result = $locker->withinSessionLevelLock(
+ $dbConnection,
+ $lockKey,
+ function () use ($dbConnection, $lockKey, $x, $y): int {
+ $this->assertPgAdvisoryLocksCount(1);
+ $this->assertPgAdvisoryLockExistsInConnection($dbConnection, $lockKey);
+
+ return $x + $y;
+ },
+ );
+
+ $this->assertSame(5, $result);
+ $this->assertPgAdvisoryLocksCount(0);
+ $this->assertPgAdvisoryLockMissingInConnection($dbConnection, $lockKey);
+ }
+
private function initLocker(): PostgresAdvisoryLocker
{
return new PostgresAdvisoryLocker();
From fe66f4fea58a5694af9382888b0a34198f449b9b Mon Sep 17 00:00:00 2001
From: Anton Komarev
Date: Sat, 9 Aug 2025 10:05:13 +0300
Subject: [PATCH 14/15] Leave only *Handler API
---
src/Postgres/PostgresAdvisoryLocker.php | 58 ++++++++++++-------------
1 file changed, 29 insertions(+), 29 deletions(-)
diff --git a/src/Postgres/PostgresAdvisoryLocker.php b/src/Postgres/PostgresAdvisoryLocker.php
index 484e8ca..90d7633 100644
--- a/src/Postgres/PostgresAdvisoryLocker.php
+++ b/src/Postgres/PostgresAdvisoryLocker.php
@@ -40,35 +40,6 @@ public function acquireTransactionLevelLock(
);
}
- /**
- * Acquire a session-level advisory lock with configurable wait and access modes.
- *
- * ⚠️ Transaction-level advisory locks are strongly preferred whenever possible,
- * as they are automatically released at the end of a transaction and are less error-prone.
- * Use session-level locks only when transactional context is not available.
- * @see acquireTransactionLevelLock() for preferred locking strategy.
- */
- public function acquireSessionLevelLock(
- PDO $dbConnection,
- PostgresLockKey $key,
- PostgresLockWaitModeEnum $waitMode = PostgresLockWaitModeEnum::NonBlocking,
- PostgresLockAccessModeEnum $accessMode = PostgresLockAccessModeEnum::Exclusive,
- ): SessionLevelLockHandle {
- return new SessionLevelLockHandle(
- $dbConnection,
- $this,
- $key,
- $accessMode,
- wasAcquired: $this->acquireLock(
- $dbConnection,
- $key,
- PostgresLockLevelEnum::Session,
- $waitMode,
- $accessMode,
- ),
- );
- }
-
/**
* Acquires a session-level advisory lock and ensures its release after executing the callback.
*
@@ -118,6 +89,35 @@ public function withinSessionLevelLock(
}
}
+ /**
+ * Acquire a session-level advisory lock with configurable wait and access modes.
+ *
+ * ⚠️ Transaction-level advisory locks are strongly preferred whenever possible,
+ * as they are automatically released at the end of a transaction and are less error-prone.
+ * Use session-level locks only when transactional context is not available.
+ * @see acquireTransactionLevelLock() for preferred locking strategy.
+ */
+ public function acquireSessionLevelLock(
+ PDO $dbConnection,
+ PostgresLockKey $key,
+ PostgresLockWaitModeEnum $waitMode = PostgresLockWaitModeEnum::NonBlocking,
+ PostgresLockAccessModeEnum $accessMode = PostgresLockAccessModeEnum::Exclusive,
+ ): SessionLevelLockHandle {
+ return new SessionLevelLockHandle(
+ $dbConnection,
+ $this,
+ $key,
+ $accessMode,
+ wasAcquired: $this->acquireLock(
+ $dbConnection,
+ $key,
+ PostgresLockLevelEnum::Session,
+ $waitMode,
+ $accessMode,
+ ),
+ );
+ }
+
/**
* Release session level advisory lock.
*/
From 9990b1b6592810a0f2f349179221c38873f0ce06 Mon Sep 17 00:00:00 2001
From: Anton Komarev
Date: Sat, 9 Aug 2025 10:32:12 +0300
Subject: [PATCH 15/15] Leave only *Handler API
---
README.md | 1 -
1 file changed, 1 deletion(-)
diff --git a/README.md b/README.md
index 61290cd..cb3cf07 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,6 @@
## Things to decide
-- [ ] Do we need handle methods? Or leave simple bool for transaction and callback only for session lock
- [ ] Should wait mode be blocking or non-blocking by default?
- [ ] Should callback for session lock be at the end of the params (after optional ones)?