GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 650429...b2d98c )
by Rémi
02:52
created

MultipleInstanceLocker::checkQuorum()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 9.4285
cc 2
eloc 3
nc 2
nop 1
crap 2
1
<?php
2
3
namespace RemiSan\Lock\Locker;
4
5
use RemiSan\Lock\Connection;
6
use RemiSan\Lock\Exceptions\LockingException;
7
use RemiSan\Lock\Exceptions\UnlockingException;
8
use RemiSan\Lock\Lock;
9
use RemiSan\Lock\Locker;
10
use RemiSan\Lock\Quorum;
11
use RemiSan\Lock\TokenGenerator;
12
use Symfony\Component\Stopwatch\Stopwatch;
13
14
final class MultipleInstanceLocker implements Locker
15
{
16
    /** @var Connection[] */
17
    private $instances = [];
18
19
    /** @var TokenGenerator */
20
    private $tokenGenerator;
21
22
    /** @var Quorum */
23
    private $quorum;
24
25
    /** @var Stopwatch */
26
    private $stopwatch;
27
28
    /**
29
     * RedLock constructor.
30
     *
31
     * @param Connection\[]  $instances      Array of persistence system connections
32
     * @param TokenGenerator $tokenGenerator The token generator
33
     * @param Quorum         $quorum         The quorum implementation to use
34
     * @param Stopwatch      $stopwatch      A way to measure time passed
35
     */
36 36
    public function __construct(
37
        array $instances,
38
        TokenGenerator $tokenGenerator,
39
        Quorum $quorum,
40
        Stopwatch $stopwatch
41
    ) {
42 36
        $this->setInstances($instances);
43 36
        $this->setQuorum($quorum);
44
45 36
        $this->tokenGenerator = $tokenGenerator;
46 36
        $this->stopwatch = $stopwatch;
47 36
    }
48
49
    /**
50
     * {@inheritdoc}
51
     */
52 12
    public function lock($resource, $ttl = null, $retryDelay = 0, $retryCount = 0)
53
    {
54 12
        $lock = new Lock((string) $resource, $this->tokenGenerator->generateToken());
55
56 12
        $tried = 0;
57 12
        while (true) {
58
            try {
59 12
                return $this->monitoredLockingOfAllInstances($lock, $ttl);
60 6
            } catch (LockingException $e) {
61 6
                $this->resetLock($lock);
62
            }
63
64 6
            if ($tried++ === $retryCount) {
65 6
                break;
66
            }
67
68 6
            $this->waitBeforeRetrying($retryDelay);
69 4
        }
70
71 6
        throw new LockingException('Failed locking the resource.');
72
    }
73
74
    /**
75
     * {@inheritdoc}
76
     */
77 9
    public function isLocked($resource)
78
    {
79 9
        foreach ($this->instances as $instance) {
80 9
            if ($instance->exists((string) $resource)) {
81 7
                return true;
82
            }
83 4
        }
84
85 3
        return false;
86
    }
87
88
    /**
89
     * {@inheritdoc}
90
     */
91 12
    public function unlock(Lock $lock)
92
    {
93 12
        foreach ($this->instances as $instance) {
94 12
            if (!$instance->delete($lock)) {
95 9
                if ($instance->exists($lock->getResource())) {
96
                    // Only throw an exception if the lock is still present
97 8
                    throw new UnlockingException('Failed releasing the lock.');
98
                }
99 2
            }
100 6
        }
101 6
    }
102
103
    /**
104
     * Try locking all connected instances.
105
     *
106
     * Measure the time to do it and reject if not enough connected instance have successfully
107
     * locked the resource or if time to lock all instances have exceeded the ttl.
108
     *
109
     * @param Lock $lock The lock instance
110
     * @param int  $ttl  Time to live in milliseconds
111
     *
112
     * @throws LockingException
113
     *
114
     * @return Lock
115
     */
116 12
    private function monitoredLockingOfAllInstances(Lock $lock, $ttl)
117
    {
118 12
        $timeMeasure = $this->stopwatch->start($lock->getToken());
119 12
        $instancesLocked = $this->lockInstances($lock, $ttl);
120 12
        $timeMeasure->stop();
121
122 12
        $this->checkQuorum($instancesLocked);
123
124 9
        if ($ttl) {
125 6
            $this->checkTtl($timeMeasure->getDuration(), $ttl);
126 3
            $lock->setValidityEndTime($timeMeasure->getOrigin() + $ttl);
127 2
        }
128
129 6
        return $lock;
130
    }
131
132
    /**
133
     * Lock resource in connected instances and count how many instance did it with success.
134
     *
135
     * @param Lock $lock The lock instance
136
     * @param int  $ttl  Time to live in milliseconds
137
     *
138
     * @return int The number of instances locked
139
     */
140 12
    private function lockInstances(Lock $lock, $ttl)
141
    {
142 12
        $instancesLocked = 0;
143
144 12
        foreach ($this->instances as $instance) {
145 12
            if ($instance->set($lock, $ttl)) {
146 12
                ++$instancesLocked;
147 8
            }
148 8
        }
149
150 12
        return $instancesLocked;
151
    }
152
153
    /**
154
     * Unlock the resource on all Redis instances.
155
     *
156
     * @param Lock $lock The lock to release
157
     */
158 6
    private function resetLock($lock)
159
    {
160 6
        foreach ($this->instances as $instance) {
161 6
            $instance->delete($lock);
162 4
        }
163 6
    }
164
165
    /**
166
     * Init all Redis instances passed to the constructor.
167
     *
168
     * If no Redis instance is given, it will return a InvalidArgumentException.
169
     * If one or more Redis instance is not connected, it will return a InvalidArgumentException.
170
     *
171
     * @param \Redis[] $instances The connected Redis instances
172
     *
173
     * @throws \InvalidArgumentException
174
     */
175 36
    private function setInstances(array $instances)
176
    {
177 36
        if (count($instances) === 0) {
178 3
            throw new \InvalidArgumentException('You must provide at least one Redis instance.');
179
        }
180
181 36
        $this->instances = $instances;
0 ignored issues
show
Documentation Bug introduced by
It seems like $instances of type array<integer,object<Redis>> is incompatible with the declared type array<integer,object<RemiSan\Lock\Connection>> of property $instances.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
182 36
    }
183
184
    /**
185
     * Set the quorum based on the number of instances passed to the constructor.
186
     *
187
     * @param Quorum $quorum The quorum implementation to use
188
     */
189 36
    private function setQuorum(Quorum $quorum)
190
    {
191 36
        $this->quorum = $quorum;
192 36
        $this->quorum->init(count($this->instances));
193 36
    }
194
195
    /**
196
     * Check if the number of instances that have been locked reach the quorum.
197
     *
198
     * @param int $instancesLocked The number of instances that have been locked
199
     *
200
     * @throws LockingException
201
     */
202 12
    private function checkQuorum($instancesLocked)
203
    {
204 12
        if (!$this->quorum->isMet($instancesLocked)) {
205 3
            throw new LockingException('Quorum has not been met.');
206
        }
207 9
    }
208
209
    /**
210
     * Make the script wait before retrying to lock.
211
     *
212
     * @param int $retryDelay The retry delay in milliseconds
213
     */
214 6
    private function waitBeforeRetrying($retryDelay)
215
    {
216 6
        usleep($retryDelay * 1000);
217 6
    }
218
219
    /**
220
     * Checks if the elapsed time is inferior to the ttl.
221
     *
222
     * To the elapsed time is added a drift time to have a margin of error.
223
     * If this adjusted time is greater than the ttl, it will throw a LockingException.
224
     *
225
     * @param int $elapsedTime The time elapsed in milliseconds
226
     * @param int $ttl         The time to live in milliseconds
227
     *
228
     * @throws LockingException
229
     */
230 6
    private function checkTtl($elapsedTime, $ttl)
231
    {
232 6
        $adjustedElapsedTime = ($elapsedTime + $this->getDrift($ttl));
233
234 6
        if ($adjustedElapsedTime >= $ttl) {
235 3
            throw new LockingException('Time to lock the resource has exceeded the ttl.');
236
        }
237 3
    }
238
239
    /**
240
     * Get the drift time based on ttl in ms.
241
     *
242
     * @param int $ttl The time to live in milliseconds
243
     *
244
     * @return float
245
     */
246 6
    private function getDrift($ttl)
247
    {
248 6
        return array_values($this->instances)[0]->getDrift($ttl);
249
    }
250
}
251