Last active
December 25, 2015 11:48
-
-
Save craiga/6971226 to your computer and use it in GitHub Desktop.
Locking functions
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 | |
require_once(realpath(dirname(__FILE__ )) . "/../logging-trait/Logging.php"); | |
/** | |
* Functions for locking. | |
* | |
* _lock, _unlock and _isLockedByOther for file-based, non-blocking locking. | |
* | |
* @author Craig Anderson <craiga@craiga.id.au> | |
* @link https://gist.github.com/craiga/6971226 | |
*/ | |
trait Locking | |
{ | |
use Logging; // include https://gist.github.com/craiga/1849563 | |
protected function _isLockedByOther() | |
{ | |
$isLocked = false; | |
if (!$this->_ignoreLock && !$this->_ownsLock) { | |
$lockFileName = $this->_getLockFileName(); | |
if (file_exists($lockFileName)) { | |
$timeCreated = filemtime($lockFileName); | |
$timeSinceCreation = time() - $timeCreated; | |
if ($timeSinceCreation > $this->_lockTimeout) { | |
$this->_log(LOG_NOTICE, "Lock file has existed for %s seconds; timing out.", number_format($timeSinceCreation)); | |
if (!@unlink($this->_getLockFileName())) { | |
throw new RuntimeException(sprintf("Couldn't delete lock file %s", $this->_getLockFileName())); | |
} | |
} | |
else { | |
$isLocked = true; | |
} | |
} | |
} | |
return $isLocked; | |
} | |
protected function _lock() | |
{ | |
if ($this->_isLockedByOther()) { | |
throw new RuntimeException("Cannot lock this process as a lock belonging to another instance already exists!"); | |
} | |
if ($this->_lockDepth < 1) { | |
$this->_log(LOG_DEBUG, "Locking"); | |
} | |
else { | |
$this->_log(LOG_DEBUG, "Renewing lock"); | |
} | |
if (!@touch($this->_getLockFileName())) { | |
throw new RuntimeException(sprintf("Couldn't create lock file %s", $this->_getLockFileName())); | |
} | |
$this->_ownsLock = true; | |
$this->_lockDepth++; | |
} | |
protected function _unlock() | |
{ | |
if ($this->_isLockedByOther()) { | |
throw new RuntimeException("Cannot unlock this process as a lock belonging to another instance already exists!"); | |
} | |
$this->_lockDepth--; | |
if ($this->_lockDepth < 1) { | |
$this->_log(LOG_DEBUG, "Unlocking"); | |
if (!@unlink($this->_getLockFileName())) { | |
throw new RuntimeException(sprintf("Couldn't delete lock file %s", $this->_getLockFileName())); | |
} | |
$this->_ownsLock = false; | |
} | |
} | |
protected function _getLockFileName() | |
{ | |
$f = sys_get_temp_dir() . DIRECTORY_SEPARATOR . get_class($this) . ".lock"; | |
$this->_log(LOG_DEBUG, "Using lock file %s", $f); | |
return $f; | |
} | |
protected $_lockDepth = 0; | |
protected $_ownsLock = false; | |
protected $_lockTimeout = 3600; // one hour | |
protected $_ignoreLock = false; | |
} |
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 | |
class LockingExample | |
{ | |
use Locking; | |
function doStuff() { | |
// configure locking | |
$this->_lockTimeout = 60 * 5; // five minutes | |
if ($this->_isLockedByOther()) { // check for lock | |
$this->_log(LOG_INFO, "Process locked; aborting."); // die | |
} | |
else { | |
$this->_lock(); // lock | |
try { | |
$items = $this->_getItems(); | |
foreach ($items as $item) { | |
$this->_lock(); // renew lock | |
try { | |
$item->doMoreStuff(); | |
$this->_unlock(); // decrease lock depth | |
} | |
catch (Exception $e) { | |
$this->_unlock(); // decrease lock depth in error | |
throw $e; | |
} | |
} | |
$this->_unlock(); // unlock | |
} | |
catch (Exception $e) { | |
$this->_unlock(); // unlock in error | |
throw $e; | |
} | |
} | |
} | |
} |
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 | |
require("Locking.php"); | |
class LockingTest | |
{ | |
use Locking; | |
public function __construct() | |
{ | |
$this->setLogging(false); | |
} | |
public function setLogging($enable = true) | |
{ | |
$this->_logFile = $enable ? "php://output" : null; | |
} | |
public function lock() | |
{ | |
return $this->_lock(); | |
} | |
public function unlock() | |
{ | |
return $this->_unlock(); | |
} | |
public function isLockedByOther() | |
{ | |
return $this->_isLockedByOther(); | |
} | |
public function setIgnoreLock($ignore = true) | |
{ | |
$this->_ignoreLock = $ignore; | |
} | |
} | |
function assertEqual($expected, $actual) { | |
$failCount = 0; | |
if ($expected != $actual) { | |
printf("FAIL: Expected \"%s\"; got \"%s\"\n", $expected, $actual); | |
$failCount++; | |
} | |
return $failCount; | |
} | |
$failCount = 0; | |
$a = new LockingTest(); | |
$b = new LockingTest(); | |
print("Testing initial state\n"); | |
$failCount += assertEqual(false, $a->isLockedByOther()); | |
$failCount += assertEqual(false, $b->isLockedByOther()); | |
$a->lock(); | |
print("Testing after lock\n"); | |
$failCount += assertEqual(false, $a->isLockedByOther()); | |
$failCount += assertEqual(true, $b->isLockedByOther()); | |
print("Testing after ignoring lock\n"); | |
$b->setIgnoreLock(true); | |
$failCount += assertEqual(false, $a->isLockedByOther()); | |
$failCount += assertEqual(false, $b->isLockedByOther()); | |
$b->setIgnoreLock(false); | |
print("Testing after reignoring lock\n"); | |
$failCount += assertEqual(false, $a->isLockedByOther()); | |
$failCount += assertEqual(true, $b->isLockedByOther()); | |
$a->unlock(); | |
print("Testing after unlock\n"); | |
$failCount += assertEqual(false, $a->isLockedByOther()); | |
$failCount += assertEqual(false, $b->isLockedByOther()); | |
$a->lock(); | |
print("Testing locking from seperate instance after lock\n"); | |
try { | |
$b->lock(); | |
printf("FAIL: Expected exception\n"); | |
$failCount++; | |
} | |
catch (Exception $e) { | |
// expected | |
} | |
print("Testing unlocking from seperate instance after lock\n"); | |
try { | |
$b->unlock(); | |
printf("FAIL: Expected exception\n"); | |
$failCount++; | |
} | |
catch (Exception $e) { | |
// expected | |
} | |
printf("Testing still locked\n"); | |
$failCount += assertEqual(false, $a->isLockedByOther()); | |
$failCount += assertEqual(true, $b->isLockedByOther()); | |
$a->unlock(); | |
$b->lock(); | |
$b->lock(); | |
$b->lock(); | |
printf("Testing after three locks\n"); | |
$failCount += assertEqual(true, $a->isLockedByOther()); | |
$failCount += assertEqual(false, $b->isLockedByOther()); | |
$b->unlock(); | |
printf("Testing after three locks and one unlock\n"); | |
$failCount += assertEqual(true, $a->isLockedByOther()); | |
$failCount += assertEqual(false, $b->isLockedByOther()); | |
$b->unlock(); | |
printf("Testing after three locks and two unlocks\n"); | |
$failCount += assertEqual(true, $a->isLockedByOther()); | |
$failCount += assertEqual(false, $b->isLockedByOther()); | |
$b->unlock(); | |
printf("Testing after three locks and three unlocks\n"); | |
$failCount += assertEqual(false, $a->isLockedByOther()); | |
$failCount += assertEqual(false, $b->isLockedByOther()); | |
printf("%s %s failed.\n", number_format($failCount), $failCount == 1 ? "test" : "tests"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment