i want a flock with timeout, unfortunately i haven't found a good way to implement it (like a signaled timeout on an actual blocking request?), so i'm just trying and sleeping until success or timeout, code:
function flockWithTimeout($handle, int $flags, int &$would_block = null, float $timeout_seconds = 10, float $sleep_time_seconds = 0.01): bool
{
$flags = $flags | LOCK_NB;
$would_block = null;
$timeout_timestamp = microtime(true) + $timeout_seconds;
$sleep_time_microseconds = (int)($sleep_time_seconds * 1000000);
for (;;) {
$success = flock($handle, $flags, $would_block);
if ($success) {
return true;
}
if (!$would_block) {
// something else is wrong, like flocking on FAT32 which doesn't support flock?
return false;
}
// another process has the lock
if (microtime(true) >= $timeout_timestamp) {
return false;
}
usleep($sleep_time_microseconds);
}
throw new \LogicException('unreachable');
}
sample usage:
$h = tmpfile();
$h2 = fopen(stream_get_meta_data($h)['uri'], "rb");
if(flockWithTimeout($h, LOCK_EX)){
echo "got the lock! :)\n";
} else {
echo "did not get the lock :(\n";
}
$timeout_seconds = 0.1;
if(flockWithTimeout($h2, LOCK_SH, $would_block, $timeout_seconds)){
echo "got the lock! :)\n";
} else {
echo "did not get the lock :(\n";
}
should print
got the lock! :)
did not get the lock :(
with the did not get the lock :(
taking roughly 100 milliseconds
1 Answer 1
I think this function looks good. Your choice of variable names is good. I'm not an expert on file locking, but, although valid code, I don't like the for (;;)
forever loop. Let me try to rewrite that. What about this:
function flockWithTimeout($handle, int $flags, int &$would_block = null, float $timeout_seconds = 10, float $sleep_time_seconds = 0.01): bool
{
$flags = $flags | LOCK_NB;
$timeout_timestamp = microtime(true) + $timeout_seconds;
do {
if (flock($handle, $flags, $would_block)) {
// success, we got the file lock, we're done
return true;
}
if (!$would_block) {
// something else is wrong, like flocking on FAT32 which doesn't support flock?
return false;
}
usleep((int)($sleep_time_seconds * 1000000));
} while (microtime(true) < $timeout_timestamp);
// timed out, another process has the lock
return false;
}
- There's no need to do
$would_block = null;
. - Since you're wasting time anyway, I put the computation inside the
usleep()
function. This is debatable... - I got rid of the exception, sorry, I didn't see the point.
- The
$success
variable wasn't really needed.
-
\$\begingroup\$ thanks! by the way, i think removing the LOCK_UN check is a mistake, pretty sure the LOCK_NB bits is not supposed to be set for LOCK_UN, and at least in PHP it does make a difference: it's entirely possible that the value 7 doesn't mean anything (i'm not sure): 3v4l.org/N0eLH \$\endgroup\$hanshenrik– hanshenrik2023年03月20日 21:27:18 +00:00Commented Mar 20, 2023 at 21:27
$flags = (($flags & LOCK_UN) ? $flags : ($flags | LOCK_NB));
🤔 PHP does weird stuff with LOCK_UN, ref reddit.com/r/lolphp/comments/uapav7/… \$\endgroup\$$would_block
parameter of theflockWithTimeout()
function is used strange. It seems to serve no purpose. \$\endgroup\$&
in front of the$would_block
argument. It means that it points to a variable outside the function. It will return the value that was set byflock($handle, $flags, $would_block)
. \$\endgroup\$