Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

readLock and writeLock #5

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions README-zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,48 @@ public function lock(ResponseInterface $response)
```


- 读锁
```
/**
* @return \Psr\Http\Message\ResponseInterface
*/
public function lockA(ResponseInterface $response)
{
try {
$lock = new RedisLock($this->redis, 'lock', 4);
$res = $lock->readLock(4, function () {
return [456];
}, 250000);
return $response->json(['res' => $res]);
// catch the exception
} catch (LockTimeoutException $exception) {
var_dump('lockA lock check timeout');
return $response->json(['res' => false, 'message' => 'timeout']);
}
}
```
- 写锁
```
/**
* @return \Psr\Http\Message\ResponseInterface
*/
public function lockA(ResponseInterface $response)
{
try {
$lock = new RedisLock($this->redis, 'lock', 4);
$res = $lock->writeLock(4, function () {
return [456];
}, 250000);
return $response->json(['res' => $res]);
// catch the exception
} catch (LockTimeoutException $exception) {
var_dump('lockA lock check timeout');
return $response->json(['res' => false, 'message' => 'timeout']);
}
}
```


## 最后

#### 代码贡献
Expand Down
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,46 @@ for example:
}
```

- read lock
```
/**
* @return \Psr\Http\Message\ResponseInterface
*/
public function lockA(ResponseInterface $response)
{
try {
$lock = new RedisLock($this->redis, 'lock', 4);
$res = $lock->readLock(4, function () {
return [456];
}, 250000);
return $response->json(['res' => $res]);
// catch the exception
} catch (LockTimeoutException $exception) {
var_dump('lockA lock check timeout');
return $response->json(['res' => false, 'message' => 'timeout']);
}
}
```
- write lock
```
/**
* @return \Psr\Http\Message\ResponseInterface
*/
public function lockA(ResponseInterface $response)
{
try {
$lock = new RedisLock($this->redis, 'lock', 4);
$res = $lock->writeLock(4, function () {
return [456];
}, 250000);
return $response->json(['res' => $res]);
// catch the exception
} catch (LockTimeoutException $exception) {
var_dump('lockA lock check timeout');
return $response->json(['res' => false, 'message' => 'timeout']);
}
}
```

## Finally

Expand Down
71 changes: 71 additions & 0 deletions src/Lock.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ abstract class Lock implements LockContract
{
use InteractsWithTime;

const LOCK_MODE_SHARE = 1;
const LOCK_MODE_WRITE = 2;

/**
* The name of the lock
* @var string
Expand Down Expand Up @@ -42,12 +45,34 @@ public function __construct($name, $seconds, $owner = null)
*/
abstract public function acquire();

/**
* Attempt to acquire the share lock
* @return bool
*/
abstract protected function acquireShareLock();

/**
* Attempt to acquire the write lock
* @return bool
*/
abstract protected function acquireWriteLock(): bool;

/**
* Release the lock
* @return void
*/
abstract public function release();

/**
* @return void
*/
abstract protected function releaseShareLock();

/**
* @return void
*/
abstract protected function releaseWriteLock();

/**
* Returns the owner value written into the driver for this lock
* @return string
Expand Down Expand Up @@ -104,6 +129,52 @@ public function block($seconds, $callback = null)
return true;
}

/**
* @throws LockTimeoutException
*/
public function readLock($seconds, $callback = null, $interval = 250000)
{
$starting = $this->currentTime();
while ($this->acquireShareLock() == 0) {
usleep($interval);
if ($this->currentTime() - $seconds >= $starting) {
throw new LockTimeoutException();
}
}
if (is_callable($callback)) {
try {
return $callback();
} finally {
$this->releaseShareLock();
}
}

return true;
}

/**
* @throws LockTimeoutException
*/
public function writeLock($seconds, $callback = null, $interval = 250000)
{
$starting = $this->currentTime();
while ($this->acquireWriteLock() == 0) {
usleep($interval);
if ($this->currentTime() - $seconds >= $starting) {
throw new LockTimeoutException();
}
}
if (is_callable($callback)) {
try {
return $callback();
} finally {
$this->releaseWriteLock();
}
}

return true;
}

/**
* Returns the current owner of the lock.
*
Expand Down
18 changes: 18 additions & 0 deletions src/LockContract.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,24 @@ public function get($callback = null);
*/
public function block($seconds, $callback = null);

/**
* Attempt to acquire share lock for given number of seconds, try every $interval microseconds
* @param $seconds
* @param null $callback
* @param int $interval
* @return mixed
*/
public function readLock($seconds, $callback = null, $interval = 250000);

/**
* Attempt to acquire exclusive lock for given number of seconds, try every $interval microseconds
* @param $seconds
* @param null $callback
* @param int $interval
* @return mixed
*/
public function writeLock($seconds, $callback = null, $interval = 250000);

/**
* Release the lock
* @return mixed
Expand Down
93 changes: 93 additions & 0 deletions src/LockScripts.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,97 @@ public static function releaseLock()
end
LUA;
}

/**
* lock in share mode
* eval param key_name , share_mode, expire_time
* @return string
*/
public static function shareLock()
{
return <<<LUA
local mode = redis.call('hget', KEYS[1], 'mode')
if mode == false then
local lock = redis.call('hset', KEYS[1], 'mode', ARGV[1])
redis.call('expire', KEYS[1], ARGV[2])
redis.call('hincrby', KEYS[1], 'lock_count', 1)
return lock
elseif mode == ARGV[1] then
redis.call('expire', KEYS[1], ARGV[2])
redis.call('hincrby', KEYS[1], 'lock_count', 1)
return 1
else
return 0
end
LUA;
}

/**
* lock in write mode
* eval param key_name, LOCK_MODE_WRITE, LOCK_OWNER, EXPIRE_TIME
* @return string
*/
public static function writeLock()
{
return <<<LUA
local mode = redis.call('hget', KEYS[1], 'mode')
if mode == false then
local res = redis.call('hset', KEYS[1], 'mode', ARGV[1])
redis.call('expire', KEYS[1], ARGV[3])
redis.call('hset', KEYS[1], 'owner', ARGV[2])
return res
else
return 0
end
LUA;
}

/**
* release share lock
* eval params key_name, LOCK_MODE_READ
* @return string
*/
public static function releaseShareLock()
{
return <<<LUA
local mode = redis.call('hget', KEYS[1], 'mode')
if mode == false then
return 1
elseif mode ~= ARGV[1] then
return 0
else
local lock_count = redis.call('hget', KEYS[1], 'lock_count')
if lock_count == false or tonumber(lock_count) <= 1 then
return redis.call('del', KEYS[1])
else
redis.call('hincrby', KEYS[1], 'lock_count', -1)
return 0
end
end
LUA;
}

/**
* release write lock
* eval params key_name, LOCK_MODE_WRITE, LOCK_OWNER
* @return string
*/
public static function releaseWriteLock()
{
return <<<LUA
-- release writ lock: key_name, LOCK_MODE_WRITE LOCK_OWNER
local mode = redis.call('hget', KEYS[1], 'mode')
if mode == ARGV[1] then
local owner = redis.call('hget', KEYS[1], 'owner')
if owner == ARGV[2] then
return redis.call('del', KEYS[1])
else
return 0
end
else
return 0
end
LUA;

}
}
52 changes: 52 additions & 0 deletions src/RedisLock.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,40 @@ public function acquire()
return intval($result) === 1;
}

/**
* the key name of read lock and write lock
* @return string
*/
protected function getReadWriteLockKey()
{
return 'readWriteLock:' . $this->name;
}

/**
* @inheritDoc
*/
protected function acquireShareLock(): bool
{
$shareLockScript = LockScripts::shareLock();
$expireTime = $this->seconds > 0 ? $this->seconds : 30;

$result = $this->redis->eval($shareLockScript, [$this->getReadWriteLockKey(), self::LOCK_MODE_SHARE, $expireTime], 1);
return intval($result) === 1;
}

/**
* @inheritDoc
* @return bool
*/
protected function acquireWriteLock(): bool
{
$lua = LockScripts::writeLock();
$expireTime = $this->seconds > 0 ? $this->seconds : 30;

$result = $this->redis->eval($lua, [$this->getReadWriteLockKey(), self::LOCK_MODE_WRITE, $this->owner, $expireTime], 1);
return intval($result) === 1;
}

/**
* @inheritDoc
*/
Expand All @@ -44,6 +78,24 @@ public function release()
}
}

/**
* @inheritDoc
*/
protected function releaseShareLock()
{
$lua = LockScripts::releaseShareLock();
$this->redis->eval($lua, [$this->getReadWriteLockKey(), self::LOCK_MODE_SHARE], 1);
}

/**
* @inheritDoc
*/
protected function releaseWriteLock()
{
$lua = LockScripts::releaseWriteLock();
$this->redis->eval($lua, [$this->getReadWriteLockKey(), self::LOCK_MODE_WRITE, $this->owner], 1);
}

/**
* @inheritDoc
*/
Expand Down