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