]> git.mxchange.org Git - friendica.git/blob - src/Util/Lock.php
Merge pull request #5109 from rabuzarus/20180522_-_frio_nav_about_instance
[friendica.git] / src / Util / Lock.php
1 <?php
2 /**
3  * @file src/Util/Lock.php
4  */
5 namespace Friendica\Util;
6
7 /**
8  * @file src/Util/Lock.php
9  * @brief Functions for preventing parallel execution of functions
10  */
11
12 use Friendica\Core\Config;
13 use Friendica\Database\DBM;
14 use Memcache;
15 use dba;
16
17 require_once 'include/dba.php';
18
19 /**
20  * @brief This class contain Functions for preventing parallel execution of functions
21  */
22 class Lock
23 {
24         private static $semaphore = [];
25
26         /**
27          * @brief Check for memcache and open a connection if configured
28          *
29          * @return object|boolean The memcache object - or "false" if not successful
30          */
31         private static function connectMemcache()
32         {
33                 if (!function_exists('memcache_connect')) {
34                         return false;
35                 }
36
37                 if (!Config::get('system', 'memcache')) {
38                         return false;
39                 }
40
41                 $memcache_host = Config::get('system', 'memcache_host', '127.0.0.1');
42                 $memcache_port = Config::get('system', 'memcache_port', 11211);
43
44                 $memcache = new Memcache;
45
46                 if (!$memcache->connect($memcache_host, $memcache_port)) {
47                         return false;
48                 }
49
50                 return $memcache;
51         }
52
53         /**
54          * @brief Creates a semaphore key
55          *
56          * @param string $fn_name Name of the lock
57          *
58          * @return ressource the semaphore key
59          */
60         private static function semaphoreKey($fn_name)
61         {
62                 $temp = get_temppath();
63
64                 $file = $temp.'/'.$fn_name.'.sem';
65
66                 if (!file_exists($file)) {
67                         file_put_contents($file, $fn_name);
68                 }
69
70                 return ftok($file, 'f');
71         }
72
73         /**
74          * @brief Sets a lock for a given name
75          *
76          * @param string  $fn_name Name of the lock
77          * @param integer $timeout Seconds until we give up
78          *
79          * @return boolean Was the lock successful?
80          */
81         public static function set($fn_name, $timeout = 120)
82         {
83                 $got_lock = false;
84                 $start = time();
85
86                 // The second parameter for "sem_acquire" doesn't exist before 5.6.1
87                 if (function_exists('sem_get') && version_compare(PHP_VERSION, '5.6.1', '>=')) {
88                         self::$semaphore[$fn_name] = sem_get(self::semaphoreKey($fn_name));
89                         if (self::$semaphore[$fn_name]) {
90                                 return sem_acquire(self::$semaphore[$fn_name], ($timeout == 0));
91                         }
92                 }
93
94                 $memcache = self::connectMemcache();
95                 if (is_object($memcache)) {
96                         $cachekey = get_app()->get_hostname().";lock:".$fn_name;
97
98                         do {
99                                 // We only lock to be sure that nothing happens at exactly the same time
100                                 dba::lock('locks');
101                                 $lock = $memcache->get($cachekey);
102
103                                 if (!is_bool($lock)) {
104                                         $pid = (int)$lock;
105
106                                         // When the process id isn't used anymore, we can safely claim the lock for us.
107                                         // Or we do want to lock something that was already locked by us.
108                                         if (!posix_kill($pid, 0) || ($pid == getmypid())) {
109                                                 $lock = false;
110                                         }
111                                 }
112                                 if (is_bool($lock)) {
113                                         $memcache->set($cachekey, getmypid(), MEMCACHE_COMPRESSED, 300);
114                                         $got_lock = true;
115                                 }
116
117                                 dba::unlock();
118
119                                 if (!$got_lock && ($timeout > 0)) {
120                                         usleep(rand(10000, 200000));
121                                 }
122                         } while (!$got_lock && ((time() - $start) < $timeout));
123
124                         return $got_lock;
125                 }
126
127                 do {
128                         dba::lock('locks');
129                         $lock = dba::selectFirst('locks', ['locked', 'pid'], ['name' => $fn_name]);
130
131                         if (DBM::is_result($lock)) {
132                                 if ($lock['locked']) {
133                                         // When the process id isn't used anymore, we can safely claim the lock for us.
134                                         if (!posix_kill($lock['pid'], 0)) {
135                                                 $lock['locked'] = false;
136                                         }
137                                         // We want to lock something that was already locked by us? So we got the lock.
138                                         if ($lock['pid'] == getmypid()) {
139                                                 $got_lock = true;
140                                         }
141                                 }
142                                 if (!$lock['locked']) {
143                                         dba::update('locks', ['locked' => true, 'pid' => getmypid()], ['name' => $fn_name]);
144                                         $got_lock = true;
145                                 }
146                         } elseif (!DBM::is_result($lock)) {
147                                 dba::insert('locks', ['name' => $fn_name, 'locked' => true, 'pid' => getmypid()]);
148                                 $got_lock = true;
149                         }
150
151                         dba::unlock();
152
153                         if (!$got_lock && ($timeout > 0)) {
154                                 usleep(rand(100000, 2000000));
155                         }
156                 } while (!$got_lock && ((time() - $start) < $timeout));
157
158                 return $got_lock;
159         }
160
161         /**
162          * @brief Removes a lock if it was set by us
163          *
164          * @param string $fn_name Name of the lock
165          * @return mixed
166          */
167         public static function remove($fn_name)
168         {
169                 if (function_exists('sem_get') && version_compare(PHP_VERSION, '5.6.1', '>=')) {
170                         if (empty(self::$semaphore[$fn_name])) {
171                                 return false;
172                         } else {
173                                 $success = @sem_release(self::$semaphore[$fn_name]);
174                                 unset(self::$semaphore[$fn_name]);
175                                 return $success;
176                         }
177                 }
178
179                 $memcache = self::connectMemcache();
180                 if (is_object($memcache)) {
181                         $cachekey = get_app()->get_hostname().";lock:".$fn_name;
182                         $lock = $memcache->get($cachekey);
183
184                         if (!is_bool($lock)) {
185                                 if ((int)$lock == getmypid()) {
186                                         $memcache->delete($cachekey);
187                                 }
188                         }
189                         return;
190                 }
191
192                 dba::update('locks', ['locked' => false, 'pid' => 0], ['name' => $fn_name, 'pid' => getmypid()]);
193                 return;
194         }
195
196         /**
197          * @brief Removes all lock that were set by us
198          * @return void
199          */
200         public static function removeAll()
201         {
202                 $memcache = self::connectMemcache();
203                 if (is_object($memcache)) {
204                         // We cannot delete all cache entries, but this doesn't matter with memcache
205                         return;
206                 }
207
208                 dba::update('locks', ['locked' => false, 'pid' => 0], ['pid' => getmypid()]);
209                 return;
210         }
211 }