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
use Illuminate\Support\Str;
7
use Throwable;
8
 
9
class ConcurrencyLimiter
10
{
11
    /**
12
     * The Redis factory implementation.
13
     *
14
     * @var \Illuminate\Redis\Connections\Connection
15
     */
16
    protected $redis;
17
 
18
    /**
19
     * The name of the limiter.
20
     *
21
     * @var string
22
     */
23
    protected $name;
24
 
25
    /**
26
     * The allowed number of concurrent tasks.
27
     *
28
     * @var int
29
     */
30
    protected $maxLocks;
31
 
32
    /**
33
     * The number of seconds a slot should be maintained.
34
     *
35
     * @var int
36
     */
37
    protected $releaseAfter;
38
 
39
    /**
40
     * Create a new concurrency limiter instance.
41
     *
42
     * @param  \Illuminate\Redis\Connections\Connection  $redis
43
     * @param  string  $name
44
     * @param  int  $maxLocks
45
     * @param  int  $releaseAfter
46
     * @return void
47
     */
48
    public function __construct($redis, $name, $maxLocks, $releaseAfter)
49
    {
50
        $this->name = $name;
51
        $this->redis = $redis;
52
        $this->maxLocks = $maxLocks;
53
        $this->releaseAfter = $releaseAfter;
54
    }
55
 
56
    /**
57
     * Attempt to acquire the lock for the given number of seconds.
58
     *
59
     * @param  int  $timeout
60
     * @param  callable|null  $callback
61
     * @param  int  $sleep
62
     * @return mixed
63
     *
64
     * @throws \Illuminate\Contracts\Redis\LimiterTimeoutException
65
     * @throws \Throwable
66
     */
67
    public function block($timeout, $callback = null, $sleep = 250)
68
    {
69
        $starting = time();
70
 
71
        $id = Str::random(20);
72
 
73
        while (! $slot = $this->acquire($id)) {
74
            if (time() - $timeout >= $starting) {
75
                throw new LimiterTimeoutException;
76
            }
77
 
78
            usleep($sleep * 1000);
79
        }
80
 
81
        if (is_callable($callback)) {
82
            try {
83
                return tap($callback(), function () use ($slot, $id) {
84
                    $this->release($slot, $id);
85
                });
86
            } catch (Throwable $exception) {
87
                $this->release($slot, $id);
88
 
89
                throw $exception;
90
            }
91
        }
92
 
93
        return true;
94
    }
95
 
96
    /**
97
     * Attempt to acquire the lock.
98
     *
99
     * @param  string  $id  A unique identifier for this lock
100
     * @return mixed
101
     */
102
    protected function acquire($id)
103
    {
104
        $slots = array_map(function ($i) {
105
            return $this->name.$i;
106
        }, range(1, $this->maxLocks));
107
 
108
        return $this->redis->eval(...array_merge(
109
            [$this->lockScript(), count($slots)],
110
            array_merge($slots, [$this->name, $this->releaseAfter, $id])
111
        ));
112
    }
113
 
114
    /**
115
     * Get the Lua script for acquiring a lock.
116
     *
117
     * KEYS    - The keys that represent available slots
118
     * ARGV[1] - The limiter name
119
     * ARGV[2] - The number of seconds the slot should be reserved
120
     * ARGV[3] - The unique identifier for this lock
121
     *
122
     * @return string
123
     */
124
    protected function lockScript()
125
    {
126
        return <<<'LUA'
127
for index, value in pairs(redis.call('mget', unpack(KEYS))) do
128
    if not value then
129
        redis.call('set', KEYS[index], ARGV[3], "EX", ARGV[2])
130
        return ARGV[1]..index
131
    end
132
end
133
LUA;
134
    }
135
 
136
    /**
137
     * Release the lock.
138
     *
139
     * @param  string  $key
140
     * @param  string  $id
141
     * @return void
142
     */
143
    protected function release($key, $id)
144
    {
145
        $this->redis->eval($this->releaseScript(), 1, $key, $id);
146
    }
147
 
148
    /**
149
     * Get the Lua script to atomically release a lock.
150
     *
151
     * KEYS[1] - The name of the lock
152
     * ARGV[1] - The unique identifier for this lock
153
     *
154
     * @return string
155
     */
156
    protected function releaseScript()
157
    {
158
        return <<<'LUA'
159
if redis.call('get', KEYS[1]) == ARGV[1]
160
then
161
    return redis.call('del', KEYS[1])
162
else
163
    return 0
164
end
165
LUA;
166
    }
167
}