0
\$\begingroup\$

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

asked Mar 13, 2023 at 9:37
\$\endgroup\$
7
  • \$\begingroup\$ wonder if it should have been $flags = (($flags & LOCK_UN) ? $flags : ($flags | LOCK_NB)); 🤔 PHP does weird stuff with LOCK_UN, ref reddit.com/r/lolphp/comments/uapav7/… \$\endgroup\$ Commented Mar 13, 2023 at 9:39
  • \$\begingroup\$ I find the way the $would_block parameter of the flockWithTimeout() function is used strange. It seems to serve no purpose. \$\endgroup\$ Commented Mar 13, 2023 at 12:38
  • \$\begingroup\$ @KIKOSoftware well it ensures that the arguments are compatible with flock's arguments, making it a drop-in replacements. there's also a very obscure use case, detecting if it timed out or if flock failed for some other reason (eg if the function returns false and $would_block is also false, it suggest some error other than timeout, for example trying to flock() on FAT32, or some filesystem without flock support, some networked filesystems doesn't) \$\endgroup\$ Commented Mar 13, 2023 at 12:43
  • 1
    \$\begingroup\$ OK, I see, yes, that makes sense. I missed the & 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 by flock($handle, $flags, $would_block). \$\endgroup\$ Commented Mar 13, 2023 at 12:48
  • \$\begingroup\$ I think there may be a big problem with this. If user A uses flock to hold an exclusive lock, but then the process ends unexpectedly, the lock will continue to be held, perhaps for many minutes or hours, so no other user can obtain the lock. There is no way to force-unlock standard locks like flock, to repair the lock after a timeout. \$\endgroup\$ Commented Jan 2 at 20:44

1 Answer 1

1
\$\begingroup\$

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.
answered Mar 13, 2023 at 13:00
\$\endgroup\$
1
  • \$\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\$ Commented Mar 20, 2023 at 21:27

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.