Completed
Pull Request — master (#8)
by
unknown
01:27
created

RedisLockStrategy::acquire()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 38
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 6.0702

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 38
ccs 14
cts 16
cp 0.875
rs 8.439
cc 6
eloc 18
nc 6
nop 1
crap 6.0702
1
<?php
2
3
namespace Tourstream\RedisLockStrategy;
4
5
/***************************************************************
6
 *  Copyright notice
7
 *
8
 *  (c) 2017 Alexander Miehe ([email protected])
9
 *  All rights reserved
10
 *
11
 *  You may not remove or change the name of the author above. See:
12
 *  http://www.gnu.org/licenses/gpl-faq.html#IWantCredit
13
 *
14
 *  This script is part of the Typo3 project. The Typo3 project is
15
 *  free software; you can redistribute it and/or modify
16
 *  it under the terms of the GNU General Public License as published by
17
 *  the Free Software Foundation; either version 3 of the License, or
18
 *  (at your option) any later version.
19
 *
20
 *  The GNU General Public License can be found at
21
 *  http://www.gnu.org/copyleft/gpl.html.
22
 *  A copy is found in the LICENSE and distributed with these scripts.
23
 *
24
 *
25
 *  This script is distributed in the hope that it will be useful,
26
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
27
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
28
 *  GNU General Public License for more details.
29
 *
30
 *  This copyright notice MUST APPEAR in all copies of the script!
31
 ***************************************************************/
32
33
use TYPO3\CMS\Core\Locking\Exception\LockAcquireException;
34
use TYPO3\CMS\Core\Locking\Exception\LockCreateException;
35
use TYPO3\CMS\Core\Locking\Exception\LockAcquireWouldBlockException;
36
use TYPO3\CMS\Core\Locking\LockingStrategyInterface;
37
38
/**
39
 * @author Alexander Miehe <[email protected]>
40
 */
41
class RedisLockStrategy implements LockingStrategyInterface
42
{
43
    /**
44
     * @var \Redis A key-value data store
45
     */
46
    private $redis;
47
48
    /**
49
     * @var string The locking subject, i.e. a string to discriminate the lock
50
     */
51
    private $subject;
52
53
    /**
54
     * @var boolean TRUE if lock is acquired
55
     */
56
    private $isAcquired = false;
57
58
    /**
59
     * @var int Seconds the lock remains persistent
60
     */
61
    private $ttl = 3600;
62
63
    /**
64
     * @var int Seconds to wait for a lock
65
     */
66
    private $blTo = 60;
67
68
    /**
69
     * @inheritdoc
70
     */
71 10
    public function __construct($subject)
72
    {
73 10
        $config = null;
74
75 10
        if (\array_key_exists('redis_lock', $GLOBALS['TYPO3_CONF_VARS']['SYS'])) {
76 9
            $config = $GLOBALS['TYPO3_CONF_VARS']['SYS']['redis_lock'];
77
        }
78
79 10
        if (!\is_array($config)) {
80 2
            throw new LockCreateException('no configuration for redis lock strategy found');
81
        }
82
83 8
        if (!\array_key_exists('host', $config)) {
84 1
            throw new LockCreateException('no host for redis lock strategy found');
85
        }
86 7
        $port = 6379;
87
88 7
        if (\array_key_exists('port', $config)) {
89 6
            $port = (int) $config['port'];
90
        }
91
92 7
        if (!\array_key_exists('database', $config)) {
93 1
            throw new LockCreateException('no database for redis lock strategy found');
94
        }
95
96 6
        if (\array_key_exists('ttl', $config)) {
97
            $this->ttl = (int) $config['ttl'];
98
        }
99
100 6
        $this->subject = $subject;
101 6
        $this->redis   = new \Redis();
102 6
        $this->redis->connect($config['host'], $port);
103
104 6
        if (\array_key_exists('auth', $config)) {
105
            $this->redis->auth($config['auth']);
106
        }
107
108 6
        $this->redis->select((int) $config['database']);
109
110 6
        if (!$this->redis->exists($this->subject)) {
111 6
            $this->create();
112
        }
113 6
    }
114
115
    /**
116
     * @inheritdoc
117
     */
118 10
    public static function getCapabilities()
119
    {
120 10
        return self::LOCK_CAPABILITY_EXCLUSIVE | self::LOCK_CAPABILITY_NOBLOCK;
121
    }
122
123
    /**
124
     * @inheritdoc
125
     */
126 10
    public static function getPriority()
127
    {
128 10
        return 100;
129
    }
130
131
    /**
132
     * @inheritdoc
133
     */
134 4
    public function acquire($mode = self::LOCK_CAPABILITY_EXCLUSIVE)
135
    {
136 4
        if ($this->isAcquired) {
137
            return true;
138
        }
139
140 4
        if ($mode & self::LOCK_CAPABILITY_EXCLUSIVE) {
141
142 4
            if ($mode & self::LOCK_CAPABILITY_NOBLOCK) {
143
144 1
                echo $this->redis->llen($this->subject);
145
146 1
                $r = $this->redis->lPop($this->subject);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $r. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
147
148 1
                echo $r;
149
150
                // this does not block
151 1
                $this->isAcquired = (bool) $r;
152
153 1
                if (!$this->isAcquired) {
154 1
                    throw new LockAcquireWouldBlockException('could not acquire lock');
155
                }
156
            } else {
157
158
                // this blocks iff the list is empty
159 3
                $this->isAcquired = (bool) $this->redis->blPop([$this->subject], $this->blTo);
160
161 3
                if (!$this->isAcquired) {
162 4
                    throw new LockAcquireException('could not acquire lock');
163
                }
164
            }
165
166
        } else {
167
            throw new LockAcquireException('insufficient capabilities');
168
        }
169
170 4
        return true;
171
    }
172
173
    /**
174
     * @inheritdoc
175
     */
176 2
    public function isAcquired()
177
    {
178 2
        return $this->isAcquired;
179
    }
180
181
    /**
182
     * @inheritdoc
183
     */
184 2
    public function destroy()
185
    {
186 2
        $this->redis->del($this->subject);
187 2
    }
188
189
    /**
190
     * @inheritdoc
191
     */
192 1
    public function release()
193
    {
194 1
        if (!$this->isAcquired) {
195 1
            return true;
196
        }
197
198 1
        $this->isAcquired = !$this->create();
199
200 1
        return !$this->isAcquired;
201
    }
202
203
    /**
204
     * create synchronization object, i.e.
205
     * a simple list with some single random value
206
     *
207
     * @return boolean TRUE on success
208
     * @throws LockCreateException
209
     */
210 6
    private function create()
211
    {
212 6
        if (!$this->redis->rPush($this->subject, uniqid())) {
213
            throw new LockCreateException('could not create lock entry');
214
        }
215
216 6
        if (!$this->redis->expire($this->subject, $this->ttl)) {
217
            throw new LockCreateException('could not set ttl to lock entry');
218
        }
219
220 6
        return true;
221
    }
222
223
}
224