mirror of
https://github.com/danog/file.git
synced 2024-12-02 09:17:57 +01:00
Bump
This commit is contained in:
parent
600acf5474
commit
8041a2b428
@ -8,6 +8,13 @@ use Amp\Sync\Mutex;
|
||||
use Amp\Sync\SyncException;
|
||||
use function Amp\delay;
|
||||
|
||||
/**
|
||||
* Async mutex based on files.
|
||||
*
|
||||
* A crash of the program will NOT release the lock, manual user action will be required to remove the lockfile.
|
||||
*
|
||||
* For a mutex that will automatically release the lock in case of a crash, see LockingFileMutex.
|
||||
*/
|
||||
final class FileMutex implements Mutex
|
||||
{
|
||||
private const LATENCY_TIMEOUT = 0.01;
|
||||
@ -31,16 +38,39 @@ final class FileMutex implements Mutex
|
||||
if (!$this->filesystem->isDirectory($this->directory)) {
|
||||
throw new SyncException(\sprintf('Directory of "%s" does not exist or is not a directory', $this->fileName));
|
||||
}
|
||||
$f = \fopen($this->fileName, 'c');
|
||||
|
||||
// Try to create the lock file. If the file already exists, someone else
|
||||
// has the lock, so set an asynchronous timer and try again.
|
||||
for ($attempt = 0; true; ++$attempt) {
|
||||
if (\flock($f, LOCK_EX|LOCK_NB)) {
|
||||
$lock = new Lock(fn () => \flock($f, LOCK_UN));
|
||||
try {
|
||||
$file = $this->filesystem->openFile($this->fileName, 'x');
|
||||
|
||||
// Return a lock object that can be used to release the lock on the mutex.
|
||||
$lock = new Lock($this->release(...));
|
||||
|
||||
$file->close();
|
||||
|
||||
return $lock;
|
||||
} catch (FilesystemException) {
|
||||
delay(\min(self::DELAY_LIMIT, self::LATENCY_TIMEOUT * (2 ** $attempt)), cancellation: $cancellation);
|
||||
}
|
||||
delay(\min(self::DELAY_LIMIT, self::LATENCY_TIMEOUT * (2 ** $attempt)), cancellation: $cancellation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the lock on the mutex.
|
||||
*
|
||||
* @throws SyncException
|
||||
*/
|
||||
private function release(): void
|
||||
{
|
||||
try {
|
||||
$this->filesystem->deleteFile($this->fileName);
|
||||
} catch (\Throwable $exception) {
|
||||
throw new SyncException(
|
||||
'Failed to unlock the mutex file: ' . $this->fileName,
|
||||
previous: $exception,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,13 @@ use Amp\Sync\KeyedMutex;
|
||||
use Amp\Sync\Lock;
|
||||
use Amp\Sync\SyncException;
|
||||
use function Amp\delay;
|
||||
|
||||
/**
|
||||
* Asynckeyed mutex based on files.
|
||||
*
|
||||
* A crash of the program will NOT release the lock, manual user action will be required to remove the lockfile.
|
||||
*
|
||||
* For a mutex that will automatically release the lock in case of a crash, see KeyedLockingFileMutex.
|
||||
*/
|
||||
final class KeyedFileMutex implements KeyedMutex
|
||||
{
|
||||
private const LATENCY_TIMEOUT = 0.01;
|
||||
|
61
src/KeyedLockingFileMutex.php
Normal file
61
src/KeyedLockingFileMutex.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Amp\File;
|
||||
|
||||
use Amp\Cancellation;
|
||||
use Amp\Sync\KeyedMutex;
|
||||
use Amp\Sync\Lock;
|
||||
use Amp\Sync\SyncException;
|
||||
use function Amp\delay;
|
||||
/**
|
||||
* Async keyed mutex based on flock.
|
||||
*
|
||||
* A crash of the program will automatically release the lock, but the lockfiles will never be removed from the filesystem (even in case of successful release).
|
||||
*
|
||||
* For a mutex that removes the lockfiles but does not release the lock in case of a crash (requiring manual user action to clean up), see KeyedFileMutex.
|
||||
*/
|
||||
|
||||
final class KeyedLockingFileMutex implements KeyedMutex
|
||||
{
|
||||
private const LATENCY_TIMEOUT = 0.01;
|
||||
private const DELAY_LIMIT = 1;
|
||||
|
||||
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, "/\\");
|
||||
}
|
||||
|
||||
public function acquire(string $key, ?Cancellation $cancellation = null): Lock
|
||||
{
|
||||
if (!$this->filesystem->isDirectory($this->directory)) {
|
||||
throw new SyncException(\sprintf('Directory "%s" does not exist or is not a directory', $this->directory));
|
||||
}
|
||||
|
||||
$filename = $this->getFilename($key);
|
||||
|
||||
$f = \fopen($filename, 'c');
|
||||
|
||||
// Try to create the lock file. If the file already exists, someone else
|
||||
// has the lock, so set an asynchronous timer and try again.
|
||||
for ($attempt = 0; true; ++$attempt) {
|
||||
if (\flock($f, LOCK_EX|LOCK_NB)) {
|
||||
$lock = new Lock(fn () => \flock($f, LOCK_UN));
|
||||
return $lock;
|
||||
}
|
||||
delay(\min(self::DELAY_LIMIT, self::LATENCY_TIMEOUT * (2 ** $attempt)), cancellation: $cancellation);
|
||||
}
|
||||
}
|
||||
|
||||
private function getFilename(string $key): string
|
||||
{
|
||||
return $this->directory . '/' . \hash('sha256', $key) . '.lock';
|
||||
}
|
||||
}
|
53
src/LockingFileMutex.php
Normal file
53
src/LockingFileMutex.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Amp\File;
|
||||
|
||||
use Amp\Cancellation;
|
||||
use Amp\Sync\Lock;
|
||||
use Amp\Sync\Mutex;
|
||||
use Amp\Sync\SyncException;
|
||||
use function Amp\delay;
|
||||
|
||||
/**
|
||||
* Async mutex based on flock.
|
||||
*
|
||||
* A crash of the program will automatically release the lock, but the lockfiles will never be removed from the filesystem (even in case of successful release).
|
||||
*
|
||||
* For a mutex that removes the lockfiles but does not release the lock in case of a crash (requiring manual user action to clean up), see FileMutex.
|
||||
*/
|
||||
final class LockingFileMutex implements Mutex
|
||||
{
|
||||
private const LATENCY_TIMEOUT = 0.01;
|
||||
private const DELAY_LIMIT = 1;
|
||||
|
||||
private readonly Filesystem $filesystem;
|
||||
|
||||
private readonly string $directory;
|
||||
|
||||
/**
|
||||
* @param string $fileName Name of temporary file to use as a mutex.
|
||||
*/
|
||||
public function __construct(private readonly string $fileName, ?Filesystem $filesystem = null)
|
||||
{
|
||||
$this->filesystem = $filesystem ?? filesystem();
|
||||
$this->directory = \dirname($this->fileName);
|
||||
}
|
||||
|
||||
public function acquire(?Cancellation $cancellation = null): Lock
|
||||
{
|
||||
if (!$this->filesystem->isDirectory($this->directory)) {
|
||||
throw new SyncException(\sprintf('Directory of "%s" does not exist or is not a directory', $this->fileName));
|
||||
}
|
||||
$f = \fopen($this->fileName, 'c');
|
||||
|
||||
// Try to create the lock file. If the file already exists, someone else
|
||||
// has the lock, so set an asynchronous timer and try again.
|
||||
for ($attempt = 0; true; ++$attempt) {
|
||||
if (\flock($f, LOCK_EX|LOCK_NB)) {
|
||||
$lock = new Lock(fn () => \flock($f, LOCK_UN));
|
||||
return $lock;
|
||||
}
|
||||
delay(\min(self::DELAY_LIMIT, self::LATENCY_TIMEOUT * (2 ** $attempt)), cancellation: $cancellation);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user