]> git.mxchange.org Git - friendica.git/blob - src/Core/Lock/Type/CacheLock.php
Merge pull request #11520 from annando/display-polls
[friendica.git] / src / Core / Lock / Type / CacheLock.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2010-2022, the Friendica project
4  *
5  * @license GNU AGPL version 3 or any later version
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Affero General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Affero General Public License for more details.
16  *
17  * You should have received a copy of the GNU Affero General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  *
20  */
21
22 namespace Friendica\Core\Lock\Type;
23
24 use Friendica\Core\Cache\Capability\ICanCache;
25 use Friendica\Core\Cache\Capability\ICanCacheInMemory;
26 use Friendica\Core\Cache\Enum\Duration;
27 use Friendica\Core\Cache\Exception\CachePersistenceException;
28 use Friendica\Core\Lock\Exception\LockPersistenceException;
29
30 class CacheLock extends AbstractLock
31 {
32         /**
33          * @var string The static prefix of all locks inside the cache
34          */
35         const CACHE_PREFIX = 'lock:';
36
37         /**
38          * @var ICanCache;
39          */
40         private $cache;
41
42         /**
43          * CacheLock constructor.
44          *
45          * @param ICanCacheInMemory $cache The CacheDriver for this type of lock
46          */
47         public function __construct(ICanCacheInMemory $cache)
48         {
49                 $this->cache = $cache;
50         }
51
52         /**
53          * (@inheritdoc)
54          */
55         public function acquire(string $key, int $timeout = 120, int $ttl = Duration::FIVE_MINUTES): bool
56         {
57                 $got_lock = false;
58                 $start    = time();
59
60                 $lockKey = self::getLockKey($key);
61
62                 try {
63                         do {
64                                 $lock = $this->cache->get($lockKey);
65                                 // When we do want to lock something that was already locked by us.
66                                 if ((int)$lock == getmypid()) {
67                                         $got_lock = true;
68                                 }
69
70                                 // When we do want to lock something new
71                                 if (is_null($lock)) {
72                                         // At first initialize it with "0"
73                                         $this->cache->add($lockKey, 0);
74                                         // Now the value has to be "0" because otherwise the key was used by another process meanwhile
75                                         if ($this->cache->compareSet($lockKey, 0, getmypid(), $ttl)) {
76                                                 $got_lock = true;
77                                                 $this->markAcquire($key);
78                                         }
79                                 }
80
81                                 if (!$got_lock && ($timeout > 0)) {
82                                         usleep(rand(10000, 200000));
83                                 }
84                         } while (!$got_lock && ((time() - $start) < $timeout));
85                 } catch (CachePersistenceException $exception) {
86                         throw new LockPersistenceException(sprintf('Cannot acquire lock for key %s', $key), $exception);
87                 }
88
89                 return $got_lock;
90         }
91
92         /**
93          * (@inheritdoc)
94          */
95         public function release(string $key, bool $override = false): bool
96         {
97                 $lockKey = self::getLockKey($key);
98
99                 try {
100                         if ($override) {
101                                 $return = $this->cache->delete($lockKey);
102                         } else {
103                                 $return = $this->cache->compareDelete($lockKey, getmypid());
104                         }
105                 } catch (CachePersistenceException $exception) {
106                         throw new LockPersistenceException(sprintf('Cannot release lock for key %s (override %b)', $key, $override), $exception);
107                 }
108                 $this->markRelease($key);
109
110                 return $return;
111         }
112
113         /**
114          * (@inheritdoc)
115          */
116         public function isLocked(string $key): bool
117         {
118                 $lockKey = self::getLockKey($key);
119                 try {
120                         $lock = $this->cache->get($lockKey);
121                 } catch (CachePersistenceException $exception) {
122                         throw new LockPersistenceException(sprintf('Cannot check lock state for key %s', $key), $exception);
123                 }
124                 return isset($lock) && ($lock !== false);
125         }
126
127         /**
128          * {@inheritDoc}
129          */
130         public function getName(): string
131         {
132                 return $this->cache->getName();
133         }
134
135         /**
136          * {@inheritDoc}
137          */
138         public function getLocks(string $prefix = ''): array
139         {
140                 try {
141                         $locks = $this->cache->getAllKeys(self::CACHE_PREFIX . $prefix);
142                 } catch (CachePersistenceException $exception) {
143                         throw new LockPersistenceException(sprintf('Cannot get locks with prefix %s', $prefix), $exception);
144                 }
145
146                 array_walk($locks, function (&$lock) {
147                         $lock = substr($lock, strlen(self::CACHE_PREFIX));
148                 });
149
150                 return $locks;
151         }
152
153         /**
154          * {@inheritDoc}
155          */
156         public function releaseAll(bool $override = false): bool
157         {
158                 $success = parent::releaseAll($override);
159
160                 $locks = $this->getLocks();
161
162                 foreach ($locks as $lock) {
163                         if (!$this->release($lock, $override)) {
164                                 $success = false;
165                         }
166                 }
167
168                 return $success;
169         }
170
171         /**
172          * @param string $key The original key
173          *
174          * @return string        The cache key used for the cache
175          */
176         private static function getLockKey(string $key): string
177         {
178                 return self::CACHE_PREFIX . $key;
179         }
180 }