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.

lockAndCheckQuorumAndTtlOnAllStores()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 15
ccs 10
cts 10
cp 1
rs 9.4285
cc 2
eloc 9
nc 2
nop 2
crap 2
1
<?php
2
3
namespace RemiSan\Lock\Locker;
4
5
use Psr\Log\LoggerAwareInterface;
6
use Psr\Log\LoggerAwareTrait;
7
use Psr\Log\NullLogger;
8
use RemiSan\Lock\Exceptions\LockingException;
9
use RemiSan\Lock\Exceptions\UnlockingException;
10
use RemiSan\Lock\Lock;
11
use RemiSan\Lock\Locker;
12
use RemiSan\Lock\LockStore;
13
use RemiSan\Lock\Quorum;
14
use RemiSan\Lock\TokenGenerator;
15
use Symfony\Component\Stopwatch\Stopwatch;
16
17
final class MultipleStoreLocker implements Locker, LoggerAwareInterface
18
{
19
    use LoggerAwareTrait;
20
21
    /** @var LockStore[] */
22
    private $stores = [];
23
24
    /** @var TokenGenerator */
25
    private $tokenGenerator;
26
27
    /** @var Quorum */
28
    private $quorum;
29
30
    /** @var Stopwatch */
31
    private $stopwatch;
32
33
    /**
34
     * MultipleStoreLocker constructor.
35
     *
36
     * @param LockStore[]    $stores         Array of persistence stores for the locks
37
     * @param TokenGenerator $tokenGenerator The token generator
38
     * @param Quorum         $quorum         The quorum implementation to use
39
     * @param Stopwatch      $stopwatch      A way to measure time passed
40
     */
41 36
    public function __construct(
42
        array $stores,
43
        TokenGenerator $tokenGenerator,
44
        Quorum $quorum,
45
        Stopwatch $stopwatch
46
    ) {
47 36
        $this->setStores($stores);
48 36
        $this->setQuorum($quorum);
49
50 36
        $this->tokenGenerator = $tokenGenerator;
51 36
        $this->stopwatch = $stopwatch;
52
53 36
        $this->logger = new NullLogger();
54 36
    }
55
56
    /**
57
     * {@inheritdoc}
58
     */
59 12
    public function lock($resource, $ttl = null, $retryDelay = 0, $retryCount = 0)
60
    {
61 12
        $lock = new Lock((string) $resource, $this->tokenGenerator->generateToken());
62
63 12
        $tried = 0;
64 12
        while (true) {
65
            try {
66 12
                return $this->lockAndCheckQuorumAndTtlOnAllStores($lock, $ttl);
67 6
            } catch (LockingException $e) {
68 6
                $this->logger->notice($e->getMessage(), ['resource' => $lock->getResource()]);
69 6
                $this->resetLock($lock);
70
            }
71
72 6
            if ($tried++ === $retryCount) {
73 6
                break;
74
            }
75
76 6
            $this->waitBeforeRetrying($retryDelay);
77 4
        }
78
79 6
        throw new LockingException('Failed locking the resource.');
80
    }
81
82
    /**
83
     * {@inheritdoc}
84
     */
85 9
    public function isLocked($resource)
86
    {
87 9
        foreach ($this->stores as $store) {
88 9
            if ($store->exists((string) $resource)) {
89 7
                return true;
90
            }
91 4
        }
92
93 3
        return false;
94
    }
95
96
    /**
97
     * {@inheritdoc}
98
     */
99 12
    public function unlock(Lock $lock)
100
    {
101 12
        foreach ($this->stores as $store) {
102 12
            if (!$store->delete($lock)) {
103 9
                if ($store->exists($lock->getResource())) {
104
                    // Only throw an exception if the lock is still present
105 8
                    throw new UnlockingException('Failed releasing the lock.');
106
                }
107 2
            }
108 6
        }
109 6
    }
110
111
    /**
112
     * Try locking resource on all stores.
113
     *
114
     * Measure the time to do it and reject if not enough stores have successfully
115
     * locked the resource or if time to lock on all stores have exceeded the ttl.
116
     *
117
     * @param Lock $lock The lock instance
118
     * @param int  $ttl  Time to live in milliseconds
119
     *
120
     * @throws LockingException
121
     *
122
     * @return Lock
123
     */
124 12
    private function lockAndCheckQuorumAndTtlOnAllStores(Lock $lock, $ttl)
125
    {
126 12
        $timeMeasure = $this->stopwatch->start($lock->getToken());
127 12
        $storesLocked = $this->lockOnAllStores($lock, $ttl);
128 12
        $timeMeasure->stop();
129
130 12
        $this->checkQuorum($storesLocked);
131
132 9
        if ($ttl) {
133 6
            $this->checkTtl($timeMeasure->getDuration(), $ttl);
134 3
            $lock->setValidityEndTime($timeMeasure->getOrigin() + $ttl);
135 2
        }
136
137 6
        return $lock;
138
    }
139
140
    /**
141
     * Lock resource on all stores and count how many stores did it with success.
142
     *
143
     * @param Lock $lock The lock instance
144
     * @param int  $ttl  Time to live in milliseconds
145
     *
146
     * @return int The number of stores on which the resource has been locked
147
     */
148 12
    private function lockOnAllStores(Lock $lock, $ttl)
149
    {
150 12
        $storesLocked = 0;
151
152 12
        foreach ($this->stores as $store) {
153 12
            if ($store->set($lock, $ttl)) {
154 12
                ++$storesLocked;
155 8
            }
156 8
        }
157
158 12
        return $storesLocked;
159
    }
160
161
    /**
162
     * Unlock the resource on all stores.
163
     *
164
     * @param Lock $lock The lock to release
165
     */
166 6
    private function resetLock($lock)
167
    {
168 6
        foreach ($this->stores as $store) {
169 6
            $store->delete($lock);
170 4
        }
171 6
    }
172
173
    /**
174
     * Init all stores passed to the constructor.
175
     *
176
     * If no store is given, it will return an InvalidArgumentException.
177
     *
178
     * @param LockStore[] $stores The lock stores
179
     *
180
     * @throws \InvalidArgumentException
181
     */
182 36
    private function setStores(array $stores)
183
    {
184 36
        if (count($stores) === 0) {
185 3
            throw new \InvalidArgumentException('You must provide at least one LockStore.');
186
        }
187
188 36
        $this->stores = $stores;
189 36
    }
190
191
    /**
192
     * Set the quorum based on the number of stores passed to the constructor.
193
     *
194
     * @param Quorum $quorum The quorum implementation to use
195
     */
196 36
    private function setQuorum(Quorum $quorum)
197
    {
198 36
        $this->quorum = $quorum;
199 36
        $this->quorum->init(count($this->stores));
200 36
    }
201
202
    /**
203
     * Check if the number of stores where the resource has been locked meet the quorum.
204
     *
205
     * @param int $storesLocked The number of stores on which the resource has been locked
206
     *
207
     * @throws LockingException
208
     */
209 12
    private function checkQuorum($storesLocked)
210
    {
211 12
        if (!$this->quorum->isMet($storesLocked)) {
212 3
            throw new LockingException('Quorum has not been met.');
213
        }
214 9
    }
215
216
    /**
217
     * Make the script wait before retrying to lock.
218
     *
219
     * @param int $retryDelay The retry delay in milliseconds
220
     */
221 6
    private function waitBeforeRetrying($retryDelay)
222
    {
223 6
        usleep($retryDelay * 1000);
224 6
    }
225
226
    /**
227
     * Checks if the elapsed time is inferior to the ttl.
228
     *
229
     * To the elapsed time is added a drift time to have a margin of error.
230
     * If this adjusted time is greater than the ttl, it will throw a LockingException.
231
     *
232
     * @param int $elapsedTime The time elapsed in milliseconds
233
     * @param int $ttl         The time to live in milliseconds
234
     *
235
     * @throws LockingException
236
     */
237 6
    private function checkTtl($elapsedTime, $ttl)
238
    {
239 6
        $adjustedElapsedTime = ($elapsedTime + $this->getDrift($ttl));
240
241 6
        if ($adjustedElapsedTime >= $ttl) {
242 3
            throw new LockingException('Time to lock the resource has exceeded the ttl.');
243
        }
244 3
    }
245
246
    /**
247
     * Get the drift time based on ttl in ms.
248
     *
249
     * Return the max drift time of all stores
250
     *
251
     * @param int $ttl The time to live in milliseconds
252
     *
253
     * @return float
254
     */
255
    private function getDrift($ttl)
256
    {
257 6
        return max(array_map(function (LockStore $store) use ($ttl) {
258 6
            return $store->getDrift($ttl);
259 6
        }, $this->stores));
260
    }
261
}
262