123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148 |
- <?php
-
- namespace Illuminate\Redis\Limiters;
-
- use Illuminate\Contracts\Redis\LimiterTimeoutException;
-
- class DurationLimiter
- {
- /**
- * The Redis factory implementation.
- *
- * @var \Illuminate\Redis\Connections\Connection
- */
- private $redis;
-
- /**
- * The unique name of the lock.
- *
- * @var string
- */
- private $name;
-
- /**
- * The allowed number of concurrent tasks.
- *
- * @var int
- */
- private $maxLocks;
-
- /**
- * The number of seconds a slot should be maintained.
- *
- * @var int
- */
- private $decay;
-
- /**
- * The timestamp of the end of the current duration.
- *
- * @var int
- */
- public $decaysAt;
-
- /**
- * The number of remaining slots.
- *
- * @var int
- */
- public $remaining;
-
- /**
- * Create a new duration limiter instance.
- *
- * @param \Illuminate\Redis\Connections\Connection $redis
- * @param string $name
- * @param int $maxLocks
- * @param int $decay
- * @return void
- */
- public function __construct($redis, $name, $maxLocks, $decay)
- {
- $this->name = $name;
- $this->decay = $decay;
- $this->redis = $redis;
- $this->maxLocks = $maxLocks;
- }
-
- /**
- * Attempt to acquire the lock for the given number of seconds.
- *
- * @param int $timeout
- * @param callable|null $callback
- * @return bool
- * @throws \Illuminate\Contracts\Redis\LimiterTimeoutException
- */
- public function block($timeout, $callback = null)
- {
- $starting = time();
-
- while (! $this->acquire()) {
- if (time() - $timeout >= $starting) {
- throw new LimiterTimeoutException;
- }
-
- usleep(750 * 1000);
- }
-
- if (is_callable($callback)) {
- $callback();
- }
-
- return true;
- }
-
- /**
- * Attempt to acquire the lock.
- *
- * @return bool
- */
- public function acquire()
- {
- $results = $this->redis->eval($this->luaScript(), 1,
- $this->name, microtime(true), time(), $this->decay, $this->maxLocks
- );
-
- $this->decaysAt = $results[1];
-
- $this->remaining = max(0, $results[2]);
-
- return (bool) $results[0];
- }
-
- /**
- * Get the Lua script for acquiring a lock.
- *
- * KEYS[1] - The limiter name
- * ARGV[1] - Current time in microseconds
- * ARGV[2] - Current time in seconds
- * ARGV[3] - Duration of the bucket
- * ARGV[4] - Allowed number of tasks
- *
- * @return string
- */
- protected function luaScript()
- {
- return <<<'LUA'
- local function reset()
- redis.call('HMSET', KEYS[1], 'start', ARGV[2], 'end', ARGV[2] + ARGV[3], 'count', 1)
- return redis.call('EXPIRE', KEYS[1], ARGV[3] * 2)
- end
-
- if redis.call('EXISTS', KEYS[1]) == 0 then
- return {reset(), ARGV[2] + ARGV[3], ARGV[4] - 1}
- end
-
- if ARGV[1] >= redis.call('HGET', KEYS[1], 'start') and ARGV[1] <= redis.call('HGET', KEYS[1], 'end') then
- return {
- tonumber(redis.call('HINCRBY', KEYS[1], 'count', 1)) <= tonumber(ARGV[4]),
- redis.call('HGET', KEYS[1], 'end'),
- ARGV[4] - redis.call('HGET', KEYS[1], 'count')
- }
- end
-
- return {reset(), ARGV[2] + ARGV[3], ARGV[4] - 1}
- LUA;
- }
- }
|