+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Core;
-
-use Friendica\Core\Lock\ILock;
-
-/**
- * Basic class for Locking with common functions (local acquired locks, releaseAll, ..)
- */
-abstract class BaseLock implements ILock
-{
- /**
- * @var array The local acquired locks
- */
- protected $acquiredLocks = [];
-
- /**
- * Check if we've locally acquired a lock
- *
- * @param string key The Name of the lock
- *
- * @return bool Returns true if the lock is set
- */
- protected function hasAcquiredLock($key)
- {
- return isset($this->acquireLock[$key]) && $this->acquiredLocks[$key] === true;
- }
-
- /**
- * Mark a locally acquired lock
- *
- * @param string $key The Name of the lock
- */
- protected function markAcquire($key)
- {
- $this->acquiredLocks[$key] = true;
- }
-
- /**
- * Mark a release of a locally acquired lock
- *
- * @param string $key The Name of the lock
- */
- protected function markRelease($key)
- {
- unset($this->acquiredLocks[$key]);
- }
-
- /**
- * {@inheritDoc}
- */
- public function releaseAll($override = false)
- {
- $return = true;
-
- foreach ($this->acquiredLocks as $acquiredLock => $hasLock) {
- if (!$this->release($acquiredLock, $override)) {
- $return = false;
- }
- }
-
- return $return;
- }
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Core\Lock;
-
-use Friendica\Core\BaseLock;
-use Friendica\Core\Cache\Enum\Duration;
-use Friendica\Core\Cache\IMemoryCache;
-
-class CacheLock extends BaseLock
-{
- /**
- * @var string The static prefix of all locks inside the cache
- */
- const CACHE_PREFIX = 'lock:';
-
- /**
- * @var \Friendica\Core\Cache\ICache;
- */
- private $cache;
-
- /**
- * CacheLock constructor.
- *
- * @param IMemoryCache $cache The CacheDriver for this type of lock
- */
- public function __construct(IMemoryCache $cache)
- {
- $this->cache = $cache;
- }
-
- /**
- * (@inheritdoc)
- */
- public function acquire($key, $timeout = 120, $ttl = Duration::FIVE_MINUTES)
- {
- $got_lock = false;
- $start = time();
-
- $cachekey = self::getLockKey($key);
-
- do {
- $lock = $this->cache->get($cachekey);
- // When we do want to lock something that was already locked by us.
- if ((int)$lock == getmypid()) {
- $got_lock = true;
- }
-
- // When we do want to lock something new
- if (is_null($lock)) {
- // At first initialize it with "0"
- $this->cache->add($cachekey, 0);
- // Now the value has to be "0" because otherwise the key was used by another process meanwhile
- if ($this->cache->compareSet($cachekey, 0, getmypid(), $ttl)) {
- $got_lock = true;
- $this->markAcquire($key);
- }
- }
-
- if (!$got_lock && ($timeout > 0)) {
- usleep(rand(10000, 200000));
- }
- } while (!$got_lock && ((time() - $start) < $timeout));
-
- return $got_lock;
- }
-
- /**
- * (@inheritdoc)
- */
- public function release($key, $override = false)
- {
- $cachekey = self::getLockKey($key);
-
- if ($override) {
- $return = $this->cache->delete($cachekey);
- } else {
- $return = $this->cache->compareDelete($cachekey, getmypid());
- }
- $this->markRelease($key);
-
- return $return;
- }
-
- /**
- * (@inheritdoc)
- */
- public function isLocked($key)
- {
- $cachekey = self::getLockKey($key);
- $lock = $this->cache->get($cachekey);
- return isset($lock) && ($lock !== false);
- }
-
- /**
- * {@inheritDoc}
- */
- public function getName()
- {
- return $this->cache->getName();
- }
-
- /**
- * {@inheritDoc}
- */
- public function getLocks(string $prefix = '')
- {
- $locks = $this->cache->getAllKeys(self::CACHE_PREFIX . $prefix);
-
- array_walk($locks, function (&$lock, $key) {
- $lock = substr($lock, strlen(self::CACHE_PREFIX));
- });
-
- return $locks;
- }
-
- /**
- * {@inheritDoc}
- */
- public function releaseAll($override = false)
- {
- $success = parent::releaseAll($override);
-
- $locks = $this->getLocks();
-
- foreach ($locks as $lock) {
- if (!$this->release($lock, $override)) {
- $success = false;
- }
- }
-
- return $success;
- }
-
- /**
- * @param string $key The original key
- *
- * @return string The cache key used for the cache
- */
- private static function getLockKey($key)
- {
- return self::CACHE_PREFIX . $key;
- }
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Core\Lock;
-
-use Friendica\Core\BaseLock;
-use Friendica\Core\Cache\Enum\Duration;
-use Friendica\Database\Database;
-use Friendica\Util\DateTimeFormat;
-
-/**
- * Locking driver that stores the locks in the database
- */
-class DatabaseLock extends BaseLock
-{
- /**
- * The current ID of the process
- *
- * @var int
- */
- private $pid;
-
- /**
- * @var Database The database connection of Friendica
- */
- private $dba;
-
- /**
- * @param null|int $pid The Id of the current process (null means determine automatically)
- */
- public function __construct(Database $dba, $pid = null)
- {
- $this->dba = $dba;
- $this->pid = isset($pid) ? $pid : getmypid();
- }
-
- /**
- * (@inheritdoc)
- */
- public function acquire($key, $timeout = 120, $ttl = Duration::FIVE_MINUTES)
- {
- $got_lock = false;
- $start = time();
-
- do {
- $this->dba->lock('locks');
- $lock = $this->dba->selectFirst('locks', ['locked', 'pid'], ['`name` = ? AND `expires` >= ?', $key, DateTimeFormat::utcNow()]);
-
- if ($this->dba->isResult($lock)) {
- if ($lock['locked']) {
- // We want to lock something that was already locked by us? So we got the lock.
- if ($lock['pid'] == $this->pid) {
- $got_lock = true;
- }
- }
- if (!$lock['locked']) {
- $this->dba->update('locks', ['locked' => true, 'pid' => $this->pid, 'expires' => DateTimeFormat::utc('now + ' . $ttl . 'seconds')], ['name' => $key]);
- $got_lock = true;
- }
- } else {
- $this->dba->insert('locks', ['name' => $key, 'locked' => true, 'pid' => $this->pid, 'expires' => DateTimeFormat::utc('now + ' . $ttl . 'seconds')]);
- $got_lock = true;
- $this->markAcquire($key);
- }
-
- $this->dba->unlock();
-
- if (!$got_lock && ($timeout > 0)) {
- usleep(rand(100000, 2000000));
- }
- } while (!$got_lock && ((time() - $start) < $timeout));
-
- return $got_lock;
- }
-
- /**
- * (@inheritdoc)
- */
- public function release($key, $override = false)
- {
- if ($override) {
- $where = ['name' => $key];
- } else {
- $where = ['name' => $key, 'pid' => $this->pid];
- }
-
- if ($this->dba->exists('locks', $where)) {
- $return = $this->dba->delete('locks', $where);
- } else {
- $return = false;
- }
-
- $this->markRelease($key);
-
- return $return;
- }
-
- /**
- * (@inheritdoc)
- */
- public function releaseAll($override = false)
- {
- $success = parent::releaseAll($override);
-
- if ($override) {
- $where = ['1 = 1'];
- } else {
- $where = ['pid' => $this->pid];
- }
- $return = $this->dba->delete('locks', $where);
-
- $this->acquiredLocks = [];
-
- return $return && $success;
- }
-
- /**
- * (@inheritdoc)
- */
- public function isLocked($key)
- {
- $lock = $this->dba->selectFirst('locks', ['locked'], ['`name` = ? AND `expires` >= ?', $key, DateTimeFormat::utcNow()]);
-
- if ($this->dba->isResult($lock)) {
- return $lock['locked'] !== false;
- } else {
- return false;
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public function getName()
- {
- return Type::DATABASE;
- }
-
- /**
- * {@inheritDoc}
- */
- public function getLocks(string $prefix = '')
- {
- if (empty($prefix)) {
- $where = ['`expires` >= ?', DateTimeFormat::utcNow()];
- } else {
- $where = ['`expires` >= ? AND `name` LIKE CONCAT(?, \'%\')', DateTimeFormat::utcNow(), $prefix];
- }
-
- $stmt = $this->dba->select('locks', ['name'], $where);
-
- $keys = [];
- while ($key = $this->dba->fetch($stmt)) {
- array_push($keys, $key['name']);
- }
- $this->dba->close($stmt);
-
- return $keys;
- }
-}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Lock\Enum;
+
+use Friendica\Core\Cache\Enum\Type as CacheType;
+
+/**
+ * Enumeration for lock types
+ *
+ * There's no "Cache" lock type, because the type depends on the concrete, used cache
+ */
+abstract class Type
+{
+ const DATABASE = CacheType::DATABASE;
+ const SEMAPHORE = 'semaphore';
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Lock\Factory;
+
+use Friendica\Core\Cache\Factory\CacheFactory;
+use Friendica\Core\Cache\IMemoryCache;
+use Friendica\Core\Cache\Enum\Type;
+use Friendica\Core\Config\IConfig;
+use Friendica\Core\Lock;
+use Friendica\Database\Database;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Class LockFactory
+ *
+ * @package Friendica\Core\Cache
+ *
+ * A basic class to generate a LockDriver
+ */
+class LockFactory
+{
+ /**
+ * @var string The default driver for caching
+ */
+ const DEFAULT_DRIVER = 'default';
+
+ /**
+ * @var IConfig The configuration to read parameters out of the config
+ */
+ private $config;
+
+ /**
+ * @var Database The database connection in case that the cache is used the dba connection
+ */
+ private $dba;
+
+ /**
+ * @var CacheFactory The memory cache driver in case we use it
+ */
+ private $cacheFactory;
+
+ /**
+ * @var LoggerInterface The Friendica Logger
+ */
+ private $logger;
+
+ public function __construct(CacheFactory $cacheFactory, IConfig $config, Database $dba, LoggerInterface $logger)
+ {
+ $this->cacheFactory = $cacheFactory;
+ $this->config = $config;
+ $this->dba = $dba;
+ $this->logger = $logger;
+ }
+
+ public function create()
+ {
+ $lock_type = $this->config->get('system', 'lock_driver', self::DEFAULT_DRIVER);
+
+ try {
+ switch ($lock_type) {
+ case Type::MEMCACHE:
+ case Type::MEMCACHED:
+ case Type::REDIS:
+ case Type::APCU:
+ $cache = $this->cacheFactory->create($lock_type);
+ if ($cache instanceof IMemoryCache) {
+ return new Lock\Type\CacheLock($cache);
+ } else {
+ throw new \Exception(sprintf('Incompatible cache driver \'%s\' for lock used', $lock_type));
+ }
+ break;
+
+ case 'database':
+ return new Lock\Type\DatabaseLock($this->dba);
+ break;
+
+ case 'semaphore':
+ return new Lock\Type\SemaphoreLock();
+ break;
+
+ default:
+ return self::useAutoDriver();
+ }
+ } catch (\Exception $exception) {
+ $this->logger->alert('Driver \'' . $lock_type . '\' failed - Fallback to \'useAutoDriver()\'', ['exception' => $exception]);
+ return self::useAutoDriver();
+ }
+ }
+
+ /**
+ * This method tries to find the best - local - locking method for Friendica
+ *
+ * The following sequence will be tried:
+ * 1. Semaphore Locking
+ * 2. Cache Locking
+ * 3. Database Locking
+ *
+ * @return Lock\ILock
+ */
+ private function useAutoDriver()
+ {
+ // 1. Try to use Semaphores for - local - locking
+ if (function_exists('sem_get')) {
+ try {
+ return new Lock\Type\SemaphoreLock();
+ } catch (\Exception $exception) {
+ $this->logger->warning('Using Semaphore driver for locking failed.', ['exception' => $exception]);
+ }
+ }
+
+ // 2. Try to use Cache Locking (don't use the DB-Cache Locking because it works different!)
+ $cache_type = $this->config->get('system', 'cache_driver', 'database');
+ if ($cache_type != Type::DATABASE) {
+ try {
+ $cache = $this->cacheFactory->create($cache_type);
+ if ($cache instanceof IMemoryCache) {
+ return new Lock\Type\CacheLock($cache);
+ }
+ } catch (\Exception $exception) {
+ $this->logger->warning('Using Cache driver for locking failed.', ['exception' => $exception]);
+ }
+ }
+
+ // 3. Use Database Locking as a Fallback
+ return new Lock\Type\DatabaseLock($this->dba);
+ }
+}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Core\Lock;
-
-use Friendica\Core\BaseLock;
-use Friendica\Core\Cache\Enum\Duration;
-
-class SemaphoreLock extends BaseLock
-{
- private static $semaphore = [];
-
- public function __construct()
- {
- if (!function_exists('sem_get')) {
- throw new \Exception('Semaphore lock not supported');
- }
- }
-
- /**
- * (@inheritdoc)
- */
- private static function semaphoreKey($key)
- {
- $success = true;
-
- $temp = get_temppath();
-
- $file = $temp . '/' . $key . '.sem';
-
- if (!file_exists($file)) {
- $success = !empty(file_put_contents($file, $key));
- }
-
- return $success ? ftok($file, 'f') : false;
- }
-
- /**
- * (@inheritdoc)
- */
- public function acquire($key, $timeout = 120, $ttl = Duration::FIVE_MINUTES)
- {
- self::$semaphore[$key] = sem_get(self::semaphoreKey($key));
- if (!empty(self::$semaphore[$key])) {
- if ((bool)sem_acquire(self::$semaphore[$key], ($timeout === 0))) {
- $this->markAcquire($key);
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * (@inheritdoc)
- *
- * @param bool $override not necessary parameter for semaphore locks since the lock lives as long as the execution
- * of the using function
- */
- public function release($key, $override = false)
- {
- $success = false;
-
- if (!empty(self::$semaphore[$key])) {
- try {
- $success = @sem_release(self::$semaphore[$key]);
- unset(self::$semaphore[$key]);
- $this->markRelease($key);
- } catch (\Exception $exception) {
- $success = false;
- }
- }
-
- return $success;
- }
-
- /**
- * (@inheritdoc)
- */
- public function isLocked($key)
- {
- return isset(self::$semaphore[$key]);
- }
-
- /**
- * {@inheritDoc}
- */
- public function getName()
- {
- return Type::SEMAPHORE;
- }
-
- /**
- * {@inheritDoc}
- */
- public function getLocks(string $prefix = '')
- {
- // We can just return our own semaphore keys, since we don't know
- // the state of other semaphores, even if the .sem files exists
- $keys = array_keys(self::$semaphore);
-
- if (empty($prefix)) {
- return $keys;
- } else {
- $result = [];
-
- foreach ($keys as $key) {
- if (strpos($key, $prefix) === 0) {
- array_push($result, $key);
- }
- }
-
- return $result;
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public function releaseAll($override = false)
- {
- // Semaphores are just alive during a run, so there is no need to release
- // You can just release your own locks
- return parent::releaseAll($override);
- }
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Core\Lock;
-
-use Friendica\Core\Cache\Enum\Type as CacheType;
-
-/**
- * Enumeration for lock types
- *
- * There's no "Cache" lock type, because the type depends on the concrete, used cache
- */
-abstract class Type
-{
- const DATABASE = CacheType::DATABASE;
- const SEMAPHORE = 'semaphore';
-}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Lock\Type;
+
+use Friendica\Core\Lock\ILock;
+
+/**
+ * Basic class for Locking with common functions (local acquired locks, releaseAll, ..)
+ */
+abstract class BaseLock implements ILock
+{
+ /**
+ * @var array The local acquired locks
+ */
+ protected $acquiredLocks = [];
+
+ /**
+ * Check if we've locally acquired a lock
+ *
+ * @param string key The Name of the lock
+ *
+ * @return bool Returns true if the lock is set
+ */
+ protected function hasAcquiredLock($key)
+ {
+ return isset($this->acquireLock[$key]) && $this->acquiredLocks[$key] === true;
+ }
+
+ /**
+ * Mark a locally acquired lock
+ *
+ * @param string $key The Name of the lock
+ */
+ protected function markAcquire($key)
+ {
+ $this->acquiredLocks[$key] = true;
+ }
+
+ /**
+ * Mark a release of a locally acquired lock
+ *
+ * @param string $key The Name of the lock
+ */
+ protected function markRelease($key)
+ {
+ unset($this->acquiredLocks[$key]);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function releaseAll($override = false)
+ {
+ $return = true;
+
+ foreach ($this->acquiredLocks as $acquiredLock => $hasLock) {
+ if (!$this->release($acquiredLock, $override)) {
+ $return = false;
+ }
+ }
+
+ return $return;
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Lock\Type;
+
+use Friendica\Core\Cache\Enum\Duration;
+use Friendica\Core\Cache\IMemoryCache;
+
+class CacheLock extends BaseLock
+{
+ /**
+ * @var string The static prefix of all locks inside the cache
+ */
+ const CACHE_PREFIX = 'lock:';
+
+ /**
+ * @var \Friendica\Core\Cache\ICache;
+ */
+ private $cache;
+
+ /**
+ * CacheLock constructor.
+ *
+ * @param IMemoryCache $cache The CacheDriver for this type of lock
+ */
+ public function __construct(IMemoryCache $cache)
+ {
+ $this->cache = $cache;
+ }
+
+ /**
+ * (@inheritdoc)
+ */
+ public function acquire($key, $timeout = 120, $ttl = Duration::FIVE_MINUTES)
+ {
+ $got_lock = false;
+ $start = time();
+
+ $cachekey = self::getLockKey($key);
+
+ do {
+ $lock = $this->cache->get($cachekey);
+ // When we do want to lock something that was already locked by us.
+ if ((int)$lock == getmypid()) {
+ $got_lock = true;
+ }
+
+ // When we do want to lock something new
+ if (is_null($lock)) {
+ // At first initialize it with "0"
+ $this->cache->add($cachekey, 0);
+ // Now the value has to be "0" because otherwise the key was used by another process meanwhile
+ if ($this->cache->compareSet($cachekey, 0, getmypid(), $ttl)) {
+ $got_lock = true;
+ $this->markAcquire($key);
+ }
+ }
+
+ if (!$got_lock && ($timeout > 0)) {
+ usleep(rand(10000, 200000));
+ }
+ } while (!$got_lock && ((time() - $start) < $timeout));
+
+ return $got_lock;
+ }
+
+ /**
+ * (@inheritdoc)
+ */
+ public function release($key, $override = false)
+ {
+ $cachekey = self::getLockKey($key);
+
+ if ($override) {
+ $return = $this->cache->delete($cachekey);
+ } else {
+ $return = $this->cache->compareDelete($cachekey, getmypid());
+ }
+ $this->markRelease($key);
+
+ return $return;
+ }
+
+ /**
+ * (@inheritdoc)
+ */
+ public function isLocked($key)
+ {
+ $cachekey = self::getLockKey($key);
+ $lock = $this->cache->get($cachekey);
+ return isset($lock) && ($lock !== false);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getName()
+ {
+ return $this->cache->getName();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getLocks(string $prefix = '')
+ {
+ $locks = $this->cache->getAllKeys(self::CACHE_PREFIX . $prefix);
+
+ array_walk($locks, function (&$lock, $key) {
+ $lock = substr($lock, strlen(self::CACHE_PREFIX));
+ });
+
+ return $locks;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function releaseAll($override = false)
+ {
+ $success = parent::releaseAll($override);
+
+ $locks = $this->getLocks();
+
+ foreach ($locks as $lock) {
+ if (!$this->release($lock, $override)) {
+ $success = false;
+ }
+ }
+
+ return $success;
+ }
+
+ /**
+ * @param string $key The original key
+ *
+ * @return string The cache key used for the cache
+ */
+ private static function getLockKey($key)
+ {
+ return self::CACHE_PREFIX . $key;
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Lock\Type;
+
+use Friendica\Core\Cache\Enum\Duration;
+use Friendica\Core\Lock\Enum\Type;
+use Friendica\Database\Database;
+use Friendica\Util\DateTimeFormat;
+
+/**
+ * Locking driver that stores the locks in the database
+ */
+class DatabaseLock extends BaseLock
+{
+ /**
+ * The current ID of the process
+ *
+ * @var int
+ */
+ private $pid;
+
+ /**
+ * @var Database The database connection of Friendica
+ */
+ private $dba;
+
+ /**
+ * @param null|int $pid The Id of the current process (null means determine automatically)
+ */
+ public function __construct(Database $dba, $pid = null)
+ {
+ $this->dba = $dba;
+ $this->pid = isset($pid) ? $pid : getmypid();
+ }
+
+ /**
+ * (@inheritdoc)
+ */
+ public function acquire($key, $timeout = 120, $ttl = Duration::FIVE_MINUTES)
+ {
+ $got_lock = false;
+ $start = time();
+
+ do {
+ $this->dba->lock('locks');
+ $lock = $this->dba->selectFirst('locks', ['locked', 'pid'], ['`name` = ? AND `expires` >= ?', $key, DateTimeFormat::utcNow()]);
+
+ if ($this->dba->isResult($lock)) {
+ if ($lock['locked']) {
+ // We want to lock something that was already locked by us? So we got the lock.
+ if ($lock['pid'] == $this->pid) {
+ $got_lock = true;
+ }
+ }
+ if (!$lock['locked']) {
+ $this->dba->update('locks', ['locked' => true, 'pid' => $this->pid, 'expires' => DateTimeFormat::utc('now + ' . $ttl . 'seconds')], ['name' => $key]);
+ $got_lock = true;
+ }
+ } else {
+ $this->dba->insert('locks', ['name' => $key, 'locked' => true, 'pid' => $this->pid, 'expires' => DateTimeFormat::utc('now + ' . $ttl . 'seconds')]);
+ $got_lock = true;
+ $this->markAcquire($key);
+ }
+
+ $this->dba->unlock();
+
+ if (!$got_lock && ($timeout > 0)) {
+ usleep(rand(100000, 2000000));
+ }
+ } while (!$got_lock && ((time() - $start) < $timeout));
+
+ return $got_lock;
+ }
+
+ /**
+ * (@inheritdoc)
+ */
+ public function release($key, $override = false)
+ {
+ if ($override) {
+ $where = ['name' => $key];
+ } else {
+ $where = ['name' => $key, 'pid' => $this->pid];
+ }
+
+ if ($this->dba->exists('locks', $where)) {
+ $return = $this->dba->delete('locks', $where);
+ } else {
+ $return = false;
+ }
+
+ $this->markRelease($key);
+
+ return $return;
+ }
+
+ /**
+ * (@inheritdoc)
+ */
+ public function releaseAll($override = false)
+ {
+ $success = parent::releaseAll($override);
+
+ if ($override) {
+ $where = ['1 = 1'];
+ } else {
+ $where = ['pid' => $this->pid];
+ }
+ $return = $this->dba->delete('locks', $where);
+
+ $this->acquiredLocks = [];
+
+ return $return && $success;
+ }
+
+ /**
+ * (@inheritdoc)
+ */
+ public function isLocked($key)
+ {
+ $lock = $this->dba->selectFirst('locks', ['locked'], ['`name` = ? AND `expires` >= ?', $key, DateTimeFormat::utcNow()]);
+
+ if ($this->dba->isResult($lock)) {
+ return $lock['locked'] !== false;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getName()
+ {
+ return Type::DATABASE;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getLocks(string $prefix = '')
+ {
+ if (empty($prefix)) {
+ $where = ['`expires` >= ?', DateTimeFormat::utcNow()];
+ } else {
+ $where = ['`expires` >= ? AND `name` LIKE CONCAT(?, \'%\')', DateTimeFormat::utcNow(), $prefix];
+ }
+
+ $stmt = $this->dba->select('locks', ['name'], $where);
+
+ $keys = [];
+ while ($key = $this->dba->fetch($stmt)) {
+ array_push($keys, $key['name']);
+ }
+ $this->dba->close($stmt);
+
+ return $keys;
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Lock\Type;
+
+use Friendica\Core\Cache\Enum\Duration;
+use Friendica\Core\Lock\Enum\Type;
+use function get_temppath;
+
+class SemaphoreLock extends BaseLock
+{
+ private static $semaphore = [];
+
+ public function __construct()
+ {
+ if (!function_exists('sem_get')) {
+ throw new \Exception('Semaphore lock not supported');
+ }
+ }
+
+ /**
+ * (@inheritdoc)
+ */
+ private static function semaphoreKey($key)
+ {
+ $success = true;
+
+ $temp = get_temppath();
+
+ $file = $temp . '/' . $key . '.sem';
+
+ if (!file_exists($file)) {
+ $success = !empty(file_put_contents($file, $key));
+ }
+
+ return $success ? ftok($file, 'f') : false;
+ }
+
+ /**
+ * (@inheritdoc)
+ */
+ public function acquire($key, $timeout = 120, $ttl = Duration::FIVE_MINUTES)
+ {
+ self::$semaphore[$key] = sem_get(self::semaphoreKey($key));
+ if (!empty(self::$semaphore[$key])) {
+ if ((bool)sem_acquire(self::$semaphore[$key], ($timeout === 0))) {
+ $this->markAcquire($key);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * (@inheritdoc)
+ *
+ * @param bool $override not necessary parameter for semaphore locks since the lock lives as long as the execution
+ * of the using function
+ */
+ public function release($key, $override = false)
+ {
+ $success = false;
+
+ if (!empty(self::$semaphore[$key])) {
+ try {
+ $success = @sem_release(self::$semaphore[$key]);
+ unset(self::$semaphore[$key]);
+ $this->markRelease($key);
+ } catch (\Exception $exception) {
+ $success = false;
+ }
+ }
+
+ return $success;
+ }
+
+ /**
+ * (@inheritdoc)
+ */
+ public function isLocked($key)
+ {
+ return isset(self::$semaphore[$key]);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getName()
+ {
+ return Type::SEMAPHORE;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getLocks(string $prefix = '')
+ {
+ // We can just return our own semaphore keys, since we don't know
+ // the state of other semaphores, even if the .sem files exists
+ $keys = array_keys(self::$semaphore);
+
+ if (empty($prefix)) {
+ return $keys;
+ } else {
+ $result = [];
+
+ foreach ($keys as $key) {
+ if (strpos($key, $prefix) === 0) {
+ array_push($result, $key);
+ }
+ }
+
+ return $result;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function releaseAll($override = false)
+ {
+ // Semaphores are just alive during a run, so there is no need to release
+ // You can just release your own locks
+ return parent::releaseAll($override);
+ }
+}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Factory;
-
-use Friendica\Core\Cache\Factory\CacheFactory;
-use Friendica\Core\Cache\IMemoryCache;
-use Friendica\Core\Cache\Enum\Type;
-use Friendica\Core\Config\IConfig;
-use Friendica\Core\Lock;
-use Friendica\Database\Database;
-use Psr\Log\LoggerInterface;
-
-/**
- * Class LockFactory
- *
- * @package Friendica\Core\Cache
- *
- * A basic class to generate a LockDriver
- */
-class LockFactory
-{
- /**
- * @var string The default driver for caching
- */
- const DEFAULT_DRIVER = 'default';
-
- /**
- * @var IConfig The configuration to read parameters out of the config
- */
- private $config;
-
- /**
- * @var Database The database connection in case that the cache is used the dba connection
- */
- private $dba;
-
- /**
- * @var CacheFactory The memory cache driver in case we use it
- */
- private $cacheFactory;
-
- /**
- * @var LoggerInterface The Friendica Logger
- */
- private $logger;
-
- public function __construct(CacheFactory $cacheFactory, IConfig $config, Database $dba, LoggerInterface $logger)
- {
- $this->cacheFactory = $cacheFactory;
- $this->config = $config;
- $this->dba = $dba;
- $this->logger = $logger;
- }
-
- public function create()
- {
- $lock_type = $this->config->get('system', 'lock_driver', self::DEFAULT_DRIVER);
-
- try {
- switch ($lock_type) {
- case Type::MEMCACHE:
- case Type::MEMCACHED:
- case Type::REDIS:
- case Type::APCU:
- $cache = $this->cacheFactory->create($lock_type);
- if ($cache instanceof IMemoryCache) {
- return new Lock\CacheLock($cache);
- } else {
- throw new \Exception(sprintf('Incompatible cache driver \'%s\' for lock used', $lock_type));
- }
- break;
-
- case 'database':
- return new Lock\DatabaseLock($this->dba);
- break;
-
- case 'semaphore':
- return new Lock\SemaphoreLock();
- break;
-
- default:
- return self::useAutoDriver();
- }
- } catch (\Exception $exception) {
- $this->logger->alert('Driver \'' . $lock_type . '\' failed - Fallback to \'useAutoDriver()\'', ['exception' => $exception]);
- return self::useAutoDriver();
- }
- }
-
- /**
- * This method tries to find the best - local - locking method for Friendica
- *
- * The following sequence will be tried:
- * 1. Semaphore Locking
- * 2. Cache Locking
- * 3. Database Locking
- *
- * @return Lock\ILock
- */
- private function useAutoDriver()
- {
- // 1. Try to use Semaphores for - local - locking
- if (function_exists('sem_get')) {
- try {
- return new Lock\SemaphoreLock();
- } catch (\Exception $exception) {
- $this->logger->warning('Using Semaphore driver for locking failed.', ['exception' => $exception]);
- }
- }
-
- // 2. Try to use Cache Locking (don't use the DB-Cache Locking because it works different!)
- $cache_type = $this->config->get('system', 'cache_driver', 'database');
- if ($cache_type != Type::DATABASE) {
- try {
- $cache = $this->cacheFactory->create($cache_type);
- if ($cache instanceof IMemoryCache) {
- return new Lock\CacheLock($cache);
- }
- } catch (\Exception $exception) {
- $this->logger->warning('Using Cache driver for locking failed.', ['exception' => $exception]);
- }
- }
-
- // 3. Use Database Locking as a Fallback
- return new Lock\DatabaseLock($this->dba);
- }
-}
use Friendica\Core\Config;
use Friendica\Core\PConfig;
use Friendica\Core\L10n;
-use Friendica\Core\Lock\ILock;
+use Friendica\Core\Lock;
use Friendica\Core\Process;
use Friendica\Core\Session\ISession;
use Friendica\Core\StorageManager;
['create', [], Dice::CHAIN_CALL],
],
],
- \Friendica\Core\PConfig\IPConfig::class => [
+ PConfig\IPConfig::class => [
'instanceOf' => PConfig\Factory\PConfigFactory::class,
'call' => [
['create', [], Dice::CHAIN_CALL],
['create', [], Dice::CHAIN_CALL],
],
],
- ILock::class => [
- 'instanceOf' => Factory\LockFactory::class,
+ Lock\ILock::class => [
+ 'instanceOf' => Lock\Factory\LockFactory::class,
'call' => [
['create', [], Dice::CHAIN_CALL],
],
namespace Friendica\Test\src\Core\Lock;
use Friendica\Core\Cache\Type\APCuCache;
-use Friendica\Core\Lock\CacheLock;
+use Friendica\Core\Lock\Type\CacheLock;
/**
* @group APCU
protected function getInstance()
{
- return new CacheLock(new APCuCache('localhost'));
+ return new \Friendica\Core\Lock\Type\CacheLock(new APCuCache('localhost'));
}
}
namespace Friendica\Test\src\Core\Lock;
use Friendica\Core\Cache\Type\ArrayCache;
-use Friendica\Core\Lock\CacheLock;
+use Friendica\Core\Lock\Type\CacheLock;
class ArrayCacheLockTest extends LockTest
{
protected function getInstance()
{
- return new CacheLock(new ArrayCache('localhost'));
+ return new \Friendica\Core\Lock\Type\CacheLock(new ArrayCache('localhost'));
}
/**
namespace Friendica\Test\src\Core\Lock;
-use Friendica\Core\Lock\DatabaseLock;
+use Friendica\Core\Lock\Type\DatabaseLock;
use Friendica\Core\Config\Factory\ConfigFactory;
use Friendica\Test\DatabaseTestTrait;
use Friendica\Test\Util\Database\StaticDatabase;
use Exception;
use Friendica\Core\Cache\Type\MemcacheCache;
use Friendica\Core\Config\IConfig;
-use Friendica\Core\Lock\CacheLock;
+use Friendica\Core\Lock\Type\CacheLock;
use Mockery;
/**
try {
$cache = new MemcacheCache($host, $configMock);
- $lock = new CacheLock($cache);
+ $lock = new \Friendica\Core\Lock\Type\CacheLock($cache);
} catch (Exception $e) {
static::markTestSkipped('Memcache is not available');
}
use Exception;
use Friendica\Core\Cache\Type\MemcachedCache;
use Friendica\Core\Config\IConfig;
-use Friendica\Core\Lock\CacheLock;
+use Friendica\Core\Lock\Type\CacheLock;
use Mockery;
use Psr\Log\NullLogger;
use Exception;
use Friendica\Core\Cache\Type\RedisCache;
use Friendica\Core\Config\IConfig;
-use Friendica\Core\Lock\CacheLock;
+use Friendica\Core\Lock\Type\CacheLock;
use Mockery;
/**
try {
$cache = new RedisCache($host, $configMock);
- $lock = new CacheLock($cache);
+ $lock = new \Friendica\Core\Lock\Type\CacheLock($cache);
} catch (Exception $e) {
static::markTestSkipped('Redis is not available. Error: ' . $e->getMessage());
}
use Friendica\App;
use Friendica\Core\Config\IConfig;
use Friendica\Core\Config\Type\JitConfig;
-use Friendica\Core\Lock\SemaphoreLock;
+use Friendica\Core\Lock\Type\SemaphoreLock;
use Friendica\DI;
use Mockery;
use Mockery\MockInterface;
protected function getInstance()
{
- return new SemaphoreLock();
+ return new \Friendica\Core\Lock\Type\SemaphoreLock();
}
/**