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