Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Illuminate\Redis\Limiters;
4
 
5
use Illuminate\Contracts\Redis\LimiterTimeoutException;
6
 
7
class DurationLimiter
8
{
9
    /**
10
     * The Redis factory implementation.
11
     *
12
     * @var \Illuminate\Redis\Connections\Connection
13
     */
14
    private $redis;
15
 
16
    /**
17
     * The unique name of the lock.
18
     *
19
     * @var string
20
     */
21
    private $name;
22
 
23
    /**
24
     * The allowed number of concurrent tasks.
25
     *
26
     * @var int
27
     */
28
    private $maxLocks;
29
 
30
    /**
31
     * The number of seconds a slot should be maintained.
32
     *
33
     * @var int
34
     */
35
    private $decay;
36
 
37
    /**
38
     * The timestamp of the end of the current duration.
39
     *
40
     * @var int
41
     */
42
    public $decaysAt;
43
 
44
    /**
45
     * The number of remaining slots.
46
     *
47
     * @var int
48
     */
49
    public $remaining;
50
 
51
    /**
52
     * Create a new duration limiter instance.
53
     *
54
     * @param  \Illuminate\Redis\Connections\Connection  $redis
55
     * @param  string  $name
56
     * @param  int  $maxLocks
57
     * @param  int  $decay
58
     * @return void
59
     */
60
    public function __construct($redis, $name, $maxLocks, $decay)
61
    {
62
        $this->name = $name;
63
        $this->decay = $decay;
64
        $this->redis = $redis;
65
        $this->maxLocks = $maxLocks;
66
    }
67
 
68
    /**
69
     * Attempt to acquire the lock for the given number of seconds.
70
     *
71
     * @param  int  $timeout
72
     * @param  callable|null  $callback
73
     * @param  int  $sleep
74
     * @return mixed
75
     *
76
     * @throws \Illuminate\Contracts\Redis\LimiterTimeoutException
77
     */
78
    public function block($timeout, $callback = null, $sleep = 750)
79
    {
80
        $starting = time();
81
 
82
        while (! $this->acquire()) {
83
            if (time() - $timeout >= $starting) {
84
                throw new LimiterTimeoutException;
85
            }
86
 
87
            usleep($sleep * 1000);
88
        }
89
 
90
        if (is_callable($callback)) {
91
            return $callback();
92
        }
93
 
94
        return true;
95
    }
96
 
97
    /**
98
     * Attempt to acquire the lock.
99
     *
100
     * @return bool
101
     */
102
    public function acquire()
103
    {
104
        $results = $this->redis->eval(
105
            $this->luaScript(), 1, $this->name, microtime(true), time(), $this->decay, $this->maxLocks
106
        );
107
 
108
        $this->decaysAt = $results[1];
109
 
110
        $this->remaining = max(0, $results[2]);
111
 
112
        return (bool) $results[0];
113
    }
114
 
115
    /**
116
     * Determine if the key has been "accessed" too many times.
117
     *
118
     * @return bool
119
     */
120
    public function tooManyAttempts()
121
    {
122
        [$this->decaysAt, $this->remaining] = $this->redis->eval(
123
            $this->tooManyAttemptsLuaScript(), 1, $this->name, microtime(true), time(), $this->decay, $this->maxLocks
124
        );
125
 
126
        return $this->remaining <= 0;
127
    }
128
 
129
    /**
130
     * Clear the limiter.
131
     *
132
     * @return void
133
     */
134
    public function clear()
135
    {
136
        $this->redis->del($this->name);
137
    }
138
 
139
    /**
140
     * Get the Lua script for acquiring a lock.
141
     *
142
     * KEYS[1] - The limiter name
143
     * ARGV[1] - Current time in microseconds
144
     * ARGV[2] - Current time in seconds
145
     * ARGV[3] - Duration of the bucket
146
     * ARGV[4] - Allowed number of tasks
147
     *
148
     * @return string
149
     */
150
    protected function luaScript()
151
    {
152
        return <<<'LUA'
153
local function reset()
154
    redis.call('HMSET', KEYS[1], 'start', ARGV[2], 'end', ARGV[2] + ARGV[3], 'count', 1)
155
    return redis.call('EXPIRE', KEYS[1], ARGV[3] * 2)
156
end
157
 
158
if redis.call('EXISTS', KEYS[1]) == 0 then
159
    return {reset(), ARGV[2] + ARGV[3], ARGV[4] - 1}
160
end
161
 
162
if ARGV[1] >= redis.call('HGET', KEYS[1], 'start') and ARGV[1] <= redis.call('HGET', KEYS[1], 'end') then
163
    return {
164
        tonumber(redis.call('HINCRBY', KEYS[1], 'count', 1)) <= tonumber(ARGV[4]),
165
        redis.call('HGET', KEYS[1], 'end'),
166
        ARGV[4] - redis.call('HGET', KEYS[1], 'count')
167
    }
168
end
169
 
170
return {reset(), ARGV[2] + ARGV[3], ARGV[4] - 1}
171
LUA;
172
    }
173
 
174
    /**
175
     * Get the Lua script to determine if the key has been "accessed" too many times.
176
     *
177
     * KEYS[1] - The limiter name
178
     * ARGV[1] - Current time in microseconds
179
     * ARGV[2] - Current time in seconds
180
     * ARGV[3] - Duration of the bucket
181
     * ARGV[4] - Allowed number of tasks
182
     *
183
     * @return string
184
     */
185
    protected function tooManyAttemptsLuaScript()
186
    {
187
        return <<<'LUA'
188
 
189
if redis.call('EXISTS', KEYS[1]) == 0 then
190
    return {0, ARGV[2] + ARGV[3]}
191
end
192
 
193
if ARGV[1] >= redis.call('HGET', KEYS[1], 'start') and ARGV[1] <= redis.call('HGET', KEYS[1], 'end') then
194
    return {
195
        redis.call('HGET', KEYS[1], 'end'),
196
        ARGV[4] - redis.call('HGET', KEYS[1], 'count')
197
    }
198
end
199
 
200
return {0, ARGV[2] + ARGV[3]}
201
LUA;
202
    }
203
}