Skip to content

Commit 62e5acb

Browse files
authored
Improve distributed locking (#77)
1 parent f2d29aa commit 62e5acb

File tree

2 files changed

+33
-4
lines changed

2 files changed

+33
-4
lines changed

src/Mutex/DistributedMutex.php

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,10 @@ protected function acquireWithToken(string $key, float $expireTimeout)
5252

5353
// 2.
5454
$acquiredIndexes = [];
55+
$notAcquired = 0;
5556
$errored = 0;
5657
$exception = null;
57-
foreach ($this->mutexes as $index => $mutex) {
58+
foreach ($this->getMutexesInRandomOrder() as $index => $mutex) {
5859
try {
5960
if ($this->acquireMutex($mutex, $key, $acquireTimeout - (microtime(true) - $startTs), $expireTimeout)) {
6061
$acquiredIndexes[] = $index;
@@ -68,6 +69,14 @@ protected function acquireWithToken(string $key, float $expireTimeout)
6869

6970
++$errored;
7071
}
72+
73+
if (end($acquiredIndexes) !== $index) {
74+
++$notAcquired;
75+
}
76+
77+
if (!$this->isCountMajority(count($this->mutexes) - $notAcquired)) {
78+
break;
79+
}
7180
}
7281

7382
// 3.
@@ -129,6 +138,22 @@ protected function releaseWithToken(string $key, string $token): bool
129138
}
130139
}
131140

141+
/**
142+
* @return array<int, AbstractSpinlockMutex>
143+
*/
144+
private function getMutexesInRandomOrder(): array
145+
{
146+
$indexes = array_keys($this->mutexes);
147+
shuffle($indexes);
148+
149+
$res = [];
150+
foreach ($indexes as $index) {
151+
$res[$index] = $this->mutexes[$index];
152+
}
153+
154+
return $res;
155+
}
156+
132157
/**
133158
* @return bool True if the count is the majority
134159
*/

tests/Mutex/DistributedMutexTest.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public function testTooFewServerToAcquire(int $count, int $available): void
9393
$mutex = $this->createDistributedMutexMock($count);
9494

9595
$i = 0;
96-
$mutex->expects(self::exactly($count))
96+
$mutex->expects(self::atMost((int) floor($count / 2) + $count - $available))
9797
->method('acquireMutex')
9898
->willReturnCallback(static function () use (&$i, $available) {
9999
if ($i++ < $available) {
@@ -307,6 +307,9 @@ public static function provideMinorityCases(): iterable
307307
yield [4, 0];
308308
yield [4, 1];
309309
yield [4, 2];
310+
yield [5, 2];
311+
yield [6, 2];
312+
yield [6, 3];
310313
}
311314

312315
/**
@@ -321,6 +324,7 @@ public static function provideMajorityCases(): iterable
321324
yield [3, 2];
322325
yield [3, 3];
323326
yield [4, 3];
327+
yield [5, 3];
324328
}
325329

326330
public function testAcquireMutexLogger(): void
@@ -329,12 +333,12 @@ public function testAcquireMutexLogger(): void
329333
$logger = $this->createMock(LoggerInterface::class);
330334
$mutex->setLogger($logger);
331335

332-
$mutex->expects(self::exactly(3))
336+
$mutex->expects(self::exactly(2))
333337
->method('acquireMutex')
334338
->with(self::isInstanceOf(AbstractSpinlockMutex::class), 'distributed', 1.0, \INF)
335339
->willThrowException($this->createMock(/* PredisException::class */ LockAcquireException::class));
336340

337-
$logger->expects(self::exactly(3))
341+
$logger->expects(self::exactly(2))
338342
->method('warning')
339343
->with('Could not set {key} = {token} at server #{index}', self::anything());
340344

0 commit comments

Comments
 (0)