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 ( 056009...46877f )
by Rémi
04:28
created

lockAndCheckQuorumAndTtlOnAllStores()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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