-
-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add KeyedFileMutex * Rebase and update * Style fix * Swap prefix for directory --------- Co-authored-by: Aaron Piotrowski <aaron@trowski.com>
- Loading branch information
Showing
2 changed files
with
102 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace Amp\File; | ||
|
||
use Amp\Sync\KeyedMutex; | ||
use Amp\Sync\Lock; | ||
use Amp\Sync\SyncException; | ||
use ValueError; | ||
use function Amp\delay; | ||
|
||
final class KeyedFileMutex implements KeyedMutex | ||
{ | ||
private const LATENCY_TIMEOUT = 0.01; | ||
|
||
private readonly Filesystem $filesystem; | ||
|
||
private readonly string $directory; | ||
|
||
/** | ||
* @param string $directory Directory in which to store key files. | ||
*/ | ||
public function __construct(string $directory, ?Filesystem $filesystem = null) | ||
{ | ||
$this->filesystem = $filesystem ?? filesystem(); | ||
$this->directory = \rtrim($directory, "/\\"); | ||
|
||
if (!$this->filesystem->isDirectory($this->directory)) { | ||
throw new ValueError(\sprintf('Directory "%s" does not exist', $this->directory)); | ||
} | ||
} | ||
|
||
public function acquire(string $key): Lock | ||
{ | ||
$filename = $this->getFilename($key); | ||
|
||
// Try to create the lock file. If the file already exists, someone else | ||
// has the lock, so set an asynchronous timer and try again. | ||
while (true) { | ||
try { | ||
$file = $this->filesystem->openFile($filename, 'x'); | ||
|
||
// Return a lock object that can be used to release the lock on the mutex. | ||
$lock = new Lock(fn () => $this->release($filename)); | ||
|
||
$file->close(); | ||
|
||
return $lock; | ||
} catch (FilesystemException) { | ||
delay(self::LATENCY_TIMEOUT); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Releases the lock on the mutex. | ||
* | ||
* @throws SyncException | ||
*/ | ||
private function release(string $filename): void | ||
{ | ||
try { | ||
$this->filesystem->deleteFile($filename); | ||
} catch (\Throwable $exception) { | ||
throw new SyncException( | ||
'Failed to unlock the mutex file: ' . $filename, | ||
previous: $exception, | ||
); | ||
} | ||
} | ||
|
||
private function getFilename(string $key): string | ||
{ | ||
return $this->directory . '/' . \hash('sha256', $key) . '.lock'; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace Amp\File\Test; | ||
|
||
use Amp\File\KeyedFileMutex; | ||
use Amp\Sync\AbstractKeyedMutexTest; | ||
use Amp\Sync\KeyedMutex; | ||
|
||
final class KeyedFileMutexTest extends AbstractKeyedMutexTest | ||
{ | ||
protected function setUp(): void | ||
{ | ||
parent::setUp(); | ||
Fixture::init(); | ||
} | ||
|
||
protected function tearDown(): void | ||
{ | ||
parent::tearDown(); | ||
Fixture::clear(); | ||
} | ||
|
||
public function createMutex(): KeyedMutex | ||
{ | ||
return new KeyedFileMutex(Fixture::path()); | ||
} | ||
} |