1
0
Fork 0

Refactoring Core class structures ...

This commit is contained in:
Philipp Holzer 2021-10-26 21:44:29 +02:00
commit b216317477
Signed by: nupplaPhil
GPG key ID: 24A7501396EB5432
130 changed files with 1625 additions and 1397 deletions

View file

@ -19,23 +19,22 @@
*
*/
namespace Friendica\Core\Lock;
namespace Friendica\Core\Lock\Capability;
use Friendica\Core\Cache\Enum\Duration;
use Friendica\Core\Lock\Exception\LockPersistenceException;
/**
* Lock Interface
*/
interface ILock
interface ICanLock
{
/**
* Checks, if a key is currently locked to a or my process
*
* @param string $key The name of the lock
*
* @return bool
*/
public function isLocked($key);
public function isLocked(string $key): bool;
/**
*
@ -45,9 +44,9 @@ interface ILock
* @param integer $timeout Seconds until we give up
* @param integer $ttl Seconds The lock lifespan, must be one of the Cache constants
*
* @return boolean Was the lock successful?
* @throws LockPersistenceException In case the underlying persistence throws errors
*/
public function acquire($key, $timeout = 120, $ttl = Duration::FIVE_MINUTES);
public function acquire(string $key, int $timeout = 120, int $ttl = Duration::FIVE_MINUTES): bool;
/**
* Releases a lock if it was set by us
@ -55,32 +54,36 @@ interface ILock
* @param string $key The Name of the lock
* @param bool $override Overrides the lock to get released
*
* @return boolean Was the unlock successful?
* @return bool Was the unlock successful?
*
* @throws LockPersistenceException In case the underlying persistence throws errors
*/
public function release($key, $override = false);
public function release(string $key, bool $override = false): bool;
/**
* Releases all lock that were set by us
*
* @param bool $override Override to release all locks
*
* @return boolean Was the unlock of all locks successful?
* @return bool Was the unlock of all locks successful?
*
* @throws LockPersistenceException In case the underlying persistence throws errors
*/
public function releaseAll($override = false);
public function releaseAll(bool $override = false): bool;
/**
* Returns the name of the current lock
*
* @return string
*/
public function getName();
public function getName(): string;
/**
* Lists all locks
*
* @param string prefix optional a prefix to search
*
* @return array Empty if it isn't supported by the cache driver
* @return string[] Empty if it isn't supported by the cache driver
*
* @throws LockPersistenceException In case the underlying persistence throws errors
*/
public function getLocks(string $prefix = '');
public function getLocks(string $prefix = ''): array;
}

View file

@ -0,0 +1,13 @@
<?php
namespace Friendica\Core\Lock\Exception;
use Throwable;
class InvalidLockDriverException extends \RuntimeException
{
public function __construct($message = "", Throwable $previous = null)
{
parent::__construct($message, 500, $previous);
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace Friendica\Core\Lock\Exception;
use Throwable;
class LockPersistenceException extends \RuntimeException
{
public function __construct($message = "", Throwable $previous = null)
{
parent::__construct($message, 500, $previous);
}
}

View file

@ -21,11 +21,12 @@
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\Core\Cache\Factory\Cache;
use Friendica\Core\Cache\Capability\ICanCacheInMemory;
use Friendica\Core\Cache\Enum;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Lock\Capability\ICanLock;
use Friendica\Core\Lock\Type;
use Friendica\Database\Database;
use Psr\Log\LoggerInterface;
@ -36,7 +37,7 @@ use Psr\Log\LoggerInterface;
*
* A basic class to generate a LockDriver
*/
class LockFactory
class Lock
{
/**
* @var string The default driver for caching
@ -44,7 +45,7 @@ class LockFactory
const DEFAULT_DRIVER = 'default';
/**
* @var IConfig The configuration to read parameters out of the config
* @var IManageConfigValues The configuration to read parameters out of the config
*/
private $config;
@ -54,7 +55,7 @@ class LockFactory
private $dba;
/**
* @var CacheFactory The memory cache driver in case we use it
* @var Cache The memory cache driver in case we use it
*/
private $cacheFactory;
@ -63,7 +64,7 @@ class LockFactory
*/
private $logger;
public function __construct(CacheFactory $cacheFactory, IConfig $config, Database $dba, LoggerInterface $logger)
public function __construct(Cache $cacheFactory, IManageConfigValues $config, Database $dba, LoggerInterface $logger)
{
$this->cacheFactory = $cacheFactory;
$this->config = $config;
@ -77,24 +78,24 @@ class LockFactory
try {
switch ($lock_type) {
case Type::MEMCACHE:
case Type::MEMCACHED:
case Type::REDIS:
case Type::APCU:
case Enum\Type::MEMCACHE:
case Enum\Type::MEMCACHED:
case Enum\Type::REDIS:
case Enum\Type::APCU:
$cache = $this->cacheFactory->create($lock_type);
if ($cache instanceof IMemoryCache) {
return new Lock\Type\CacheLock($cache);
if ($cache instanceof ICanCacheInMemory) {
return new 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);
return new Type\DatabaseLock($this->dba);
break;
case 'semaphore':
return new Lock\Type\SemaphoreLock();
return new Type\SemaphoreLock();
break;
default:
@ -114,14 +115,14 @@ class LockFactory
* 2. Cache Locking
* 3. Database Locking
*
* @return Lock\ILock
* @return ICanLock
*/
private function useAutoDriver()
{
// 1. Try to use Semaphores for - local - locking
if (function_exists('sem_get')) {
try {
return new Lock\Type\SemaphoreLock();
return new Type\SemaphoreLock();
} catch (\Exception $exception) {
$this->logger->warning('Using Semaphore driver for locking failed.', ['exception' => $exception]);
}
@ -129,11 +130,11 @@ class LockFactory
// 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) {
if ($cache_type != Enum\Type::DATABASE) {
try {
$cache = $this->cacheFactory->create($cache_type);
if ($cache instanceof IMemoryCache) {
return new Lock\Type\CacheLock($cache);
if ($cache instanceof ICanCacheInMemory) {
return new Type\CacheLock($cache);
}
} catch (\Exception $exception) {
$this->logger->warning('Using Cache driver for locking failed.', ['exception' => $exception]);
@ -141,6 +142,6 @@ class LockFactory
}
// 3. Use Database Locking as a Fallback
return new Lock\Type\DatabaseLock($this->dba);
return new Type\DatabaseLock($this->dba);
}
}

View file

@ -21,12 +21,12 @@
namespace Friendica\Core\Lock\Type;
use Friendica\Core\Lock\ILock;
use Friendica\Core\Lock\Capability\ICanLock;
/**
* Basic class for Locking with common functions (local acquired locks, releaseAll, ..)
*/
abstract class BaseLock implements ILock
abstract class AbstractLock implements ICanLock
{
/**
* @var array The local acquired locks
@ -40,7 +40,7 @@ abstract class BaseLock implements ILock
*
* @return bool Returns true if the lock is set
*/
protected function hasAcquiredLock($key)
protected function hasAcquiredLock(string $key): bool
{
return isset($this->acquireLock[$key]) && $this->acquiredLocks[$key] === true;
}
@ -50,7 +50,7 @@ abstract class BaseLock implements ILock
*
* @param string $key The Name of the lock
*/
protected function markAcquire($key)
protected function markAcquire(string $key)
{
$this->acquiredLocks[$key] = true;
}
@ -60,7 +60,7 @@ abstract class BaseLock implements ILock
*
* @param string $key The Name of the lock
*/
protected function markRelease($key)
protected function markRelease(string $key)
{
unset($this->acquiredLocks[$key]);
}
@ -68,7 +68,7 @@ abstract class BaseLock implements ILock
/**
* {@inheritDoc}
*/
public function releaseAll($override = false)
public function releaseAll(bool $override = false): bool
{
$return = true;

View file

@ -21,10 +21,13 @@
namespace Friendica\Core\Lock\Type;
use Friendica\Core\Cache\Capability\ICanCache;
use Friendica\Core\Cache\Capability\ICanCacheInMemory;
use Friendica\Core\Cache\Enum\Duration;
use Friendica\Core\Cache\IMemoryCache;
use Friendica\Core\Cache\Exception\CachePersistenceException;
use Friendica\Core\Lock\Exception\LockPersistenceException;
class CacheLock extends BaseLock
class CacheLock extends AbstractLock
{
/**
* @var string The static prefix of all locks inside the cache
@ -32,16 +35,16 @@ class CacheLock extends BaseLock
const CACHE_PREFIX = 'lock:';
/**
* @var \Friendica\Core\Cache\ICache;
* @var ICanCache;
*/
private $cache;
/**
* CacheLock constructor.
*
* @param IMemoryCache $cache The CacheDriver for this type of lock
* @param ICanCacheInMemory $cache The CacheDriver for this type of lock
*/
public function __construct(IMemoryCache $cache)
public function __construct(ICanCacheInMemory $cache)
{
$this->cache = $cache;
}
@ -49,35 +52,39 @@ class CacheLock extends BaseLock
/**
* (@inheritdoc)
*/
public function acquire($key, $timeout = 120, $ttl = Duration::FIVE_MINUTES)
public function acquire(string $key, int $timeout = 120, int $ttl = Duration::FIVE_MINUTES): bool
{
$got_lock = false;
$start = time();
$cachekey = self::getLockKey($key);
$lockKey = 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)) {
try {
do {
$lock = $this->cache->get($lockKey);
// When we do want to lock something that was already locked by us.
if ((int)$lock == getmypid()) {
$got_lock = true;
$this->markAcquire($key);
}
}
if (!$got_lock && ($timeout > 0)) {
usleep(rand(10000, 200000));
}
} while (!$got_lock && ((time() - $start) < $timeout));
// When we do want to lock something new
if (is_null($lock)) {
// At first initialize it with "0"
$this->cache->add($lockKey, 0);
// Now the value has to be "0" because otherwise the key was used by another process meanwhile
if ($this->cache->compareSet($lockKey, 0, getmypid(), $ttl)) {
$got_lock = true;
$this->markAcquire($key);
}
}
if (!$got_lock && ($timeout > 0)) {
usleep(rand(10000, 200000));
}
} while (!$got_lock && ((time() - $start) < $timeout));
} catch (CachePersistenceException $exception) {
throw new LockPersistenceException(sprintf('Cannot acquire lock for key %s', $key), $exception);
}
return $got_lock;
}
@ -85,14 +92,18 @@ class CacheLock extends BaseLock
/**
* (@inheritdoc)
*/
public function release($key, $override = false)
public function release(string $key, bool $override = false): bool
{
$cachekey = self::getLockKey($key);
$lockKey = self::getLockKey($key);
if ($override) {
$return = $this->cache->delete($cachekey);
} else {
$return = $this->cache->compareDelete($cachekey, getmypid());
try {
if ($override) {
$return = $this->cache->delete($lockKey);
} else {
$return = $this->cache->compareDelete($lockKey, getmypid());
}
} catch (CachePersistenceException $exception) {
throw new LockPersistenceException(sprintf('Cannot release lock for key %s (override %b)', $key, $override), $exception);
}
$this->markRelease($key);
@ -102,17 +113,21 @@ class CacheLock extends BaseLock
/**
* (@inheritdoc)
*/
public function isLocked($key)
public function isLocked(string $key): bool
{
$cachekey = self::getLockKey($key);
$lock = $this->cache->get($cachekey);
$lockKey = self::getLockKey($key);
try {
$lock = $this->cache->get($lockKey);
} catch (CachePersistenceException $exception) {
throw new LockPersistenceException(sprintf('Cannot check lock state for key %s', $key), $exception);
}
return isset($lock) && ($lock !== false);
}
/**
* {@inheritDoc}
*/
public function getName()
public function getName(): string
{
return $this->cache->getName();
}
@ -120,11 +135,15 @@ class CacheLock extends BaseLock
/**
* {@inheritDoc}
*/
public function getLocks(string $prefix = '')
public function getLocks(string $prefix = ''): array
{
$locks = $this->cache->getAllKeys(self::CACHE_PREFIX . $prefix);
try {
$locks = $this->cache->getAllKeys(self::CACHE_PREFIX . $prefix);
} catch (CachePersistenceException $exception) {
throw new LockPersistenceException(sprintf('Cannot get locks with prefix %s', $prefix), $exception);
}
array_walk($locks, function (&$lock, $key) {
array_walk($locks, function (&$lock) {
$lock = substr($lock, strlen(self::CACHE_PREFIX));
});
@ -134,7 +153,7 @@ class CacheLock extends BaseLock
/**
* {@inheritDoc}
*/
public function releaseAll($override = false)
public function releaseAll(bool $override = false): bool
{
$success = parent::releaseAll($override);
@ -154,7 +173,7 @@ class CacheLock extends BaseLock
*
* @return string The cache key used for the cache
*/
private static function getLockKey($key)
private static function getLockKey(string $key): string
{
return self::CACHE_PREFIX . $key;
}

View file

@ -23,13 +23,14 @@ namespace Friendica\Core\Lock\Type;
use Friendica\Core\Cache\Enum\Duration;
use Friendica\Core\Lock\Enum\Type;
use Friendica\Core\Lock\Exception\LockPersistenceException;
use Friendica\Database\Database;
use Friendica\Util\DateTimeFormat;
/**
* Locking driver that stores the locks in the database
*/
class DatabaseLock extends BaseLock
class DatabaseLock extends AbstractLock
{
/**
* The current ID of the process
@ -44,49 +45,63 @@ class DatabaseLock extends BaseLock
private $dba;
/**
* @param null|int $pid The Id of the current process (null means determine automatically)
* @param int|null $pid The id of the current process (null means determine automatically)
*/
public function __construct(Database $dba, $pid = null)
public function __construct(Database $dba, ?int $pid = null)
{
$this->dba = $dba;
$this->pid = isset($pid) ? $pid : getmypid();
$this->pid = $pid ?? getmypid();
}
/**
* (@inheritdoc)
*/
public function acquire($key, $timeout = 120, $ttl = Duration::FIVE_MINUTES)
public function acquire(string $key, int $timeout = 120, int $ttl = Duration::FIVE_MINUTES): bool
{
$got_lock = false;
$start = time();
do {
$this->dba->lock('locks');
$lock = $this->dba->selectFirst('locks', ['locked', 'pid'], ['`name` = ? AND `expires` >= ?', $key, DateTimeFormat::utcNow()]);
try {
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) {
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;
}
}
if (!$lock['locked']) {
$this->dba->update('locks', ['locked' => true, 'pid' => $this->pid, 'expires' => DateTimeFormat::utc('now + ' . $ttl . 'seconds')], ['name' => $key]);
} else {
$this->dba->insert('locks', [
'name' => $key,
'locked' => true,
'pid' => $this->pid,
'expires' => DateTimeFormat::utc('now + ' . $ttl . 'seconds')]);
$got_lock = true;
$this->markAcquire($key);
}
} 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();
$this->dba->unlock();
if (!$got_lock && ($timeout > 0)) {
usleep(rand(100000, 2000000));
}
} while (!$got_lock && ((time() - $start) < $timeout));
if (!$got_lock && ($timeout > 0)) {
usleep(rand(100000, 2000000));
}
} while (!$got_lock && ((time() - $start) < $timeout));
} catch (\Exception $exception) {
throw new LockPersistenceException(sprintf('Cannot acquire lock for key %s', $key), $exception);
}
return $got_lock;
}
@ -94,7 +109,7 @@ class DatabaseLock extends BaseLock
/**
* (@inheritdoc)
*/
public function release($key, $override = false)
public function release(string $key, bool $override = false): bool
{
if ($override) {
$where = ['name' => $key];
@ -102,10 +117,14 @@ class DatabaseLock extends BaseLock
$where = ['name' => $key, 'pid' => $this->pid];
}
if ($this->dba->exists('locks', $where)) {
$return = $this->dba->delete('locks', $where);
} else {
$return = false;
try {
if ($this->dba->exists('locks', $where)) {
$return = $this->dba->delete('locks', $where);
} else {
$return = false;
}
} catch (\Exception $exception) {
throw new LockPersistenceException(sprintf('Cannot release lock for key %s (override %b)', $key, $override), $exception);
}
$this->markRelease($key);
@ -116,7 +135,7 @@ class DatabaseLock extends BaseLock
/**
* (@inheritdoc)
*/
public function releaseAll($override = false)
public function releaseAll(bool $override = false): bool
{
$success = parent::releaseAll($override);
@ -125,7 +144,12 @@ class DatabaseLock extends BaseLock
} else {
$where = ['pid' => $this->pid];
}
$return = $this->dba->delete('locks', $where);
try {
$return = $this->dba->delete('locks', $where);
} catch (\Exception $exception) {
throw new LockPersistenceException(sprintf('Cannot release all lock (override %b)', $override), $exception);
}
$this->acquiredLocks = [];
@ -135,9 +159,14 @@ class DatabaseLock extends BaseLock
/**
* (@inheritdoc)
*/
public function isLocked($key)
public function isLocked(string $key): bool
{
$lock = $this->dba->selectFirst('locks', ['locked'], ['`name` = ? AND `expires` >= ?', $key, DateTimeFormat::utcNow()]);
try {
$lock = $this->dba->selectFirst('locks', ['locked'], [
'`name` = ? AND `expires` >= ?', $key, DateTimeFormat::utcNow()]);
} catch (\Exception $exception) {
throw new LockPersistenceException(sprintf('Cannot check lock state for key %s', $key), $exception);
}
if ($this->dba->isResult($lock)) {
return $lock['locked'] !== false;
@ -149,7 +178,7 @@ class DatabaseLock extends BaseLock
/**
* {@inheritDoc}
*/
public function getName()
public function getName(): string
{
return Type::DATABASE;
}
@ -157,21 +186,26 @@ class DatabaseLock extends BaseLock
/**
* {@inheritDoc}
*/
public function getLocks(string $prefix = '')
public function getLocks(string $prefix = ''): array
{
if (empty($prefix)) {
$where = ['`expires` >= ?', DateTimeFormat::utcNow()];
} else {
$where = ['`expires` >= ? AND `name` LIKE CONCAT(?, \'%\')', DateTimeFormat::utcNow(), $prefix];
}
try {
if (empty($prefix)) {
$where = ['`expires` >= ?', DateTimeFormat::utcNow()];
} else {
$where = ['`expires` >= ? AND `name` LIKE CONCAT(?, \'%\')', DateTimeFormat::utcNow(), $prefix];
}
$stmt = $this->dba->select('locks', ['name'], $where);
$stmt = $this->dba->select('locks', ['name'], $where);
$keys = [];
while ($key = $this->dba->fetch($stmt)) {
array_push($keys, $key['name']);
$keys = [];
while ($key = $this->dba->fetch($stmt)) {
array_push($keys, $key['name']);
}
} catch (\Exception $exception) {
throw new LockPersistenceException(sprintf('Cannot get lock with prefix %s', $prefix), $exception);
} finally {
$this->dba->close($stmt);
}
$this->dba->close($stmt);
return $keys;
}

View file

@ -23,16 +23,17 @@ namespace Friendica\Core\Lock\Type;
use Friendica\Core\Cache\Enum\Duration;
use Friendica\Core\Lock\Enum\Type;
use Friendica\Core\Lock\Exception\InvalidLockDriverException;
use function get_temppath;
class SemaphoreLock extends BaseLock
class SemaphoreLock extends AbstractLock
{
private static $semaphore = [];
public function __construct()
{
if (!function_exists('sem_get')) {
throw new \Exception('Semaphore lock not supported');
throw new InvalidLockDriverException('Semaphore lock not supported');
}
}
@ -57,11 +58,11 @@ class SemaphoreLock extends BaseLock
/**
* (@inheritdoc)
*/
public function acquire($key, $timeout = 120, $ttl = Duration::FIVE_MINUTES)
public function acquire(string $key, int $timeout = 120, int $ttl = Duration::FIVE_MINUTES): bool
{
self::$semaphore[$key] = sem_get(self::semaphoreKey($key));
if (!empty(self::$semaphore[$key])) {
if ((bool)sem_acquire(self::$semaphore[$key], ($timeout === 0))) {
if (sem_acquire(self::$semaphore[$key], ($timeout === 0))) {
$this->markAcquire($key);
return true;
}
@ -76,7 +77,7 @@ class SemaphoreLock extends BaseLock
* @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)
public function release(string $key, bool $override = false): bool
{
$success = false;
@ -96,7 +97,7 @@ class SemaphoreLock extends BaseLock
/**
* (@inheritdoc)
*/
public function isLocked($key)
public function isLocked(string $key): bool
{
return isset(self::$semaphore[$key]);
}
@ -104,7 +105,7 @@ class SemaphoreLock extends BaseLock
/**
* {@inheritDoc}
*/
public function getName()
public function getName(): string
{
return Type::SEMAPHORE;
}
@ -112,7 +113,7 @@ class SemaphoreLock extends BaseLock
/**
* {@inheritDoc}
*/
public function getLocks(string $prefix = '')
public function getLocks(string $prefix = ''): array
{
// We can just return our own semaphore keys, since we don't know
// the state of other semaphores, even if the .sem files exists
@ -136,7 +137,7 @@ class SemaphoreLock extends BaseLock
/**
* {@inheritDoc}
*/
public function releaseAll($override = false)
public function releaseAll(bool $override = false): bool
{
// Semaphores are just alive during a run, so there is no need to release
// You can just release your own locks