Update copyright
[friendica.git/.git] / src / Core / Lock / CacheLock.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2010-2021, 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;
23
24 use Friendica\Core\BaseLock;
25 use Friendica\Core\Cache\Duration;
26 use Friendica\Core\Cache\IMemoryCache;
27
28 class CacheLock extends BaseLock
29 {
30         /**
31          * @var string The static prefix of all locks inside the cache
32          */
33         const CACHE_PREFIX = 'lock:';
34
35         /**
36          * @var \Friendica\Core\Cache\ICache;
37          */
38         private $cache;
39
40         /**
41          * CacheLock constructor.
42          *
43          * @param IMemoryCache $cache The CacheDriver for this type of lock
44          */
45         public function __construct(IMemoryCache $cache)
46         {
47                 $this->cache = $cache;
48         }
49
50         /**
51          * (@inheritdoc)
52          */
53         public function acquire($key, $timeout = 120, $ttl = Duration::FIVE_MINUTES)
54         {
55                 $got_lock = false;
56                 $start    = time();
57
58                 $cachekey = self::getLockKey($key);
59
60                 do {
61                         $lock = $this->cache->get($cachekey);
62                         // When we do want to lock something that was already locked by us.
63                         if ((int)$lock == getmypid()) {
64                                 $got_lock = true;
65                         }
66
67                         // When we do want to lock something new
68                         if (is_null($lock)) {
69                                 // At first initialize it with "0"
70                                 $this->cache->add($cachekey, 0);
71                                 // Now the value has to be "0" because otherwise the key was used by another process meanwhile
72                                 if ($this->cache->compareSet($cachekey, 0, getmypid(), $ttl)) {
73                                         $got_lock = true;
74                                         $this->markAcquire($key);
75                                 }
76                         }
77
78                         if (!$got_lock && ($timeout > 0)) {
79                                 usleep(rand(10000, 200000));
80                         }
81                 } while (!$got_lock && ((time() - $start) < $timeout));
82
83                 return $got_lock;
84         }
85
86         /**
87          * (@inheritdoc)
88          */
89         public function release($key, $override = false)
90         {
91                 $cachekey = self::getLockKey($key);
92
93                 if ($override) {
94                         $return = $this->cache->delete($cachekey);
95                 } else {
96                         $return = $this->cache->compareDelete($cachekey, getmypid());
97                 }
98                 $this->markRelease($key);
99
100                 return $return;
101         }
102
103         /**
104          * (@inheritdoc)
105          */
106         public function isLocked($key)
107         {
108                 $cachekey = self::getLockKey($key);
109                 $lock     = $this->cache->get($cachekey);
110                 return isset($lock) && ($lock !== false);
111         }
112
113         /**
114          * {@inheritDoc}
115          */
116         public function getName()
117         {
118                 return $this->cache->getName();
119         }
120
121         /**
122          * {@inheritDoc}
123          */
124         public function getLocks(string $prefix = '')
125         {
126                 $locks = $this->cache->getAllKeys(self::CACHE_PREFIX . $prefix);
127
128                 array_walk($locks, function (&$lock, $key) {
129                         $lock = substr($lock, strlen(self::CACHE_PREFIX));
130                 });
131
132                 return $locks;
133         }
134
135         /**
136          * {@inheritDoc}
137          */
138         public function releaseAll($override = false)
139         {
140                 $success = parent::releaseAll($override);
141
142                 $locks = $this->getLocks();
143
144                 foreach ($locks as $lock) {
145                         if (!$this->release($lock, $override)) {
146                                 $success = false;
147                         }
148                 }
149
150                 return $success;
151         }
152
153         /**
154          * @param string $key The original key
155          *
156          * @return string        The cache key used for the cache
157          */
158         private static function getLockKey($key)
159         {
160                 return self::CACHE_PREFIX . $key;
161         }
162 }