mirror of
https://git.friendi.ca/friendica/friendica.git
synced 2025-06-17 01:25:21 +02:00
redesign of locking & caching
- New Factory "CacheDriverFactory" for Cache and Locks - Adding Redis/Memcached Locking - Moved Lock to Core - other improvements
This commit is contained in:
parent
acf6a5cb9e
commit
3f7e4f5bb6
12 changed files with 369 additions and 240 deletions
57
src/Core/Lock/AbstractLockDriver.php
Normal file
57
src/Core/Lock/AbstractLockDriver.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Core\Lock;
|
||||
|
||||
/**
|
||||
* Class AbstractLockDriver
|
||||
*
|
||||
* @package Friendica\Core\Lock
|
||||
*
|
||||
* @brief Basic class for Locking with common functions (local acquired locks, releaseAll, ..)
|
||||
*/
|
||||
abstract class AbstractLockDriver implements ILockDriver
|
||||
{
|
||||
/**
|
||||
* @var array The local acquired locks
|
||||
*/
|
||||
protected $acquiredLocks = [];
|
||||
|
||||
/**
|
||||
* @brief 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(string $key): bool {
|
||||
return isset($this->acquireLock[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Mark a locally acquired lock
|
||||
*
|
||||
* @param string $key The Name of the lock
|
||||
*/
|
||||
protected function markAcquire(string $key) {
|
||||
$this->acquiredLocks[$key] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Mark a release of a locally acquired lock
|
||||
*
|
||||
* @param string $key The Name of the lock
|
||||
*/
|
||||
protected function markRelease(string $key) {
|
||||
unset($this->acquiredLocks[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Releases all lock that were set by us
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function releaseAll() {
|
||||
foreach ($this->acquiredLocks as $acquiredLock) {
|
||||
$this->releaseLock($acquiredLock);
|
||||
}
|
||||
}
|
||||
}
|
89
src/Core/Lock/CacheLockDriver.php
Normal file
89
src/Core/Lock/CacheLockDriver.php
Normal file
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Core\Lock;
|
||||
|
||||
use Friendica\Core\Cache\ICacheDriver;
|
||||
|
||||
class CacheLockDriver extends AbstractLockDriver
|
||||
{
|
||||
/**
|
||||
* @var \Friendica\Core\Cache\ICacheDriver;
|
||||
*/
|
||||
private $cache;
|
||||
|
||||
/**
|
||||
* CacheLockDriver constructor.
|
||||
*
|
||||
* @param ICacheDriver $cache The CacheDriver for this type of lock
|
||||
*/
|
||||
public function __construct(ICacheDriver $cache)
|
||||
{
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief Sets a lock for a given name
|
||||
*
|
||||
* @param string $key The Name of the lock
|
||||
* @param integer $timeout Seconds until we give up
|
||||
*
|
||||
* @return boolean Was the lock successful?
|
||||
*/
|
||||
public function acquireLock(string $key, int $timeout = 120)
|
||||
{
|
||||
$got_lock = false;
|
||||
$start = time();
|
||||
|
||||
$cachekey = get_app()->get_hostname() . ";lock:" . $key;
|
||||
|
||||
do {
|
||||
$lock = $this->cache->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) || ($pid == getmypid())) {
|
||||
$lock = false;
|
||||
}
|
||||
}
|
||||
if (is_bool($lock)) {
|
||||
$this->cache->set($cachekey, getmypid(), 300);
|
||||
$got_lock = true;
|
||||
}
|
||||
|
||||
if (!$got_lock && ($timeout > 0)) {
|
||||
usleep(rand(10000, 200000));
|
||||
}
|
||||
} while (!$got_lock && ((time() - $start) < $timeout));
|
||||
|
||||
$this->markAcquire($key);
|
||||
|
||||
return $got_lock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes a lock if it was set by us
|
||||
*
|
||||
* @param string $key Name of the lock
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function releaseLock(string $key)
|
||||
{
|
||||
$cachekey = get_app()->get_hostname() . ";lock:" . $key;
|
||||
$lock = $this->cache->get($cachekey);
|
||||
|
||||
if (!is_bool($lock)) {
|
||||
if ((int)$lock == getmypid()) {
|
||||
$this->cache->delete($cachekey);
|
||||
}
|
||||
}
|
||||
|
||||
$this->markRelease($key);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
89
src/Core/Lock/DatabaseLockDriver.php
Normal file
89
src/Core/Lock/DatabaseLockDriver.php
Normal file
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Core\Lock;
|
||||
|
||||
use dba;
|
||||
use Friendica\Database\DBM;
|
||||
|
||||
/**
|
||||
* Locking driver that stores the locks in the database
|
||||
*/
|
||||
class DatabaseLockDriver extends AbstractLockDriver
|
||||
{
|
||||
/**
|
||||
* @brief Sets a lock for a given name
|
||||
*
|
||||
* @param string $key The Name of the lock
|
||||
* @param integer $timeout Seconds until we give up
|
||||
*
|
||||
* @return boolean Was the lock successful?
|
||||
*/
|
||||
public function acquireLock(string $key, int $timeout = 120)
|
||||
{
|
||||
$got_lock = false;
|
||||
$start = time();
|
||||
|
||||
do {
|
||||
dba::lock('locks');
|
||||
$lock = dba::selectFirst('locks', ['locked', 'pid'], ['name' => $key]);
|
||||
|
||||
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', ['locked' => true, 'pid' => getmypid()], ['name' => $key]);
|
||||
$got_lock = true;
|
||||
}
|
||||
} else {
|
||||
dba::insert('locks', ['name' => $key, 'locked' => true, 'pid' => getmypid()]);
|
||||
$got_lock = true;
|
||||
}
|
||||
|
||||
dba::unlock();
|
||||
|
||||
if (!$got_lock && ($timeout > 0)) {
|
||||
usleep(rand(100000, 2000000));
|
||||
}
|
||||
} while (!$got_lock && ((time() - $start) < $timeout));
|
||||
|
||||
$this->markAcquire($key);
|
||||
|
||||
return $got_lock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes a lock if it was set by us
|
||||
*
|
||||
* @param string $key Name of the lock
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function releaseLock(string $key)
|
||||
{
|
||||
dba::delete('locks', ['locked' => false, 'pid' => 0], ['name' => $key, 'pid' => getmypid()]);
|
||||
|
||||
$this->releaseLock($key);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes all lock that were set by us
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function releaseAll()
|
||||
{
|
||||
dba::delete('locks', ['locked' => false, 'pid' => 0], ['pid' => getmypid()]);
|
||||
|
||||
$this->acquiredLocks = [];
|
||||
}
|
||||
}
|
38
src/Core/Lock/ILockDriver.php
Normal file
38
src/Core/Lock/ILockDriver.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Core\Lock;
|
||||
|
||||
/**
|
||||
* Lock Driver Interface
|
||||
*
|
||||
* @author Philipp Holzer <admin@philipp.info>
|
||||
*/
|
||||
interface ILockDriver
|
||||
{
|
||||
/**
|
||||
*
|
||||
* @brief Acquires a lock for a given name
|
||||
*
|
||||
* @param string $key The Name of the lock
|
||||
* @param integer $timeout Seconds until we give up
|
||||
*
|
||||
* @return boolean Was the lock successful?
|
||||
*/
|
||||
public function acquireLock(string $key, int $timeout = 120);
|
||||
|
||||
/**
|
||||
* @brief Releases a lock if it was set by us
|
||||
*
|
||||
* @param string $key The Name of the lock
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function releaseLock(string $key);
|
||||
|
||||
/**
|
||||
* @brief Releases all lock that were set by us
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function releaseAll();
|
||||
}
|
68
src/Core/Lock/SemaphoreLockDriver.php
Normal file
68
src/Core/Lock/SemaphoreLockDriver.php
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Core\Lock;
|
||||
|
||||
class SemaphoreLockDriver extends AbstractLockDriver
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
if (!function_exists('sem_get')) {
|
||||
throw new \Exception('Semaphore lock not supported');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a semaphore key
|
||||
*
|
||||
* @param string $key Name of the lock
|
||||
*
|
||||
* @return integer the semaphore key
|
||||
*/
|
||||
private static function semaphoreKey(string $key): int
|
||||
{
|
||||
$temp = get_temppath();
|
||||
|
||||
$file = $temp.'/'.$key.'.sem';
|
||||
|
||||
if (!file_exists($file)) {
|
||||
file_put_contents($file, $key);
|
||||
}
|
||||
|
||||
return ftok($file, 'f');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief Sets a lock for a given name
|
||||
*
|
||||
* @param string $key The Name of the lock
|
||||
* @param integer $timeout Seconds until we give up
|
||||
*
|
||||
* @return boolean Was the lock successful?
|
||||
*/
|
||||
public function acquireLock(string $key, int $timeout = 120)
|
||||
{
|
||||
$this->acquiredLocks[$key] = sem_get(self::semaphoreKey($key));
|
||||
if ($this->acquiredLocks[$key]) {
|
||||
return sem_acquire($this->acquiredLocks[$key], ($timeout == 0));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes a lock if it was set by us
|
||||
*
|
||||
* @param string $key Name of the lock
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function releaseLock(string $key)
|
||||
{
|
||||
if (empty($this->acquiredLocks[$key])) {
|
||||
return false;
|
||||
} else {
|
||||
$success = @sem_release($this->acquiredLocks[$key]);
|
||||
unset($this->acquiredLocks[$key]);
|
||||
return $success;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue