Skip to content

Commit 413e1cd

Browse files
committed
Split etcd storage logic from Lock class
1 parent 49f7738 commit 413e1cd

File tree

6 files changed

+164
-125
lines changed

6 files changed

+164
-125
lines changed

src/AbstractLock.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
namespace Aternos\Lock;
44

5+
/**
6+
* Abstract base class for locks that contains getters and setters for the lock properties.
7+
*/
58
abstract class AbstractLock implements LockInterface
69
{
710
/**

src/Lock.php

Lines changed: 42 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,23 @@
22

33
namespace Aternos\Lock;
44

5-
use Aternos\Etcd\Client;
6-
use Aternos\Etcd\ClientInterface;
7-
use Aternos\Etcd\Exception\Status\DeadlineExceededException;
8-
use Aternos\Etcd\Exception\Status\InvalidResponseStatusCodeException;
9-
use Aternos\Etcd\Exception\Status\UnavailableException;
10-
use Aternos\Etcd\Exception\Status\UnknownException;
5+
use Aternos\Lock\Storage\EtcdStorage;
6+
use Aternos\Lock\Storage\StorageException;
7+
use Aternos\Lock\Storage\StorageInterface;
8+
use Exception;
119

1210
/**
13-
* Class Lock
11+
* LockInterface implementation using etcd-like storage
1412
*
1513
* @package Aternos\Lock
1614
*/
1715
class Lock extends AbstractLock
1816
{
1917
/**
20-
* see Lock::setClient()
21-
*
22-
* @var ClientInterface|null
18+
* @see static::setStorage()
19+
* @var StorageInterface|null
2320
*/
24-
protected static ?ClientInterface $client = null;
21+
protected static ?StorageInterface $storage = null;
2522

2623
/**
2724
* see Lock::setPrefix()
@@ -66,15 +63,22 @@ class Lock extends AbstractLock
6663
protected static int $delayPerUnavailableRetry = 1;
6764

6865
/**
69-
* Set the etcd client (Aternos\Etcd\Client)
70-
*
71-
* Uses a localhost client if not set
72-
*
73-
* @param ClientInterface $client
66+
* Set the storage interface used to store locks. If not set, {@link EtcdStorage} is used.
67+
* @param StorageInterface $storage
68+
* @return void
69+
*/
70+
public static function setStorage(StorageInterface $storage): void
71+
{
72+
static::$storage = $storage;
73+
}
74+
75+
/**
76+
* Get the storage interface used to store locks. If not set, {@link EtcdStorage} is used.
77+
* @return StorageInterface
7478
*/
75-
public static function setClient(ClientInterface $client): void
79+
protected static function getStorage(): StorageInterface
7680
{
77-
static::$client = $client;
81+
return static::$storage ??= new EtcdStorage();
7882
}
7983

8084
/**
@@ -110,12 +114,13 @@ public static function setMaxSaveRetries(int $retries): void
110114
}
111115

112116
/**
113-
* Set the maximum delay in microseconds (1,000,000 microseconds = 1 second) that should used for the random delay between retries
117+
* Set the maximum delay in microseconds (1,000,000 microseconds = 1 second) that should be used for the random
118+
* delay between retries.
114119
*
115120
* The delay is random and calculated like this: rand(0, $retries * $delayPerRetry)
116121
*
117122
* Lower value = faster retries (probably more retries necessary)
118-
* Higher value = slower retries (probably less retries necessary)
123+
* Higher value = slower retries (probably fewer retries necessary)
119124
*
120125
* @param int $delayPerRetry
121126
*/
@@ -144,20 +149,6 @@ public static function setDelayPerUnavailableRetry(int $delayPerRetry): void
144149
static::$delayPerUnavailableRetry = $delayPerRetry;
145150
}
146151

147-
/**
148-
* Get an Aternos\Etcd\Client instance
149-
*
150-
* @return ClientInterface
151-
*/
152-
protected static function getClient(): ClientInterface
153-
{
154-
if (static::$client === null) {
155-
static::$client = new Client();
156-
}
157-
158-
return static::$client;
159-
}
160-
161152
/**
162153
* Full name of the key in etcd (prefix + key)
163154
*
@@ -222,7 +213,7 @@ public function __construct(
222213
* Try to acquire lock
223214
*
224215
* @return bool true if the lock was acquired, false if it was not
225-
* @throws InvalidResponseStatusCodeException
216+
* @throws StorageException
226217
* @throws TooManySaveRetriesException
227218
*/
228219
public function lock(): bool
@@ -247,7 +238,7 @@ public function lock(): bool
247238
*
248239
* @param int|null $waitTime maximum time in seconds to wait for other locks
249240
* @return bool
250-
* @throws InvalidResponseStatusCodeException
241+
* @throws StorageException
251242
*/
252243
public function waitForOtherLocks(?int $waitTime = null): bool
253244
{
@@ -284,7 +275,7 @@ public function getRemainingLockDuration(): int
284275
* Refresh the lock
285276
*
286277
* @return boolean
287-
* @throws InvalidResponseStatusCodeException
278+
* @throws StorageException
288279
* @throws TooManySaveRetriesException
289280
*/
290281
public function refresh(): bool
@@ -312,7 +303,7 @@ public function refresh(): bool
312303
* Should be only used if you have the lock
313304
*
314305
* @return void
315-
* @throws InvalidResponseStatusCodeException
306+
* @throws StorageException
316307
* @throws TooManySaveRetriesException
317308
*/
318309
public function break(): void
@@ -340,7 +331,7 @@ protected function generateLock(): LockEntry
340331
* Remove a lock from the locking array and save the locks
341332
*
342333
* @return void
343-
* @throws InvalidResponseStatusCodeException
334+
* @throws StorageException
344335
* @throws TooManySaveRetriesException
345336
*/
346337
protected function removeLock(): void
@@ -362,7 +353,7 @@ protected function removeLock(): void
362353
*
363354
* @param int $time
364355
* @return bool
365-
* @throws InvalidResponseStatusCodeException
356+
* @throws StorageException
366357
* @throws TooManySaveRetriesException
367358
*/
368359
protected function addOrUpdateLock(int $time): bool
@@ -389,8 +380,9 @@ protected function addOrUpdateLock(int $time): bool
389380
* changed since the last update, they will be updated by this function again.
390381
*
391382
* @return bool
392-
* @throws InvalidResponseStatusCodeException
383+
* @throws StorageException
393384
* @throws TooManySaveRetriesException
385+
* @throws Exception
394386
*/
395387
protected function saveLocks(): bool
396388
{
@@ -408,9 +400,9 @@ protected function saveLocks(): bool
408400
if (count($this->locks) === 0) {
409401
for ($i = 1; $i <= static::$maxUnavailableRetries; $i++) {
410402
try {
411-
$result = static::getClient()->deleteIf($this->etcdKey, $previousLocks, !$delayRetry);
403+
$result = static::getStorage()->deleteIf($this->etcdKey, $previousLocks, !$delayRetry);
412404
break;
413-
} catch (UnavailableException | DeadlineExceededException | UnknownException $e) {
405+
} catch (StorageException $e) {
414406
if ($i === static::$maxUnavailableRetries) {
415407
throw $e;
416408
} else {
@@ -424,9 +416,9 @@ protected function saveLocks(): bool
424416

425417
for ($i = 1; $i <= static::$maxUnavailableRetries; $i++) {
426418
try {
427-
$result = static::getClient()->putIf($this->etcdKey, $lockString, $previousLocks, !$delayRetry);
419+
$result = static::getStorage()->putIf($this->etcdKey, $lockString, $previousLocks, !$delayRetry);
428420
break;
429-
} catch (UnavailableException | DeadlineExceededException | UnknownException $e) {
421+
} catch (StorageException $e) {
430422
if ($i === static::$maxUnavailableRetries) {
431423
throw $e;
432424
} else {
@@ -479,17 +471,18 @@ protected function canLock(): bool
479471
/**
480472
* Update the locks array from etcd
481473
*
482-
* @throws InvalidResponseStatusCodeException
483474
* @return $this
475+
* @throws StorageException
476+
* @throws Exception
484477
*/
485478
public function update(): static
486479
{
487480
$etcdLockString = false;
488481
for ($i = 1; $i <= static::$maxUnavailableRetries; $i++) {
489482
try {
490-
$etcdLockString = static::getClient()->get($this->etcdKey);
483+
$etcdLockString = static::getStorage()->get($this->etcdKey);
491484
break;
492-
} catch (UnavailableException | DeadlineExceededException | UnknownException $e) {
485+
} catch (StorageException $e) {
493486
if ($i === static::$maxUnavailableRetries) {
494487
throw $e;
495488
} else {
@@ -524,7 +517,7 @@ protected function updateFromString(string|bool $lockString): static
524517
/**
525518
* Break the lock on destruction of this object
526519
*
527-
* @throws InvalidResponseStatusCodeException
520+
* @throws StorageException
528521
* @throws TooManySaveRetriesException
529522
*/
530523
public function __destruct()

src/Storage/EtcdStorage.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
namespace Aternos\Lock\Storage;
4+
5+
use Aternos\Etcd\Client;
6+
use Aternos\Etcd\ClientInterface;
7+
use Aternos\Etcd\Exception\Status\DeadlineExceededException;
8+
use Aternos\Etcd\Exception\Status\UnavailableException;
9+
use Aternos\Etcd\Exception\Status\UnknownException;
10+
11+
class EtcdStorage implements StorageInterface
12+
{
13+
protected ClientInterface $client;
14+
15+
public function __construct(?ClientInterface $client = null)
16+
{
17+
$this->client = $client ?? new Client();
18+
}
19+
20+
public function putIf(string $key, string $value, bool|string $previousValue, bool $returnNewValueOnFail): bool|string
21+
{
22+
try {
23+
return $this->client->putIf($key, $value, $previousValue, $returnNewValueOnFail);
24+
} catch (UnavailableException | DeadlineExceededException | UnknownException $e) {
25+
throw new StorageException($e->getMessage(), $e->getCode(), $e);
26+
}
27+
}
28+
29+
public function deleteIf(string $key, $previousValue, bool $returnNewValueOnFail = false): bool|string
30+
{
31+
try {
32+
return $this->client->deleteIf($key, $previousValue, $returnNewValueOnFail);
33+
} catch (UnavailableException | DeadlineExceededException | UnknownException $e) {
34+
throw new StorageException($e->getMessage(), $e->getCode(), $e);
35+
}
36+
}
37+
38+
39+
public function get(string $key): bool|string
40+
{
41+
try {
42+
return $this->client->get($key);
43+
} catch (UnavailableException | DeadlineExceededException | UnknownException $e) {
44+
throw new StorageException($e->getMessage(), $e->getCode(), $e);
45+
}
46+
}
47+
}

src/Storage/StorageException.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Aternos\Lock\Storage;
4+
5+
use Exception;
6+
7+
/**
8+
* An exception thrown by a storage client. This should only be used for known exceptions as the operation will be retried by default.
9+
*/
10+
class StorageException extends Exception
11+
{
12+
13+
}

src/Storage/StorageInterface.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
namespace Aternos\Lock\Storage;
4+
5+
use Exception;
6+
7+
/**
8+
* Interface for a storage that can be used to perform etcd-like operations.
9+
*/
10+
interface StorageInterface
11+
{
12+
/**
13+
* Put `$value` if `$key` value matches `$previousValue` otherwise `$returnNewValueOnFail`
14+
* @param string $key
15+
* @param string $value The new value to set
16+
* @param bool|string $previousValue The previous value to compare against
17+
* @param bool $returnNewValueOnFail
18+
* @return bool|string
19+
* @throws StorageException a known, retryable error occurred
20+
* @throws Exception an unknown error occurred
21+
*/
22+
public function putIf(string $key, string $value, bool|string $previousValue, bool $returnNewValueOnFail): bool|string;
23+
24+
/**
25+
* Delete if $key value matches $previous value otherwise $returnNewValueOnFail
26+
*
27+
* @param string $key
28+
* @param bool|string $previousValue The previous value to compare against
29+
* @param bool $returnNewValueOnFail
30+
* @return bool|string
31+
* @throws StorageException a known, retryable error occurred
32+
* @throws Exception an unknown error occurred
33+
*/
34+
public function deleteIf(string $key, bool|string $previousValue, bool $returnNewValueOnFail = false): bool|string;
35+
36+
/**
37+
* Get the value of a key
38+
* @param string $key
39+
* @return bool|string
40+
* @throws StorageException a known, retryable error occurred
41+
* @throws Exception an unknown error occurred
42+
*/
43+
public function get(string $key): bool|string;
44+
}

0 commit comments

Comments
 (0)