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

RedisLockStrategy::__construct()   D

Complexity

Conditions 9
Paths 40

Size

Total Lines 43
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 9.0468

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 43
ccs 22
cts 24
cp 0.9167
rs 4.909
cc 9
eloc 23
nc 40
nop 1
crap 9.0468
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
                // this does not block
145 1
                $this->isAcquired = (bool) $this->redis->lPop($this->subject);
146
147 1
                if (!$this->isAcquired) {
148 1
                    throw new LockAcquireWouldBlockException('could not acquire lock');
149
                }
150
            } else {
151
152
                // this blocks iff the list is empty
153 3
                $this->isAcquired = (bool) $this->redis->blPop([$this->subject], $this->blTo);
154
155 3
                if (!$this->isAcquired) {
156 4
                    throw new LockAcquireException('could not acquire lock');
157
                }
158
            }
159
160
        } else {
161
            throw new LockAcquireException('insufficient capabilities');
162
        }
163
164 4
        return true;
165
    }
166
167
    /**
168
     * @inheritdoc
169
     */
170 2
    public function isAcquired()
171
    {
172 2
        return $this->isAcquired;
173
    }
174
175
    /**
176
     * @inheritdoc
177
     */
178 2
    public function destroy()
179
    {
180 2
        $this->redis->del($this->subject);
181 2
    }
182
183
    /**
184
     * @inheritdoc
185
     */
186 1
    public function release()
187
    {
188 1
        if (!$this->isAcquired) {
189 1
            return true;
190
        }
191
192 1
        $this->isAcquired = !$this->create();
193
194 1
        return !$this->isAcquired;
195
    }
196
197
    /**
198
     * create synchronization object, i.e.
199
     * a simple list with some single random value
200
     *
201
     * @return boolean TRUE on success
202
     * @throws LockCreateException
203
     */
204 6
    private function create()
205
    {
206 6
        if (!$this->redis->rPush($this->subject, uniqid())) {
207
            throw new LockCreateException('could not create lock entry');
208
        }
209
210 6
        if (!$this->redis->expire($this->subject, $this->ttl)) {
211
            throw new LockCreateException('could not set ttl to lock entry');
212
        }
213
214 6
        return true;
215
    }
216
217
}
218