3 * @copyright Copyright (C) 2010-2022, the Friendica project
5 * @license GNU AGPL version 3 or any later version
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.
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.
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/>.
22 namespace Friendica\Core\Lock\Type;
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;
30 class CacheLock extends AbstractLock
33 * @var string The static prefix of all locks inside the cache
35 const CACHE_PREFIX = 'lock:';
43 * CacheLock constructor.
45 * @param ICanCacheInMemory $cache The CacheDriver for this type of lock
47 public function __construct(ICanCacheInMemory $cache)
49 $this->cache = $cache;
55 public function acquire(string $key, int $timeout = 120, int $ttl = Duration::FIVE_MINUTES): bool
60 $lockKey = self::getLockKey($key);
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()) {
70 // When we do want to lock something new
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)) {
77 $this->markAcquire($key);
81 if (!$got_lock && ($timeout > 0)) {
82 usleep(rand(10000, 200000));
84 } while (!$got_lock && ((time() - $start) < $timeout));
85 } catch (CachePersistenceException $exception) {
86 throw new LockPersistenceException(sprintf('Cannot acquire lock for key %s', $key), $exception);
95 public function release(string $key, bool $override = false): bool
97 $lockKey = self::getLockKey($key);
101 $return = $this->cache->delete($lockKey);
103 $return = $this->cache->compareDelete($lockKey, getmypid());
105 } catch (CachePersistenceException $exception) {
106 throw new LockPersistenceException(sprintf('Cannot release lock for key %s (override %b)', $key, $override), $exception);
108 $this->markRelease($key);
116 public function isLocked(string $key): bool
118 $lockKey = self::getLockKey($key);
120 $lock = $this->cache->get($lockKey);
121 } catch (CachePersistenceException $exception) {
122 throw new LockPersistenceException(sprintf('Cannot check lock state for key %s', $key), $exception);
124 return isset($lock) && ($lock !== false);
130 public function getName(): string
132 return $this->cache->getName();
138 public function getLocks(string $prefix = ''): array
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);
146 array_walk($locks, function (&$lock) {
147 $lock = substr($lock, strlen(self::CACHE_PREFIX));
156 public function releaseAll(bool $override = false): bool
158 $success = parent::releaseAll($override);
160 $locks = $this->getLocks();
162 foreach ($locks as $lock) {
163 if (!$this->release($lock, $override)) {
172 * @param string $key The original key
174 * @return string The cache key used for the cache
176 private static function getLockKey(string $key): string
178 return self::CACHE_PREFIX . $key;