Skip to content

Instantly share code, notes, and snippets.

@CMCDragonkai
Last active March 3, 2022 21:24
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save CMCDragonkai/a7b446f15094f59083a2 to your computer and use it in GitHub Desktop.
Save CMCDragonkai/a7b446f15094f59083a2 to your computer and use it in GitHub Desktop.
PHP: Simulating Lock Timeout with PHP's Flock
<?php
/**
* Acquires a lock using flock, provide it a file stream, the
* lock type, a timeout in microseconds, and a sleep_by in microseconds.
* PHP's flock does not currently have a timeout or queuing mechanism.
* So we have to hack a optimistic method of continuously sleeping
* and retrying to acquire the lock until we reach a timeout.
* Doing this in microseconds is a good idea, as seconds are too
* granular and can allow a new thread to cheat the queue.
* There's no actual queue of locks being implemented here, so
* it is fundamentally non-deterministic when multiple threads
* try to acquire a lock with a timeout.
* This means a possible failure is resource starvation.
* For example, if there's too many concurrent threads competing for
* a lock, then this implementation may allow the second thread to be
* starved and allow the third thread to acquire the lock.
* The trick here is in the combination of LOCK_NB and $blocking.
* The $blocking variable is assigned by reference, it returns 1
* when the flock is blocked from acquiring a lock. With LOCK_NB
* the flock returns immediately instead of waiting indefinitely.
*
* @param resource $lockfile Lock file resource that is opened.
* @param constant $locktype LOCK_EX or LOCK_SH
* @param integer $timeout_micro In microseconds, where 1 second = 1,000,000 microseconds
* @param float $sleep_by_micro Microsecond sleep period, by default 0.01 of a second
* @return boolean
*/
function flock_t ($lockfile, $locktype, $timeout_micro, $sleep_by_micro = 10000) {
if (!is_resource($lockfile)) {
throw new \InvalidArgumentException ('The $lockfile was not a file resource or the resource was closed.');
}
if ($sleep_by_micro < 1) {
throw new \InvalidArgumentException ('The $sleep_by_micro cannot be less than 1, or else an infinite loop.');
}
if ($timeout_micro < 1) {
$locked = flock ($lockfile, $locktype | LOCK_NB);
} else {
$count_micro = 0;
$locked = true;
while (!flock($lockfile, $locktype | LOCK_NB, $blocking)) {
if ($blocking AND (($count_micro += $sleep_by_micro) <= $timeout_micro)) {
usleep($sleep_by_micro);
} else {
$locked = false;
break;
}
}
}
return $locked;
}
@tsmgeek
Copy link

tsmgeek commented May 30, 2017

Undefined $blocking variable, I set this to FALSE;
I also added the following so that an Exception could be thrown if lock was not possable.
if($exceptionOnLocked===TRUE && $locked===FALSE){ throw new Exception ('The $lockfile was not possable to be locked.'); }

@CMCDragonkai
Copy link
Author

@tsmgeek

I didn't see that error before, are why not then just have declare $blocking prior to the loop?

Exception shouldn't be needed simply because whether the lock was locked or not is returned true/false.

@REWBrianH
Copy link

REWBrianH commented Apr 12, 2018

Why don't you use microtime so that sleep_by_micro can be 0? This would also make the timer more accurate.

@CMCDragonkai
Copy link
Author

Don't see what you mean.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment