]> git.mxchange.org Git - friendica.git/blobdiff - src/Util/Lock.php
Beware of camels
[friendica.git] / src / Util / Lock.php
index 03e8545adfdaadc20866268ce7b8c1156648955c..26ccdc9dd2326378f7ad310eb4245a5421e32b33 100644 (file)
@@ -8,6 +8,8 @@ namespace Friendica\Util;
  *
  */
 
+use Friendica\Core\Config;
+use Memcache;
 use dba;
 use dbm;
 
@@ -15,48 +17,143 @@ use dbm;
  * @brief This class contain Functions for preventing parallel execution of functions
  */
 class Lock {
+       /**
+        * @brief Check for memcache and open a connection if configured
+        *
+        * @return object|boolean The memcache object - or "false" if not successful
+        */
+       private static function connectMemcache() {
+               if (!function_exists('memcache_connect')) {
+                       return false;
+               }
+
+               if (!Config::get('system', 'memcache')) {
+                       return false;
+               }
 
+               $memcache_host = Config::get('system', 'memcache_host', '127.0.0.1');
+               $memcache_port = Config::get('system', 'memcache_port', 11211);
 
-// Provide some ability to lock a PHP function so that multiple processes
-// can't run the function concurrently
+               $memcache = new Memcache;
 
-       public static function set($fn_name, $wait_sec = 2, $timeout = 30) {
-               if ($wait_sec == 0) {
-                       $wait_sec = 2;
+               if (!$memcache->connect($memcache_host, $memcache_port)) {
+                       return false;
                }
 
+               return $memcache;
+       }
+
+       /**
+        * @brief Sets a lock for a given name
+        *
+        * @param string $fn_name Name of the lock
+        * @param integer $timeout Seconds until we give up
+        *
+        * @return boolean Was the lock successful?
+        */
+       public static function set($fn_name, $timeout = 120) {
                $got_lock = false;
                $start = time();
 
-               do {
-                       dba:p("LOCK TABLE `locks` WRITE");
-                       $lock = dba::select('locks', array('locked'), array('name' => $fn_name), array('limit' => 1));
+               $memcache = self::connectMemcache();
+               if (is_object($memcache)) {
+                       $wait_sec = 0.2;
+                       $cachekey = get_app()->get_hostname().";lock:".$fn_name;
+
+                       do {
+                               $lock = $memcache->get($cachekey);
+
+                               if (!is_bool($lock)) {
+                                       $pid = (int)$lock;
+
+                                       // When the process id isn't used anymore, we can safely claim the lock for us.
+                                       // Or we do want to lock something that was already locked by us.
+                                       if (!posix_kill($pid, 0) OR ($pid == getmypid())) {
+                                               $lock = false;
+                                       }
+                               }
+                               if (is_bool($lock)) {
+                                       $memcache->set($cachekey, getmypid(), MEMCACHE_COMPRESSED, 300);
+                                       $got_lock = true;
+                               }
+                               if (!$got_lock AND ($timeout > 0)) {
+                                       usleep($wait_sec * 1000000);
+                               }
+                       } while (!$got_lock AND ((time() - $start) < $timeout));
+
+                       return $got_lock;
+               }
 
-                       if ((dbm::is_result($lock)) AND !$lock['locked']) {
-                               dba::update('locks', array('locked' => true), array('name' => $fn_name));
-                               $got_lock = true;
+               $wait_sec = 2;
+
+               do {
+                       dba::lock('locks');
+                       $lock = dba::select('locks', array('locked', 'pid'), array('name' => $fn_name), array('limit' => 1));
+
+                       if (dbm::is_result($lock)) {
+                               if ($lock['locked']) {
+                                       // When the process id isn't used anymore, we can safely claim the lock for us.
+                                       if (!posix_kill($lock['pid'], 0)) {
+                                               $lock['locked'] = false;
+                                       }
+                                       // We want to lock something that was already locked by us? So we got the lock.
+                                       if ($lock['pid'] == getmypid()) {
+                                               $got_lock = true;
+                                       }
+                               }
+                               if (!$lock['locked']) {
+                                       dba::update('locks', array('locked' => true, 'pid' => getmypid()), array('name' => $fn_name));
+                                       $got_lock = true;
+                               }
                        } elseif (!dbm::is_result($lock)) {
-                               dbm::insert('locks', array('name' => $fn_name, 'locked' => true));
+                               dba::insert('locks', array('name' => $fn_name, 'locked' => true, 'pid' => getmypid()));
                                $got_lock = true;
                        }
 
-                       dbm::p("UNLOCK TABLES");
+                       dba::unlock();
 
-                       if (!$got_lock) {
+                       if (!$got_lock AND ($timeout > 0)) {
                                sleep($wait_sec);
                        }
                } while (!$got_lock AND ((time() - $start) < $timeout));
 
-               logger('lock_function: function ' . $fn_name . ' with blocking = ' . $block . ' got_lock = ' . $got_lock . ' time = ' . (time() - $start), LOGGER_DEBUG);
-
                return $got_lock;
        }
 
+       /**
+        * @brief Removes a lock if it was set by us
+        *
+        * @param string $fn_name Name of the lock
+        */
        public static function remove($fn_name) {
-               dba::update('locks', array('locked' => false), array('name' => $fn_name));
+               $memcache = self::connectMemcache();
+               if (is_object($memcache)) {
+                       $cachekey = get_app()->get_hostname().";lock:".$fn_name;
+                       $lock = $memcache->get($cachekey);
+
+                       if (!is_bool($lock)) {
+                               if ((int)$lock == getmypid()) {
+                                       $memcache->delete($cachekey);
+                               }
+                       }
+                       return;
+               }
 
-               logger('unlock_function: released lock for function ' . $fn_name, LOGGER_DEBUG);
+               dba::update('locks', array('locked' => false, 'pid' => 0), array('name' => $fn_name, 'pid' => getmypid()));
+               return;
+       }
+
+       /**
+        * @brief Removes all lock that were set by us
+        */
+       public static function removeAll() {
+               $memcache = self::connectMemcache();
+               if (is_object($memcache)) {
+                       // We cannot delete all cache entries, but this doesn't matter with memcache
+                       return;
+               }
 
+               dba::update('locks', array('locked' => false, 'pid' => 0), array('pid' => getmypid()));
                return;
        }
 }