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 Amp\Sync\SyncException;
|
||||||
use function Amp\delay;
|
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
|
final class FileMutex implements Mutex
|
||||||
{
|
{
|
||||||
private const LATENCY_TIMEOUT = 0.01;
|
private const LATENCY_TIMEOUT = 0.01;
|
||||||
@ -31,16 +38,39 @@ final class FileMutex implements Mutex
|
|||||||
if (!$this->filesystem->isDirectory($this->directory)) {
|
if (!$this->filesystem->isDirectory($this->directory)) {
|
||||||
throw new SyncException(\sprintf('Directory of "%s" does not exist or is not a directory', $this->fileName));
|
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
|
// Try to create the lock file. If the file already exists, someone else
|
||||||
// has the lock, so set an asynchronous timer and try again.
|
// has the lock, so set an asynchronous timer and try again.
|
||||||
for ($attempt = 0; true; ++$attempt) {
|
for ($attempt = 0; true; ++$attempt) {
|
||||||
if (\flock($f, LOCK_EX|LOCK_NB)) {
|
try {
|
||||||
$lock = new Lock(fn () => \flock($f, LOCK_UN));
|
$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;
|
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\Lock;
|
||||||
use Amp\Sync\SyncException;
|
use Amp\Sync\SyncException;
|
||||||
use function Amp\delay;
|
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
|
final class KeyedFileMutex implements KeyedMutex
|
||||||
{
|
{
|
||||||
private const LATENCY_TIMEOUT = 0.01;
|
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