Skip to content

Commit c2240b3

Browse files
committed
Rewrite logic to two keyed lock
1 parent 8d68c3d commit c2240b3

File tree

3 files changed

+72
-60
lines changed

3 files changed

+72
-60
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ if ($isLockAcquired) {
6565
} else {
6666
// Execute logic if lock acquisition has been failed
6767
}
68-
$postgresLocker->releaseLock($dbConnection, $postgresLockId);
68+
$postgresLocker->releaseLockWithinSession($dbConnection, $postgresLockId);
6969
```
7070

7171
## Changelog

src/Locker/PostgresAdvisoryLocker.php

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,25 @@
1919

2020
final class PostgresAdvisoryLocker
2121
{
22-
public function tryAcquireLock(
22+
/**
23+
* Try to acquire transaction-level lock (recommended).
24+
*/
25+
public function tryAcquireLockWithinTransaction(
2326
PDO $dbConnection,
2427
PostgresLockId $postgresLockId,
2528
): bool {
29+
if ($dbConnection->inTransaction() === false) {
30+
$lockId = $postgresLockId->humanReadableValue;
31+
32+
throw new LogicException(
33+
"Transaction-level advisory lock `$lockId` cannot be acquired outside of transaction",
34+
);
35+
}
36+
2637
// TODO: Need to sanitize humanReadableValue?
2738
$statement = $dbConnection->prepare(
2839
<<<SQL
29-
SELECT PG_TRY_ADVISORY_LOCK(:class_id, :object_id); -- $postgresLockId->humanReadableValue
40+
SELECT PG_TRY_ADVISORY_XACT_LOCK(:class_id, :object_id); -- $postgresLockId->humanReadableValue
3041
SQL,
3142
);
3243
$statement->execute(
@@ -39,22 +50,17 @@ public function tryAcquireLock(
3950
return $statement->fetchColumn(0);
4051
}
4152

42-
public function tryAcquireLockWithinTransaction(
53+
/**
54+
* Try to acquire session-level lock (use only if transaction-level lock not applicable).
55+
*/
56+
public function tryAcquireLockWithinSession(
4357
PDO $dbConnection,
4458
PostgresLockId $postgresLockId,
4559
): bool {
46-
if ($dbConnection->inTransaction() === false) {
47-
$lockId = $postgresLockId->humanReadableValue;
48-
49-
throw new LogicException(
50-
"Transaction-level advisory lock `$lockId` cannot be acquired outside of transaction",
51-
);
52-
}
53-
5460
// TODO: Need to sanitize humanReadableValue?
5561
$statement = $dbConnection->prepare(
5662
<<<SQL
57-
SELECT PG_TRY_ADVISORY_XACT_LOCK(:class_id, :object_id); -- $postgresLockId->humanReadableValue
63+
SELECT PG_TRY_ADVISORY_LOCK(:class_id, :object_id); -- $postgresLockId->humanReadableValue
5864
SQL,
5965
);
6066
$statement->execute(
@@ -67,7 +73,10 @@ public function tryAcquireLockWithinTransaction(
6773
return $statement->fetchColumn(0);
6874
}
6975

70-
public function releaseLock(
76+
/**
77+
* Release session-level lock.
78+
*/
79+
public function releaseLockWithinSession(
7180
PDO $dbConnection,
7281
PostgresLockId $postgresLockId,
7382
): bool {
@@ -86,7 +95,10 @@ public function releaseLock(
8695
return $statement->fetchColumn(0);
8796
}
8897

89-
public function releaseAllLocks(
98+
/**
99+
* Release all session-level locks.
100+
*/
101+
public function releaseAllLocksWithinSession(
90102
PDO $dbConnection,
91103
): void {
92104
$statement = $dbConnection->prepare(

test/Integration/Locker/PostgresAdvisoryLockerTest.php

Lines changed: 45 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -24,34 +24,34 @@ final class PostgresAdvisoryLockerTest extends AbstractIntegrationTestCase
2424
private const DB_INT32_VALUE_MIN = -2_147_483_648;
2525
private const DB_INT32_VALUE_MAX = 2_147_483_647;
2626

27-
public function testItCanAcquireLock(): void
27+
public function testItCanTryAcquireLockWithinSession(): void
2828
{
2929
$locker = $this->initLocker();
3030
$dbConnection = $this->initPostgresPdoConnection();
3131
$postgresLockId = PostgresLockId::fromKeyValue('test');
3232

33-
$isLockAcquired = $locker->tryAcquireLock($dbConnection, $postgresLockId);
33+
$isLockAcquired = $locker->tryAcquireLockWithinSession($dbConnection, $postgresLockId);
3434

3535
$this->assertTrue($isLockAcquired);
3636
$this->assertPgAdvisoryLocksCount(1);
3737
$this->assertPgAdvisoryLockExistsInConnection($dbConnection, $postgresLockId);
3838
}
3939

40-
#[DataProvider('provideItCanAcquireLockFromIntKeysCornerCasesData')]
41-
public function testItCanAcquireLockFromIntKeysCornerCases(): void
40+
#[DataProvider('provideItCanTryAcquireLockFromIntKeysCornerCasesData')]
41+
public function testItCanTryAcquireLockFromIntKeysCornerCases(): void
4242
{
4343
$locker = $this->initLocker();
4444
$dbConnection = $this->initPostgresPdoConnection();
4545
$postgresLockId = PostgresLockId::fromIntKeys(self::DB_INT32_VALUE_MIN, 0);
4646

47-
$isLockAcquired = $locker->tryAcquireLock($dbConnection, $postgresLockId);
47+
$isLockAcquired = $locker->tryAcquireLockWithinSession($dbConnection, $postgresLockId);
4848

4949
$this->assertTrue($isLockAcquired);
5050
$this->assertPgAdvisoryLocksCount(1);
5151
$this->assertPgAdvisoryLockExistsInConnection($dbConnection, $postgresLockId);
5252
}
5353

54-
public static function provideItCanAcquireLockFromIntKeysCornerCasesData(): array
54+
public static function provideItCanTryAcquireLockFromIntKeysCornerCasesData(): array
5555
{
5656
return [
5757
'min class_id' => [
@@ -73,30 +73,30 @@ public static function provideItCanAcquireLockFromIntKeysCornerCasesData(): arra
7373
];
7474
}
7575

76-
public function testItCanAcquireLockInSameConnectionOnlyOnce(): void
76+
public function testItCanTryAcquireLockInSameConnectionOnlyOnce(): void
7777
{
7878
$locker = $this->initLocker();
7979
$dbConnection = $this->initPostgresPdoConnection();
8080
$postgresLockId = PostgresLockId::fromKeyValue('test');
8181

82-
$isLockAcquired1 = $locker->tryAcquireLock($dbConnection, $postgresLockId);
83-
$isLockAcquired2 = $locker->tryAcquireLock($dbConnection, $postgresLockId);
82+
$isLockAcquired1 = $locker->tryAcquireLockWithinSession($dbConnection, $postgresLockId);
83+
$isLockAcquired2 = $locker->tryAcquireLockWithinSession($dbConnection, $postgresLockId);
8484

8585
$this->assertTrue($isLockAcquired1);
8686
$this->assertTrue($isLockAcquired2);
8787
$this->assertPgAdvisoryLocksCount(1);
8888
$this->assertPgAdvisoryLockExistsInConnection($dbConnection, $postgresLockId);
8989
}
9090

91-
public function testItCanAcquireMultipleLocksInOneConnection(): void
91+
public function testItCanTryAcquireMultipleLocksInOneConnection(): void
9292
{
9393
$locker = $this->initLocker();
9494
$dbConnection = $this->initPostgresPdoConnection();
9595
$postgresLockId1 = PostgresLockId::fromKeyValue('test1');
9696
$postgresLockId2 = PostgresLockId::fromKeyValue('test2');
9797

98-
$isLock1Acquired = $locker->tryAcquireLock($dbConnection, $postgresLockId1);
99-
$isLock2Acquired = $locker->tryAcquireLock($dbConnection, $postgresLockId2);
98+
$isLock1Acquired = $locker->tryAcquireLockWithinSession($dbConnection, $postgresLockId1);
99+
$isLock2Acquired = $locker->tryAcquireLockWithinSession($dbConnection, $postgresLockId2);
100100

101101
$this->assertTrue($isLock1Acquired);
102102
$this->assertTrue($isLock2Acquired);
@@ -111,9 +111,9 @@ public function testItCannotAcquireSameLockInTwoConnections(): void
111111
$dbConnection1 = $this->initPostgresPdoConnection();
112112
$dbConnection2 = $this->initPostgresPdoConnection();
113113
$postgresLockId = PostgresLockId::fromKeyValue('test');
114-
$locker->tryAcquireLock($dbConnection1, $postgresLockId);
114+
$locker->tryAcquireLockWithinSession($dbConnection1, $postgresLockId);
115115

116-
$isLockAcquired = $locker->tryAcquireLock($dbConnection2, $postgresLockId);
116+
$isLockAcquired = $locker->tryAcquireLockWithinSession($dbConnection2, $postgresLockId);
117117

118118
$this->assertFalse($isLockAcquired);
119119
$this->assertPgAdvisoryLocksCount(1);
@@ -125,9 +125,9 @@ public function testItCanReleaseLock(): void
125125
$locker = $this->initLocker();
126126
$dbConnection = $this->initPostgresPdoConnection();
127127
$postgresLockId = PostgresLockId::fromKeyValue('test');
128-
$locker->tryAcquireLock($dbConnection, $postgresLockId);
128+
$locker->tryAcquireLockWithinSession($dbConnection, $postgresLockId);
129129

130-
$isLockReleased = $locker->releaseLock($dbConnection, $postgresLockId);
130+
$isLockReleased = $locker->releaseLockWithinSession($dbConnection, $postgresLockId);
131131

132132
$this->assertTrue($isLockReleased);
133133
$this->assertPgAdvisoryLocksCount(0);
@@ -138,27 +138,27 @@ public function testItCanReleaseLockTwiceIfAcquiredTwice(): void
138138
$locker = $this->initLocker();
139139
$dbConnection = $this->initPostgresPdoConnection();
140140
$postgresLockId = PostgresLockId::fromKeyValue('test');
141-
$locker->tryAcquireLock($dbConnection, $postgresLockId);
142-
$locker->tryAcquireLock($dbConnection, $postgresLockId);
141+
$locker->tryAcquireLockWithinSession($dbConnection, $postgresLockId);
142+
$locker->tryAcquireLockWithinSession($dbConnection, $postgresLockId);
143143

144-
$isLockReleased1 = $locker->releaseLock($dbConnection, $postgresLockId);
145-
$isLockReleased2 = $locker->releaseLock($dbConnection, $postgresLockId);
144+
$isLockReleased1 = $locker->releaseLockWithinSession($dbConnection, $postgresLockId);
145+
$isLockReleased2 = $locker->releaseLockWithinSession($dbConnection, $postgresLockId);
146146

147147
$this->assertTrue($isLockReleased1);
148148
$this->assertTrue($isLockReleased2);
149149
$this->assertPgAdvisoryLocksCount(0);
150150
}
151151

152-
public function testItCanAcquireLockInSecondConnectionAfterRelease(): void
152+
public function testItCanTryAcquireLockInSecondConnectionAfterRelease(): void
153153
{
154154
$locker = $this->initLocker();
155155
$dbConnection1 = $this->initPostgresPdoConnection();
156156
$dbConnection2 = $this->initPostgresPdoConnection();
157157
$postgresLockId = PostgresLockId::fromKeyValue('test');
158-
$locker->tryAcquireLock($dbConnection1, $postgresLockId);
159-
$locker->releaseLock($dbConnection1, $postgresLockId);
158+
$locker->tryAcquireLockWithinSession($dbConnection1, $postgresLockId);
159+
$locker->releaseLockWithinSession($dbConnection1, $postgresLockId);
160160

161-
$isLockAcquired = $locker->tryAcquireLock($dbConnection2, $postgresLockId);
161+
$isLockAcquired = $locker->tryAcquireLockWithinSession($dbConnection2, $postgresLockId);
162162

163163
$this->assertTrue($isLockAcquired);
164164
$this->assertPgAdvisoryLocksCount(1);
@@ -171,11 +171,11 @@ public function testItCannotAcquireLockInSecondConnectionAfterOneReleaseTwiceLoc
171171
$dbConnection1 = $this->initPostgresPdoConnection();
172172
$dbConnection2 = $this->initPostgresPdoConnection();
173173
$postgresLockId = PostgresLockId::fromKeyValue('test');
174-
$locker->tryAcquireLock($dbConnection1, $postgresLockId);
175-
$locker->tryAcquireLock($dbConnection1, $postgresLockId);
174+
$locker->tryAcquireLockWithinSession($dbConnection1, $postgresLockId);
175+
$locker->tryAcquireLockWithinSession($dbConnection1, $postgresLockId);
176176

177-
$isLockReleased = $locker->releaseLock($dbConnection1, $postgresLockId);
178-
$isLockAcquired = $locker->tryAcquireLock($dbConnection2, $postgresLockId);
177+
$isLockReleased = $locker->releaseLockWithinSession($dbConnection1, $postgresLockId);
178+
$isLockAcquired = $locker->tryAcquireLockWithinSession($dbConnection2, $postgresLockId);
179179

180180
$this->assertTrue($isLockReleased);
181181
$this->assertFalse($isLockAcquired);
@@ -190,7 +190,7 @@ public function testItCannotReleaseLockIfNotAcquired(): void
190190
$dbConnection = $this->initPostgresPdoConnection();
191191
$postgresLockId = PostgresLockId::fromKeyValue('test');
192192

193-
$isLockReleased = $locker->releaseLock($dbConnection, $postgresLockId);
193+
$isLockReleased = $locker->releaseLockWithinSession($dbConnection, $postgresLockId);
194194

195195
$this->assertFalse($isLockReleased);
196196
$this->assertPgAdvisoryLocksCount(0);
@@ -202,9 +202,9 @@ public function testItCannotReleaseLockIfAcquiredInOtherConnection(): void
202202
$dbConnection1 = $this->initPostgresPdoConnection();
203203
$dbConnection2 = $this->initPostgresPdoConnection();
204204
$postgresLockId = PostgresLockId::fromKeyValue('test');
205-
$locker->tryAcquireLock($dbConnection1, $postgresLockId);
205+
$locker->tryAcquireLockWithinSession($dbConnection1, $postgresLockId);
206206

207-
$isLockReleased = $locker->releaseLock($dbConnection2, $postgresLockId);
207+
$isLockReleased = $locker->releaseLockWithinSession($dbConnection2, $postgresLockId);
208208

209209
$this->assertFalse($isLockReleased);
210210
$this->assertPgAdvisoryLocksCount(1);
@@ -217,10 +217,10 @@ public function testItCanReleaseAllLocksInConnection(): void
217217
$dbConnection = $this->initPostgresPdoConnection();
218218
$postgresLockId1 = PostgresLockId::fromKeyValue('test');
219219
$postgresLockId2 = PostgresLockId::fromKeyValue('test2');
220-
$locker->tryAcquireLock($dbConnection, $postgresLockId1);
221-
$locker->tryAcquireLock($dbConnection, $postgresLockId2);
220+
$locker->tryAcquireLockWithinSession($dbConnection, $postgresLockId1);
221+
$locker->tryAcquireLockWithinSession($dbConnection, $postgresLockId2);
222222

223-
$locker->releaseAllLocks($dbConnection);
223+
$locker->releaseAllLocksWithinSession($dbConnection);
224224

225225
$this->assertPgAdvisoryLocksCount(0);
226226
}
@@ -230,7 +230,7 @@ public function testItCanReleaseAllLocksInConnectionIfNoLocksWereAcquired(): voi
230230
$locker = $this->initLocker();
231231
$dbConnection = $this->initPostgresPdoConnection();
232232

233-
$locker->releaseAllLocks($dbConnection);
233+
$locker->releaseAllLocksWithinSession($dbConnection);
234234

235235
$this->assertPgAdvisoryLocksCount(0);
236236
}
@@ -244,19 +244,19 @@ public function testItCanReleaseAllLocksInConnectionButKeepsOtherLocks(): void
244244
$postgresLockId2 = PostgresLockId::fromKeyValue('test2');
245245
$postgresLockId3 = PostgresLockId::fromKeyValue('test3');
246246
$postgresLockId4 = PostgresLockId::fromKeyValue('test4');
247-
$locker->tryAcquireLock($dbConnection1, $postgresLockId1);
248-
$locker->tryAcquireLock($dbConnection1, $postgresLockId2);
249-
$locker->tryAcquireLock($dbConnection2, $postgresLockId3);
250-
$locker->tryAcquireLock($dbConnection2, $postgresLockId4);
247+
$locker->tryAcquireLockWithinSession($dbConnection1, $postgresLockId1);
248+
$locker->tryAcquireLockWithinSession($dbConnection1, $postgresLockId2);
249+
$locker->tryAcquireLockWithinSession($dbConnection2, $postgresLockId3);
250+
$locker->tryAcquireLockWithinSession($dbConnection2, $postgresLockId4);
251251

252-
$locker->releaseAllLocks($dbConnection1);
252+
$locker->releaseAllLocksWithinSession($dbConnection1);
253253

254254
$this->assertPgAdvisoryLocksCount(2);
255255
$this->assertPgAdvisoryLockExistsInConnection($dbConnection2, $postgresLockId3);
256256
$this->assertPgAdvisoryLockExistsInConnection($dbConnection2, $postgresLockId4);
257257
}
258258

259-
public function testItCanAcquireLockWithinTransaction(): void
259+
public function testItCanTryAcquireLockWithinTransaction(): void
260260
{
261261
$locker = $this->initLocker();
262262
$dbConnection = $this->initPostgresPdoConnection();
@@ -293,7 +293,7 @@ public function testItCannotAcquireLockInSecondConnectionIfTakenWithinTransactio
293293
$dbConnection1->beginTransaction();
294294
$locker->tryAcquireLockWithinTransaction($dbConnection1, $postgresLockId);
295295

296-
$isLockAcquired = $locker->tryAcquireLock($dbConnection2, $postgresLockId);
296+
$isLockAcquired = $locker->tryAcquireLockWithinSession($dbConnection2, $postgresLockId);
297297

298298
$this->assertFalse($isLockAcquired);
299299
$this->assertPgAdvisoryLocksCount(1);
@@ -349,7 +349,7 @@ public function testItCannotReleaseLockAcquiredWithinTransaction(): void
349349
$dbConnection->beginTransaction();
350350
$locker->tryAcquireLockWithinTransaction($dbConnection, $postgresLockId);
351351

352-
$isLockReleased = $locker->releaseLock($dbConnection, $postgresLockId);
352+
$isLockReleased = $locker->releaseLockWithinSession($dbConnection, $postgresLockId);
353353

354354
$this->assertFalse($isLockReleased);
355355
$this->assertPgAdvisoryLocksCount(1);
@@ -362,11 +362,11 @@ public function testItCannotReleaseAllLocksAcquiredWithinTransaction(): void
362362
$dbConnection = $this->initPostgresPdoConnection();
363363
$postgresLockId1 = PostgresLockId::fromKeyValue('test');
364364
$postgresLockId2 = PostgresLockId::fromKeyValue('test2');
365-
$locker->tryAcquireLock($dbConnection, $postgresLockId1);
365+
$locker->tryAcquireLockWithinSession($dbConnection, $postgresLockId1);
366366
$dbConnection->beginTransaction();
367367
$locker->tryAcquireLockWithinTransaction($dbConnection, $postgresLockId2);
368368

369-
$locker->releaseAllLocks($dbConnection);
369+
$locker->releaseAllLocksWithinSession($dbConnection);
370370

371371
$this->assertPgAdvisoryLocksCount(1);
372372
$this->assertPgAdvisoryLockMissingInConnection($dbConnection, $postgresLockId1);

0 commit comments

Comments
 (0)