PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.8.2
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.8.2
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 6 months ago DistributedList.php 1 year ago Lock.php 3 months ago LockBackend.php 2 years ago
Lock.php
190 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 string $namespace
28 * @param int|null $defaultTtl defaults to {@link self::DEFAULT_TTL}
29 */
30 public function __construct(\Piwik\Concurrency\LockBackend $backend, $namespace, $defaultTtl = null)
31 {
32 if (mb_strlen($namespace) > self::MAX_KEY_LEN - 32) {
33 // A longer namespace could be cut off for too long ids, so we don't support it
34 throw new \InvalidArgumentException('Lock namespace must be shorter than ' . (self::MAX_KEY_LEN - 32) . ' chars');
35 }
36 $this->backend = $backend;
37 $this->namespace = $namespace;
38 $this->lockKey = $this->namespace;
39 $this->defaultTtl = $defaultTtl ?: self::DEFAULT_TTL;
40 }
41 /**
42 * For BC only
43 *
44 * @todo remove in Matomo 6.0
45 * @deprecated use {@link reacquireLock()} instead.
46 */
47 public function reexpireLock() : bool
48 {
49 return $this->reacquireLock();
50 }
51 /**
52 * Reacquires the current lock. The TTL will be extended if 1/4 of the TTL already passed by.
53 *
54 */
55 public function reacquireLock() : bool
56 {
57 $timeBetweenReexpires = $this->defaultTtl - $this->defaultTtl / 4;
58 $now = Date::getNowTimestamp();
59 if (!empty($this->lastAcquireTime) && $now <= $this->lastAcquireTime + $timeBetweenReexpires) {
60 return \false;
61 }
62 return $this->expireLock($this->defaultTtl);
63 }
64 public function getNumberOfAcquiredLocks() : int
65 {
66 return count($this->getAllAcquiredLockKeys());
67 }
68 /**
69 * Returns all acquired lock keys for the iniatially set namespace.
70 *
71 * @return string[]
72 */
73 public function getAllAcquiredLockKeys() : array
74 {
75 return $this->backend->getKeysMatchingPattern($this->namespace . '*');
76 }
77 /**
78 * Executes and returns the result of the provided callback if a lock with given id can be acquired
79 * The method will automatically retry to acquire the lock up to 5 minutes.
80 *
81 * @param string $id
82 * @param callable $callback
83 * @return mixed
84 * @throws \Exception if lock couldn't be acquired within 5 minutes
85 */
86 public function execute($id, $callback)
87 {
88 $i = 0;
89 while (!$this->acquireLock($id)) {
90 $i++;
91 usleep(100 * 1000);
92 // 100ms
93 if ($i > 50) {
94 // give up after 5seconds (50 * 100ms)
95 throw new \Exception('Could not get the lock for ID: ' . $id);
96 }
97 }
98 try {
99 return $callback();
100 } finally {
101 $this->unlock();
102 }
103 }
104 /**
105 * Acquires a lock with the given id using the provided TTL
106 *
107 * @param string $id
108 * @param int $ttlInSeconds
109 * @return bool
110 */
111 public function acquireLock($id, $ttlInSeconds = 60)
112 {
113 $this->lockKey = $this->namespace . $id;
114 if (mb_strlen($this->lockKey) > self::MAX_KEY_LEN) {
115 // Lock key might be too long for DB column, so we hash it but leave the start of the original as well
116 // to make it more readable and to ensure the namespaceis still containe
117 $md5Len = 32;
118 $this->lockKey = mb_substr($this->lockKey, 0, self::MAX_KEY_LEN - $md5Len - 1) . md5($id);
119 }
120 $lockValue = substr(Common::generateUniqId(), 0, 12);
121 $locked = $this->backend->setIfNotExists($this->lockKey, $lockValue, $ttlInSeconds);
122 if ($locked) {
123 $this->lockValue = $lockValue;
124 $this->lastAcquireTime = Date::getNowTimestamp();
125 }
126 return !!$locked;
127 }
128 /**
129 * Return if the acquired lock is currently locked
130 *
131 */
132 public function isLocked() : bool
133 {
134 if (!$this->lockValue) {
135 return \false;
136 }
137 return $this->lockValue === $this->backend->get($this->lockKey);
138 }
139 /**
140 * Releases the acquired lock
141 *
142 */
143 public function unlock() : void
144 {
145 if ($this->lockValue) {
146 $this->backend->deleteIfKeyHasValue($this->lockKey, $this->lockValue);
147 $this->lockValue = null;
148 }
149 }
150 /**
151 * For BC only
152 *
153 * @deprecated use {@link extendLock()} instead.
154 * @todo remove in Matomo 6.0
155 */
156 public function expireLock($ttlInSeconds) : bool
157 {
158 return $this->extendLock($ttlInSeconds);
159 }
160 public function extendLock($ttlInSeconds) : bool
161 {
162 if ($ttlInSeconds > 0) {
163 if ($this->lockValue) {
164 $success = $this->backend->expireIfKeyHasValue($this->lockKey, $this->lockValue, $ttlInSeconds);
165 if (!$success) {
166 $value = $this->backend->get($this->lockKey);
167 $message = sprintf('Failed to expire key %s (%s / %s).', $this->lockKey, $this->lockValue, (string) $value);
168 if ($value === \false) {
169 Common::printDebug($message . ' It seems like the key already expired as it no longer exists.');
170 } elseif (!empty($value) && $value == $this->lockValue) {
171 Common::printDebug($message . ' We still have the lock but for some reason it did not expire.');
172 } elseif (!empty($value)) {
173 Common::printDebug($message . ' This lock has been acquired by another process/server.');
174 } else {
175 Common::printDebug($message . ' Failed to expire key.');
176 }
177 return \false;
178 }
179 $this->lastAcquireTime = Date::getNowTimestamp();
180 return \true;
181 } else {
182 Common::printDebug('Lock is not acquired, cannot update expiration.');
183 }
184 } else {
185 Common::printDebug('Provided TTL ' . $ttlInSeconds . ' is in valid in Lock::expireLock().');
186 }
187 return \false;
188 }
189 }
190