redis)) { $this->redis = Yii::createObject($this->redis); if(!$this->redis || !$this->redis instanceof Connection){ throw new Exception('Redis class injected must be instanceof of "yii\redis\Connection"'); } } } public function lock($resource, $ttl) { $token = uniqid(); $retry = $this->retryCount; do { $hasGetLock = false; $startTime = microtime(true) * 1000; if ($this->lockInstance($resource, $token, $ttl)) { $hasGetLock = true; } # Add 2 milliseconds to the drift to account for Redis expires # precision, which is 1 millisecond, plus 1 millisecond min drift # for small TTLs. $drift = ($ttl * $this->clockDriftFactor) + 2; $validityTime = $ttl - (microtime(true) * 1000 - $startTime) - $drift; if ($hasGetLock && $validityTime > 0) { return [ 'validity' => $validityTime, 'resource' => $resource, 'token' => $token, ]; } else { $this->unlockInstance($resource, $token); } // Wait a random delay before to retry $delay = mt_rand(floor($this->retryDelay / 2), $this->retryDelay); usleep($delay * 1000); $retry--; } while ($retry > 0); return false; } public function unlock(array $lock) { $resource = $lock['resource']; $token = $lock['token']; $this->unlockInstance($resource, $token); } private function lockInstance($resource, $token, $ttl) { return $this->redis->set($resource, $token, 'PX', $ttl, 'NX'); } private function unlockInstance($resource, $token) { $script = ' if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end '; return $this->redis->eval($script, 1, $resource, $token); } }