PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 4.13.5
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v4.13.5
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 3 years ago DistributedList.php 5 years ago Lock.php 3 years ago LockBackend.php 5 years ago
Lock.php
153 lines
1 <?php
2 /**
3 * Matomo - free/libre analytics platform
4 *
5 * @link https://matomo.org
6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
7 *
8 */
9 namespace Piwik\Concurrency;
10
11 use Piwik\Common;
12 use Piwik\Date;
13
14 class Lock
15 {
16 const MAX_KEY_LEN = 70;
17 const DEFAULT_TTL = 60;
18
19 /**
20 * @var LockBackend
21 */
22 private $backend;
23
24 private $lockKeyStart;
25
26 private $lockKey = null;
27 private $lockValue = null;
28 private $defaultTtl = null;
29 private $lastExpireTime = null;
30
31 public function __construct(LockBackend $backend, $lockKeyStart, $defaultTtl = null)
32 {
33 $this->backend = $backend;
34 $this->lockKeyStart = $lockKeyStart;
35 $this->lockKey = $this->lockKeyStart;
36 $this->defaultTtl = $defaultTtl ?: self::DEFAULT_TTL;
37 }
38
39 public function reexpireLock()
40 {
41 $timeBetweenReexpires = $this->defaultTtl - ($this->defaultTtl / 4);
42
43 $now = Date::getNowTimestamp();
44 if (!empty($this->lastExpireTime) &&
45 $now <= $this->lastExpireTime + $timeBetweenReexpires
46 ) {
47 return false;
48 }
49
50 return $this->expireLock($this->defaultTtl);
51 }
52
53 public function getNumberOfAcquiredLocks()
54 {
55 return count($this->getAllAcquiredLockKeys());
56 }
57
58 public function getAllAcquiredLockKeys()
59 {
60 return $this->backend->getKeysMatchingPattern($this->lockKeyStart . '*');
61 }
62
63 public function execute($id, $callback)
64 {
65 $i = 0;
66 while (!$this->acquireLock($id)) {
67 $i++;
68 usleep( 100 * 1000 ); // 100ms
69 if ($i > 50) { // give up after 5seconds (50 * 100ms)
70 throw new \Exception('Could not get the lock for ID: ' . $id);
71 }
72 };
73 try {
74 return $callback();
75 } finally {
76 $this->unlock();
77 }
78 }
79
80 public function acquireLock($id, $ttlInSeconds = 60)
81 {
82 $this->lockKey = $this->lockKeyStart . $id;
83
84 if (mb_strlen($this->lockKey) > self::MAX_KEY_LEN) {
85 // Lock key might be too long for DB column, so we hash it but leave the start of the original as well
86 // to make it more readable
87 $md5Len = 32;
88 $this->lockKey = mb_substr($id, 0, self::MAX_KEY_LEN - $md5Len - 1) . md5($id);
89 }
90
91 $lockValue = substr(Common::generateUniqId(), 0, 12);
92 $locked = $this->backend->setIfNotExists($this->lockKey, $lockValue, $ttlInSeconds);
93 if ($locked) {
94 $this->lockValue = $lockValue;
95 $this->lastExpireTime = Date::getNowTimestamp();
96 }
97
98 return $locked;
99 }
100
101 public function isLocked()
102 {
103 if (!$this->lockValue) {
104 return false;
105 }
106
107 return $this->lockValue === $this->backend->get($this->lockKey);
108 }
109
110 public function unlock()
111 {
112 if ($this->lockValue) {
113 $this->backend->deleteIfKeyHasValue($this->lockKey, $this->lockValue);
114 $this->lockValue = null;
115 }
116 }
117
118 public function expireLock($ttlInSeconds)
119 {
120 if ($ttlInSeconds > 0) {
121 if ($this->lockValue) {
122 $success = $this->backend->expireIfKeyHasValue($this->lockKey, $this->lockValue, $ttlInSeconds);
123 if (!$success) {
124 $value = $this->backend->get($this->lockKey);
125 $message = sprintf('Failed to expire key %s (%s / %s).', $this->lockKey, $this->lockValue, (string)$value);
126
127 if ($value === false) {
128 Common::printDebug($message . ' It seems like the key already expired as it no longer exists.');
129 } elseif (!empty($value) && $value == $this->lockValue) {
130 Common::printDebug($message . ' We still have the lock but for some reason it did not expire.');
131 } elseif (!empty($value)) {
132 Common::printDebug($message . ' This lock has been acquired by another process/server.');
133 } else {
134 Common::printDebug($message . ' Failed to expire key.');
135 }
136
137 return false;
138 }
139
140 $this->lastExpireTime = Date::getNowTimestamp();
141
142 return true;
143 } else {
144 Common::printDebug('Lock is not acquired, cannot update expiration.');
145 }
146 } else {
147 Common::printDebug('Provided TTL ' . $ttlInSeconds . ' is in valid in Lock::expireLock().');
148 }
149
150 return false;
151 }
152 }
153