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