PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.2.0
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.2.0
5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / core / Concurrency / Lock.php
matomo / app / core / Concurrency Last commit date
LockBackend 1 year ago DistributedList.php 1 year ago Lock.php 1 year ago LockBackend.php 2 years ago
Lock.php
196 lines
1 <?php
2
3 /**
4 * Matomo - free/libre analytics platform
5 *
6 * @link https://matomo.org
7 * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
8 */
9 namespace Piwik\Concurrency;
10
11 use Piwik\Common;
12 use Piwik\Date;
13 class Lock
14 {
15 public const MAX_KEY_LEN = 70;
16 public const DEFAULT_TTL = 60;
17 /**
18 * @var LockBackend
19 */
20 private $backend;
21 private $namespace;
22 private $lockKey = null;
23 private $lockValue = null;
24 private $defaultTtl = null;
25 private $lastAcquireTime = null;
26 /**
27 * @param LockBackend $backend
28 * @param string $namespace
29 * @param int|null $defaultTtl defaults to {@link self::DEFAULT_TTL}
30 */
31 public function __construct(\Piwik\Concurrency\LockBackend $backend, $namespace, $defaultTtl = null)
32 {
33 if (mb_strlen($namespace) > self::MAX_KEY_LEN - 32) {
34 // A longer namespace could be cut off for too long ids, so we don't support it
35 throw new \InvalidArgumentException('Lock namespace must be shorter than ' . (self::MAX_KEY_LEN - 32) . ' chars');
36 }
37 $this->backend = $backend;
38 $this->namespace = $namespace;
39 $this->lockKey = $this->namespace;
40 $this->defaultTtl = $defaultTtl ?: self::DEFAULT_TTL;
41 }
42 /**
43 * For BC only
44 *
45 * @todo remove in Matomo 6.0
46 * @deprecated use {@link reacquireLock()} instead.
47 * @return bool
48 */
49 public function reexpireLock() : bool
50 {
51 return $this->reacquireLock();
52 }
53 /**
54 * Reacquires the current lock. The TTL will be extended if 1/4 of the TTL already passed by.
55 *
56 * @return bool
57 */
58 public function reacquireLock() : bool
59 {
60 $timeBetweenReexpires = $this->defaultTtl - $this->defaultTtl / 4;
61 $now = Date::getNowTimestamp();
62 if (!empty($this->lastAcquireTime) && $now <= $this->lastAcquireTime + $timeBetweenReexpires) {
63 return \false;
64 }
65 return $this->expireLock($this->defaultTtl);
66 }
67 public function getNumberOfAcquiredLocks() : int
68 {
69 return count($this->getAllAcquiredLockKeys());
70 }
71 /**
72 * Returns all acquired lock keys for the iniatially set namespace.
73 *
74 * @return string[]
75 */
76 public function getAllAcquiredLockKeys() : array
77 {
78 return $this->backend->getKeysMatchingPattern($this->namespace . '*');
79 }
80 /**
81 * Executes and returns the result of the provided callback if a lock with given id can be acquired
82 * The method will automatically retry to acquire the lock up to 5 minutes.
83 *
84 * @param string $id
85 * @param callable $callback
86 * @return mixed
87 * @throws \Exception if lock couldn't be acquired within 5 minutes
88 */
89 public function execute($id, $callback)
90 {
91 $i = 0;
92 while (!$this->acquireLock($id)) {
93 $i++;
94 usleep(100 * 1000);
95 // 100ms
96 if ($i > 50) {
97 // give up after 5seconds (50 * 100ms)
98 throw new \Exception('Could not get the lock for ID: ' . $id);
99 }
100 }
101 try {
102 return $callback();
103 } finally {
104 $this->unlock();
105 }
106 }
107 /**
108 * Acquires a lock with the given id using the provided TTL
109 *
110 * @param string $id
111 * @param int $ttlInSeconds
112 * @return bool
113 */
114 public function acquireLock($id, $ttlInSeconds = 60)
115 {
116 $this->lockKey = $this->namespace . $id;
117 if (mb_strlen($this->lockKey) > self::MAX_KEY_LEN) {
118 // Lock key might be too long for DB column, so we hash it but leave the start of the original as well
119 // to make it more readable and to ensure the namespaceis still containe
120 $md5Len = 32;
121 $this->lockKey = mb_substr($this->lockKey, 0, self::MAX_KEY_LEN - $md5Len - 1) . md5($id);
122 }
123 $lockValue = substr(Common::generateUniqId(), 0, 12);
124 $locked = $this->backend->setIfNotExists($this->lockKey, $lockValue, $ttlInSeconds);
125 if ($locked) {
126 $this->lockValue = $lockValue;
127 $this->lastAcquireTime = Date::getNowTimestamp();
128 }
129 return !!$locked;
130 }
131 /**
132 * Return if the acquired lock is currently locked
133 *
134 * @return bool
135 */
136 public function isLocked() : bool
137 {
138 if (!$this->lockValue) {
139 return \false;
140 }
141 return $this->lockValue === $this->backend->get($this->lockKey);
142 }
143 /**
144 * Releases the acquired lock
145 *
146 * @return void
147 */
148 public function unlock() : void
149 {
150 if ($this->lockValue) {
151 $this->backend->deleteIfKeyHasValue($this->lockKey, $this->lockValue);
152 $this->lockValue = null;
153 }
154 }
155 /**
156 * For BC only
157 *
158 * @deprecated use {@link extendLock()} instead.
159 * @todo remove in Matomo 6.0
160 * @return bool
161 */
162 public function expireLock($ttlInSeconds) : bool
163 {
164 return $this->extendLock($ttlInSeconds);
165 }
166 public function extendLock($ttlInSeconds) : bool
167 {
168 if ($ttlInSeconds > 0) {
169 if ($this->lockValue) {
170 $success = $this->backend->expireIfKeyHasValue($this->lockKey, $this->lockValue, $ttlInSeconds);
171 if (!$success) {
172 $value = $this->backend->get($this->lockKey);
173 $message = sprintf('Failed to expire key %s (%s / %s).', $this->lockKey, $this->lockValue, (string) $value);
174 if ($value === \false) {
175 Common::printDebug($message . ' It seems like the key already expired as it no longer exists.');
176 } elseif (!empty($value) && $value == $this->lockValue) {
177 Common::printDebug($message . ' We still have the lock but for some reason it did not expire.');
178 } elseif (!empty($value)) {
179 Common::printDebug($message . ' This lock has been acquired by another process/server.');
180 } else {
181 Common::printDebug($message . ' Failed to expire key.');
182 }
183 return \false;
184 }
185 $this->lastAcquireTime = Date::getNowTimestamp();
186 return \true;
187 } else {
188 Common::printDebug('Lock is not acquired, cannot update expiration.');
189 }
190 } else {
191 Common::printDebug('Provided TTL ' . $ttlInSeconds . ' is in valid in Lock::expireLock().');
192 }
193 return \false;
194 }
195 }
196