Skip to content

Commit af4f378

Browse files
authored
Introduce DistributedMutex class (#75)
1 parent 41509dd commit af4f378

20 files changed

+583
-669
lines changed

README.md

Lines changed: 38 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
**[Requirements](#requirements)** |
22
**[Installation](#installation)** |
33
**[Usage](#usage)** |
4+
**[Implementations](#implementations)** |
45
**[Authors](#authors)** |
56
**[License](#license)**
67

@@ -51,7 +52,7 @@ This library uses the namespace `Malkusch\Lock`.
5152

5253
The [`Malkusch\Lock\Mutex\Mutex`][5] interface provides the base API for this library.
5354

54-
#### Mutex::synchronized()
55+
### Mutex::synchronized()
5556

5657
[`Malkusch\Lock\Mutex\Mutex::synchronized()`][6] executes code exclusively. This
5758
method guarantees that the code is only executed by one process at once. Other
@@ -65,10 +66,7 @@ return value `false` or `null` should be seen as a failed action.
6566
Example:
6667

6768
```php
68-
$newBalance = $mutex->synchronized(function () use (
69-
$bankAccount,
70-
$amount
71-
): int {
69+
$newBalance = $mutex->synchronized(static function () use ($bankAccount, $amount) {
7270
$balance = $bankAccount->getBalance();
7371
$balance -= $amount;
7472
if ($balance < 0) {
@@ -80,7 +78,7 @@ $newBalance = $mutex->synchronized(function () use (
8078
});
8179
```
8280

83-
#### Mutex::check()
81+
### Mutex::check()
8482

8583
[`Malkusch\Lock\Mutex\Mutex::check()`][7] sets a callable, which will be
8684
executed when [`Malkusch\Lock\Util\DoubleCheckedLocking::then()`][8] is called,
@@ -108,24 +106,24 @@ this return value will not be checked by the library.
108106
Example:
109107

110108
```php
111-
$newBalance = $mutex->check(function () use ($bankAccount, $amount): bool {
109+
$newBalance = $mutex->check(static function () use ($bankAccount, $amount): bool {
112110
return $bankAccount->getBalance() >= $amount;
113-
})->then(function () use ($bankAccount, $amount): int {
111+
})->then(static function () use ($bankAccount, $amount) {
114112
$balance = $bankAccount->getBalance();
115113
$balance -= $amount;
116114
$bankAccount->setBalance($balance);
117115

118116
return $balance;
119117
});
120118

121-
if ($newBalance === false) {
119+
if (!$newBalance) {
122120
if ($balance < 0) {
123121
throw new \DomainException('You have no credit');
124122
}
125123
}
126124
```
127125

128-
#### Extracting code result after lock release exception
126+
### Extracting code result after lock release exception
129127

130128
Mutex implementations based on [`Malkush\Lock\Mutex\AbstractLockMutex`][10] will throw
131129
[`Malkusch\Lock\Exception\LockReleaseException`][11] in case of lock release
@@ -136,8 +134,8 @@ In order to read the code result (or an exception thrown there),
136134
Example:
137135
```php
138136
try {
139-
// or $mutex->check(...)
140-
$result = $mutex->synchronized(function () {
137+
// OR $mutex->check(...)
138+
$result = $mutex->synchronized(static function () {
141139
if (someCondition()) {
142140
throw new \DomainException();
143141
}
@@ -149,7 +147,7 @@ try {
149147
$codeException = $unlockException->getCodeException();
150148
// do something with the code exception
151149
} else {
152-
$code_result = $unlockException->getCodeResult();
150+
$codeResult = $unlockException->getCodeResult();
153151
// do something with the code result
154152
}
155153

@@ -158,7 +156,7 @@ try {
158156
}
159157
```
160158

161-
### Implementations
159+
## Implementations
162160

163161
You can choose from one of the provided [`Malkusch\Lock\Mutex\Mutex`](#mutex) interface
164162
implementations or create/extend your own implementation.
@@ -168,30 +166,23 @@ implementations or create/extend your own implementation.
168166
- [`RedisMutex`](#redismutex)
169167
- [`SemaphoreMutex`](#semaphoremutex)
170168
- [`MySQLMutex`](#mysqlmutex)
171-
- [`PostgreSQLMutex`](#PostgreSQLMutex)
169+
- [`PostgreSQLMutex`](#postgresqlmutex)
170+
- [`DistributedMutex`](#distributedmutex)
172171

173-
#### FlockMutex
172+
### FlockMutex
174173

175174
The **FlockMutex** is a lock implementation based on
176175
[`flock()`](https://php.net/manual/en/function.flock.php).
177176

178177
Example:
179178
```php
180179
$mutex = new FlockMutex(fopen(__FILE__, 'r'));
181-
$mutex->synchronized(function () use ($bankAccount, $amount) {
182-
$balance = $bankAccount->getBalance();
183-
$balance -= $amount;
184-
if ($balance < 0) {
185-
throw new \DomainException('You have no credit');
186-
}
187-
$bankAccount->setBalance($balance);
188-
});
189180
```
190181

191182
Timeouts are supported as an optional second argument. This uses the `ext-pcntl`
192183
extension if possible or busy waiting if not.
193184

194-
#### MemcachedMutex
185+
### MemcachedMutex
195186

196187
The **MemcachedMutex** is a spinlock implementation which uses the
197188
[`Memcached` extension](https://php.net/manual/en/book.memcached.php).
@@ -202,22 +193,13 @@ $memcached = new \Memcached();
202193
$memcached->addServer('localhost', 11211);
203194

204195
$mutex = new MemcachedMutex('balance', $memcached);
205-
$mutex->synchronized(function () use ($bankAccount, $amount) {
206-
$balance = $bankAccount->getBalance();
207-
$balance -= $amount;
208-
if ($balance < 0) {
209-
throw new \DomainException('You have no credit');
210-
}
211-
$bankAccount->setBalance($balance);
212-
});
213196
```
214197

215-
#### RedisMutex
198+
### RedisMutex
216199

217-
The **RedisMutex** is the distributed lock implementation of
218-
[RedLock](https://redis.io/topics/distlock#the-redlock-algorithm) which supports the
200+
The **RedisMutex** is a lock implementation which supports the
219201
[`phpredis` extension](https://github.com/phpredis/phpredis)
220-
or [`Predis` API](https://github.com/nrk/predis).
202+
or [`Predis` API](https://github.com/nrk/predis) clients.
221203

222204
Both Redis and Valkey servers are supported.
223205

@@ -230,18 +212,10 @@ $redis = new \Redis();
230212
$redis->connect('localhost');
231213
// OR $redis = new \Predis\Client('redis://localhost');
232214

233-
$mutex = new RedisMutex([$redis], 'balance');
234-
$mutex->synchronized(function () use ($bankAccount, $amount) {
235-
$balance = $bankAccount->getBalance();
236-
$balance -= $amount;
237-
if ($balance < 0) {
238-
throw new \DomainException('You have no credit');
239-
}
240-
$bankAccount->setBalance($balance);
241-
});
215+
$mutex = new RedisMutex($redis, 'balance');
242216
```
243217

244-
#### SemaphoreMutex
218+
### SemaphoreMutex
245219

246220
The **SemaphoreMutex** is a lock implementation based on
247221
[Semaphore](https://php.net/manual/en/ref.sem.php).
@@ -250,17 +224,9 @@ Example:
250224
```php
251225
$semaphore = sem_get(ftok(__FILE__, 'a'));
252226
$mutex = new SemaphoreMutex($semaphore);
253-
$mutex->synchronized(function () use ($bankAccount, $amount) {
254-
$balance = $bankAccount->getBalance();
255-
$balance -= $amount;
256-
if ($balance < 0) {
257-
throw new \DomainException('You have no credit');
258-
}
259-
$bankAccount->setBalance($balance);
260-
});
261227
```
262228

263-
#### MySQLMutex
229+
### MySQLMutex
264230

265231
The **MySQLMutex** uses MySQL's
266232
[`GET_LOCK`](https://dev.mysql.com/doc/refman/9.0/en/locking-functions.html#function_get-lock)
@@ -280,19 +246,10 @@ you to namespace your locks like `dbname.lockname`.
280246

281247
```php
282248
$pdo = new \PDO('mysql:host=localhost;dbname=test', 'username');
283-
284249
$mutex = new MySQLMutex($pdo, 'balance', 15);
285-
$mutex->synchronized(function () use ($bankAccount, $amount) {
286-
$balance = $bankAccount->getBalance();
287-
$balance -= $amount;
288-
if ($balance < 0) {
289-
throw new \DomainException('You have no credit');
290-
}
291-
$bankAccount->setBalance($balance);
292-
});
293250
```
294251

295-
#### PostgreSQLMutex
252+
### PostgreSQLMutex
296253

297254
The **PostgreSQLMutex** uses PostgreSQL's
298255
[advisory locking](https://www.postgresql.org/docs/9.4/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS)
@@ -306,16 +263,21 @@ interrupted, the lock is automatically released.
306263

307264
```php
308265
$pdo = new \PDO('pgsql:host=localhost;dbname=test', 'username');
309-
310266
$mutex = new PostgreSQLMutex($pdo, 'balance');
311-
$mutex->synchronized(function () use ($bankAccount, $amount) {
312-
$balance = $bankAccount->getBalance();
313-
$balance -= $amount;
314-
if ($balance < 0) {
315-
throw new \DomainException('You have no credit');
316-
}
317-
$bankAccount->setBalance($balance);
318-
});
267+
```
268+
269+
### DistributedMutex
270+
271+
The **DistributedMutex** is the distributed lock implementation of
272+
[RedLock](https://redis.io/topics/distlock#the-redlock-algorithm) which supports
273+
one or more [`Malkush\Lock\Mutex\AbstractSpinlockMutex`][10] instances.
274+
275+
Example:
276+
```php
277+
$mutex = new DistributedMutex([
278+
new \Predis\Client('redis://10.0.0.1'),
279+
new \Predis\Client('redis://10.0.0.2'),
280+
], 'balance');
319281
```
320282

321283
## Authors
@@ -341,3 +303,4 @@ This project is free and is licensed under the MIT.
341303
[9]: https://en.wikipedia.org/wiki/Double-checked_locking
342304
[10]: https://github.com/php-lock/lock/blob/3ca295ccda/src/Mutex/AbstractLockMutex.php
343305
[11]: https://github.com/php-lock/lock/blob/3ca295ccda/src/Exception/LockReleaseException.php
306+
[12]: https://github.com/php-lock/lock/blob/41509dda0a/src/Mutex/AbstractSpinlockMutex.php#L15

phpstan.neon.dist

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,6 @@ parameters:
1111

1212
ignoreErrors:
1313
# TODO
14-
-
15-
path: 'src/Mutex/AbstractRedlockMutex.php'
16-
identifier: if.condNotBoolean
17-
message: '~^Only booleans are allowed in an if condition, mixed given\.$~'
18-
count: 1
1914
-
2015
path: 'tests/Mutex/*Test.php'
2116
identifier: empty.notAllowed

0 commit comments

Comments
 (0)