PHP has built in support for semaphores via the
sem_get(), sem_acquire(), and sem_release() commands. But support for semaphores
must be compiled into PHP, and it is not enabled by default. So it sometimes happens that you are trying
to develop for a system where it has not been included, and you cannot just
recompile the PHP (like in the case you are trying to develop for company X’s
production servers). In a case like this
if you need semaphores, you have no choice but to roll your own.
Note the “maxLockMinutes” parameter in the “getSemaphore()”
method. If something happens and you
fail to clean up at the end of this method, you will not end up locking
yourself out of the resource permanently.
Once the process that has created the semaphore terminates, the lock on
the file will be released. But if the
process hangs or otherwise lingers around, you need some way to break the
lock. The “maxLockMinutes=5” parameter
tells the semaphore class to ignore any file lock that is older than X minutes. I have defaulted it to 5 minutes here but
you’ll want to tune it for your own system.
The bad thing about this is that the calling
program that knows how long it needs to lock the resource for cannot dictate
it. We could embed the lock time in the
filename or possibly write it to the file if that is a requirement.
I left the Semaphore class embedded in the page
I used to test it.
<html>
<body>
<?php
define("UNIQUE_KEY", '123456');
define("BASE_DIRECTORY", '/tmp/');
class Semaphore {
/* var stream */
private $fp;
/* var string */
private $filename;
/**
* Constructor
*/
public function __construct() {
}
/**
·
Attempt to get an exclusive lock on
this semaphore
·
* @param string $baseDirectory
* @param string $uniqueKey
* @param int $maxLockMinutes
*
* @return bool
*/
public function getSemaphore($baseDirectory, $uniqueKey, $maxLockMinutes=5) {
$this->filename = $baseDirectory . 'semlock-' . $uniqueKey . '.sem';
$this->releaseExpiredSemaphore($maxLockMinutes);
$this->fp = fopen($this->filename, 'w');
return (flock($this->fp, LOCK_EX | LOCK_NB));
}
/**
·
Release expired semaphore
*
* @param int $maxValidLockMinutes
*/
private function releaseExpiredSemaphore(($maxValidLockMinutes) {
if (file_exists($this->filename)) {
$fileDate = filemtime($this->filename);
if (time() - $fileDate > ($maxValidLockMinutes * 60) {
unlink($this->filename);
}
}
}
/**
* Release semaphore
*/
public function releaseSemaphore()
{
flock($this->fp, LOCK_UN);
fclose($this->fp);
unlink($this->filename);
}
}
$semTest = new Semaphore();
$sem = $semTest->getSemaphore(BASE_DIRECTORY,
UNIQUE_KEY, 5);
if($sem) {
echo "Got lock <br>";
} else {
echo "Failed to get lock. Boohoo!
<br />";
die();
}
for($i = 0; $i < 60; $i++) {
sleep(5);
echo ("sleeping
" . $i * 5 . " hold you horses...<br
/>");
}
$semTest->releaseSemaphore();
?>
</body>
</html>