-
-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add lock function #34
base: 2.x
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,8 +2,14 @@ | |
|
||
namespace Amp\File; | ||
|
||
use Amp\CancellationToken; | ||
use Amp\Delayed; | ||
use Amp\Loop; | ||
use Amp\NullCancellationToken; | ||
use Amp\Promise; | ||
use Amp\Sync\Lock; | ||
|
||
use function Amp\call; | ||
|
||
const LOOP_STATE_IDENTIFIER = Driver::class; | ||
|
||
|
@@ -350,3 +356,77 @@ function put(string $path, string $contents): Promise | |
{ | ||
return filesystem()->put($path, $contents); | ||
} | ||
|
||
/** | ||
* Asynchronously lock a file | ||
* Resolves with a callable that MUST eventually be called in order to release the lock. | ||
* | ||
* @param string $file File to lock | ||
* @param bool $shared Whether to acquire a shared or exclusive lock (\LOCK_SH or \LOCK_EX, see PHP flock docs) | ||
* @param integer $polling Polling interval for lock in milliseconds | ||
* @param CancellationToken $token Cancellation token | ||
* | ||
* @return \Amp\Promise Resolves with an \Amp\Sync\Lock | ||
*/ | ||
function lock(string $file, bool $shared, int $polling = 100, CancellationToken $token = null): Promise | ||
{ | ||
return call(static function () use ($file, $shared, $polling, $token) { | ||
$operation = $shared ? \LOCK_SH : \LOCK_EX; | ||
$token = $token ?? new NullCancellationToken; | ||
if (!yield exists($file)) { | ||
yield \touch($file); | ||
StatCache::clear($file); | ||
} | ||
$operation |= LOCK_NB; | ||
$res = \fopen($file, 'c'); | ||
|
||
while (!\flock($res, $operation, $wouldblock)) { | ||
if (!$wouldblock) { | ||
throw new FilesystemException("Failed acquiring lock on file."); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we check for the file being deleted here and recreate it? I guess it could make sense to delete after unlocking, but not in every case (i.e. only if it's a separate lock file). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I really don't think this is the place for such logic |
||
} | ||
yield new Delayed($polling); | ||
$token->throwIfRequested(); | ||
} | ||
|
||
return new Lock( | ||
0, | ||
static function () use (&$res) { | ||
if ($res) { | ||
\flock($res, LOCK_UN); | ||
\fclose($res); | ||
$res = null; | ||
} | ||
} | ||
); | ||
}); | ||
} | ||
|
||
/** | ||
* Asynchronously lock a file (shared lock) | ||
* Resolves with a callable that MUST eventually be called in order to release the lock. | ||
* | ||
* @param string $file File to lock | ||
* @param integer $polling Polling interval for lock in milliseconds | ||
* @param CancellationToken $token Cancellation token | ||
* | ||
* @return \Amp\Promise Resolves with a callable that MUST eventually be called in order to release the lock. | ||
*/ | ||
function lockShared(string $file, int $polling = 100, CancellationToken $token = null): Promise | ||
{ | ||
return lock($file, true, $polling, $token ?? new NullCancellationToken); | ||
} | ||
|
||
/** | ||
* Asynchronously lock a file (exclusive lock) | ||
* Resolves with a callable that MUST eventually be called in order to release the lock. | ||
* | ||
* @param string $file File to lock | ||
* @param integer $polling Polling interval for lock in milliseconds | ||
* @param CancellationToken $token Cancellation token | ||
* | ||
* @return \Amp\Promise Resolves with a callable that MUST eventually be called in order to release the lock. | ||
*/ | ||
function lockExclusive(string $file, int $polling = 100, CancellationToken $token = null): Promise | ||
{ | ||
return lock($file, false, $polling, $token ?? new NullCancellationToken); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we just open the file here instead of the additional
touch
above?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well technically yes, but then the touching wouldn't be async.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, the
fopen
isn't async anyway? I guess we'd have to add the function toHandle
to have it really async?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, fopen isn't async, but afaik there is no way to obtain a lock using any of the native async libs supported by amphp.
I thought it'd be nice if we could make at least the
touch
part of thefopen
async.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@trowski What's your opinion here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm rather uncomfortable introducing anything that can block when it could be rolled into a simple
Task
executed using parallel.