Created
May 21, 2014 19:10
-
-
Save jippi/8bc58782c76c64f49f55 to your computer and use it in GitHub Desktop.
This file contains hidden or 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 | |
| /** | |
| * Global Locking using Redis | |
| * | |
| */ | |
| class RedisLock { | |
| /** | |
| * How long should the lock be kept active, even if the process dies | |
| * | |
| * @var integer | |
| */ | |
| public static $lockTimeout = 20; | |
| /** | |
| * Stores the expire time of the currently held lock | |
| * | |
| * @var int | |
| */ | |
| protected static $_expire; | |
| /** | |
| * Gets a lock or waits for it to become available | |
| * | |
| * @param mixed $key Item to lock | |
| * @param int $timeout Time to wait for the key (seconds) | |
| * @return mixed The key | |
| * @throws LockException If the key is invalid | |
| * @throws LockTimeoutException If the lock is not acquired before the method times out | |
| */ | |
| public static function get($key, $timeout = 10, $sleep = 1) { | |
| if (empty($key)) { | |
| throw new LockException('Invalid Key'); | |
| } | |
| $start = time(); | |
| $acquired = false; | |
| do { | |
| static::$_expire = static::timeout(); | |
| if ($acquired = (static::_getRedis()->setnx("mutex:{$key}", static::$_expire))) { | |
| break; | |
| } | |
| if ($acquired = (static::recover($key))) { | |
| break; | |
| } | |
| if ($timeout === 0) { | |
| break; | |
| } | |
| sleep($sleep); | |
| } while(!is_numeric($timeout) || time() < $start + $timeout); | |
| if (!$acquired) { | |
| throw new LockTimeoutException("Timeout exceeded"); | |
| } | |
| return $key; | |
| } | |
| /** | |
| * Releases the lock | |
| * | |
| * @param mixed $key Item to lock | |
| * @throws LockException If the key is invalid | |
| */ | |
| public static function release($key) { | |
| if (empty($key)) { | |
| throw new LockException('Invalid Key'); | |
| } | |
| // Only release the lock if it hasn't expired | |
| if (static::$_expire > time()) { | |
| static::_getRedis()->del("mutex:{$key}"); | |
| } | |
| } | |
| /** | |
| * Generates an expire time based on the current time | |
| * | |
| * @return int timeout | |
| */ | |
| protected static function timeout() { | |
| return (int) (time() + static::$lockTimeout + 1); | |
| } | |
| /** | |
| * Recover an abandoned lock | |
| * | |
| * @param mixed $key Item to lock | |
| * @return bool Was the lock acquired? | |
| */ | |
| protected static function recover($key) { | |
| if (($lockTimeout = static::_getRedis()->get("mutex:{$key}")) > time()) { | |
| return false; | |
| } | |
| $timeout = static::timeout(); | |
| $currentTimeout = static::_getRedis()->getset("mutex:{$key}", $timeout); | |
| if ($currentTimeout != $lockTimeout) { | |
| return false; | |
| } | |
| static::$_expire = $timeout; | |
| return true; | |
| } | |
| /** | |
| * Get an redis instance | |
| * | |
| * @return Redis | |
| */ | |
| protected static function _getRedis() { | |
| return ConnectionManager::getDataSource('redis'); | |
| } | |
| } | |
| class LockException extends RuntimeException { | |
| } | |
| class LockTimeoutException extends LockException { | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment