Last active
March 3, 2022 21:24
-
-
Save CMCDragonkai/a7b446f15094f59083a2 to your computer and use it in GitHub Desktop.
PHP: Simulating Lock Timeout with PHP's Flock
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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; | |
} |
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.
Why don't you use microtime so that sleep_by_micro can be 0? This would also make the timer more accurate.
Don't see what you mean.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.'); }