Merge pull request #7515 from nupplaphil/task/console_lock
New Console Command: Lock
This commit is contained in:
commit
48caf55cff
14 changed files with 743 additions and 41 deletions
185
src/Console/Lock.php
Normal file
185
src/Console/Lock.php
Normal file
|
@ -0,0 +1,185 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Console;
|
||||
|
||||
use Asika\SimpleConsole\CommandArgsException;
|
||||
use Friendica\App;
|
||||
use Friendica\Core\Lock\ILock;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @brief tool to access the locks from the CLI
|
||||
*
|
||||
* With this script you can access the locks of your node from the CLI.
|
||||
* You can read current locks and set/remove locks.
|
||||
*
|
||||
* @author Philipp Holzer <admin@philipp.info>, Hypolite Petovan <hypolite@mrpetovan.com>
|
||||
*/
|
||||
class Lock extends \Asika\SimpleConsole\Console
|
||||
{
|
||||
protected $helpOptions = ['h', 'help', '?'];
|
||||
|
||||
/**
|
||||
* @var App\Mode
|
||||
*/
|
||||
private $appMode;
|
||||
|
||||
/**
|
||||
* @var ILock
|
||||
*/
|
||||
private $lock;
|
||||
|
||||
protected function getHelp()
|
||||
{
|
||||
$help = <<<HELP
|
||||
console lock - Manage node locks
|
||||
Synopsis
|
||||
bin/console lock list [<prefix>] [-h|--help|-?] [-v]
|
||||
bin/console lock set <lock> [<timeout> [<ttl>]] [-h|--help|-?] [-v]
|
||||
bin/console lock del <lock> [-h|--help|-?] [-v]
|
||||
bin/console lock clear [-h|--help|-?] [-v]
|
||||
|
||||
Description
|
||||
bin/console lock list [<prefix>]
|
||||
List all locks, optionally filtered by a prefix
|
||||
|
||||
bin/console lock set <lock> [<timeout> [<ttl>]]
|
||||
Sets manually a lock, optionally with the provided TTL (time to live) with a default of five minutes.
|
||||
|
||||
bin/console lock del <lock>
|
||||
Deletes a lock.
|
||||
|
||||
bin/console lock clear
|
||||
Clears all locks
|
||||
|
||||
Options
|
||||
-h|--help|-? Show help information
|
||||
-v Show more debug information.
|
||||
HELP;
|
||||
return $help;
|
||||
}
|
||||
|
||||
public function __construct(App\Mode $appMode, ILock $lock, array $argv = null)
|
||||
{
|
||||
parent::__construct($argv);
|
||||
|
||||
$this->appMode = $appMode;
|
||||
$this->lock = $lock;
|
||||
}
|
||||
|
||||
protected function doExecute()
|
||||
{
|
||||
if ($this->getOption('v')) {
|
||||
$this->out('Executable: ' . $this->executable);
|
||||
$this->out('Class: ' . __CLASS__);
|
||||
$this->out('Arguments: ' . var_export($this->args, true));
|
||||
$this->out('Options: ' . var_export($this->options, true));
|
||||
}
|
||||
|
||||
if (!$this->appMode->has(App\Mode::DBCONFIGAVAILABLE)) {
|
||||
$this->out('Database isn\'t ready or populated yet, database cache won\'t be available');
|
||||
}
|
||||
|
||||
if ($this->getOption('v')) {
|
||||
$this->out('Lock Driver Name: ' . $this->lock->getName());
|
||||
$this->out('Lock Driver Class: ' . get_class($this->lock));
|
||||
}
|
||||
|
||||
switch ($this->getArgument(0)) {
|
||||
case 'list':
|
||||
$this->executeList();
|
||||
break;
|
||||
case 'set':
|
||||
$this->executeSet();
|
||||
break;
|
||||
case 'del':
|
||||
$this->executeDel();
|
||||
break;
|
||||
case 'clear':
|
||||
$this->executeClear();
|
||||
break;
|
||||
}
|
||||
|
||||
if (count($this->args) == 0) {
|
||||
$this->out($this->getHelp());
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function executeList()
|
||||
{
|
||||
$prefix = $this->getArgument(1, '');
|
||||
$keys = $this->lock->getLocks($prefix);
|
||||
|
||||
if (empty($prefix)) {
|
||||
$this->out('Listing all Locks:');
|
||||
} else {
|
||||
$this->out('Listing all Locks starting with "' . $prefix . '":');
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
foreach ($keys as $key) {
|
||||
$this->out($key);
|
||||
$count++;
|
||||
}
|
||||
|
||||
$this->out($count . ' locks found');
|
||||
}
|
||||
|
||||
private function executeDel()
|
||||
{
|
||||
if (count($this->args) >= 2) {
|
||||
$lock = $this->getArgument(1);
|
||||
|
||||
if ($this->lock->releaseLock($lock, true)) {
|
||||
$this->out(sprintf('Lock \'%s\' released.', $lock));
|
||||
} else {
|
||||
$this->out(sprintf('Couldn\'t release Lock \'%s\'', $lock));
|
||||
}
|
||||
|
||||
} else {
|
||||
throw new CommandArgsException('Too few arguments for del.');
|
||||
}
|
||||
}
|
||||
|
||||
private function executeSet()
|
||||
{
|
||||
if (count($this->args) >= 2) {
|
||||
$lock = $this->getArgument(1);
|
||||
$timeout = intval($this->getArgument(2, false));
|
||||
$ttl = intval($this->getArgument(3, false));
|
||||
|
||||
if ($this->lock->isLocked($lock)) {
|
||||
throw new RuntimeException(sprintf('\'%s\' is already set.', $lock));
|
||||
}
|
||||
|
||||
if (!empty($ttl) && !empty($timeout)) {
|
||||
$result = $this->lock->acquireLock($lock, $timeout, $ttl);
|
||||
} elseif (!empty($timeout)) {
|
||||
$result = $this->lock->acquireLock($lock, $timeout);
|
||||
} else {
|
||||
$result = $this->lock->acquireLock($lock);
|
||||
}
|
||||
|
||||
if ($result) {
|
||||
$this->out(sprintf('Lock \'%s\' acquired.', $lock));
|
||||
} else {
|
||||
throw new RuntimeException(sprintf('Unable to lock \'%s\'.', $lock));
|
||||
}
|
||||
} else {
|
||||
throw new CommandArgsException('Too few arguments for set.');
|
||||
}
|
||||
}
|
||||
|
||||
private function executeClear()
|
||||
{
|
||||
$result = $this->lock->releaseAll(true);
|
||||
if ($result) {
|
||||
$this->out('Locks successfully cleared.');
|
||||
} else {
|
||||
throw new RuntimeException('Unable to clear the locks.');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,7 +21,7 @@ class ArrayCache extends Cache implements IMemoryCache
|
|||
*/
|
||||
public function getAllKeys($prefix = null)
|
||||
{
|
||||
return $this->filterArrayKeysByPrefix($this->cachedData, $prefix);
|
||||
return $this->filterArrayKeysByPrefix(array_keys($this->cachedData), $prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -84,19 +84,19 @@ abstract class Cache implements ICache
|
|||
* Filters the keys of an array with a given prefix
|
||||
* Returns the filtered keys as an new array
|
||||
*
|
||||
* @param array $array The array, which should get filtered
|
||||
* @param array $keys The keys, which should get filtered
|
||||
* @param string|null $prefix The prefix (if null, all keys will get returned)
|
||||
*
|
||||
* @return array The filtered array with just the keys
|
||||
*/
|
||||
protected function filterArrayKeysByPrefix($array, $prefix = null)
|
||||
protected function filterArrayKeysByPrefix(array $keys, string $prefix = null)
|
||||
{
|
||||
if (empty($prefix)) {
|
||||
return array_keys($array);
|
||||
return $keys;
|
||||
} else {
|
||||
$result = [];
|
||||
|
||||
foreach (array_keys($array) as $key) {
|
||||
foreach ($keys as $key) {
|
||||
if (strpos($key, $prefix) === 0) {
|
||||
array_push($result, $key);
|
||||
}
|
||||
|
|
|
@ -27,6 +27,17 @@ class MemcachedCache extends Cache implements IMemoryCache
|
|||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* @var string First server address
|
||||
*/
|
||||
|
||||
private $firstServer;
|
||||
|
||||
/**
|
||||
* @var int First server port
|
||||
*/
|
||||
private $firstPort;
|
||||
|
||||
/**
|
||||
* Due to limitations of the INI format, the expected configuration for Memcached servers is the following:
|
||||
* array {
|
||||
|
@ -58,6 +69,9 @@ class MemcachedCache extends Cache implements IMemoryCache
|
|||
}
|
||||
});
|
||||
|
||||
$this->firstServer = $memcached_hosts[0][0] ?? 'localhost';
|
||||
$this->firstPort = $memcached_hosts[0][1] ?? 11211;
|
||||
|
||||
$this->memcached->addServers($memcached_hosts);
|
||||
|
||||
if (count($this->memcached->getServerList()) == 0) {
|
||||
|
@ -70,14 +84,95 @@ class MemcachedCache extends Cache implements IMemoryCache
|
|||
*/
|
||||
public function getAllKeys($prefix = null)
|
||||
{
|
||||
$keys = $this->getOriginalKeys($this->memcached->getAllKeys());
|
||||
$keys = $this->getOriginalKeys($this->getMemcachedKeys());
|
||||
|
||||
if ($this->memcached->getResultCode() == Memcached::RES_SUCCESS) {
|
||||
return $this->filterArrayKeysByPrefix($keys, $prefix);
|
||||
} else {
|
||||
$this->logger->debug('Memcached \'getAllKeys\' failed', ['result' => $this->memcached->getResultMessage()]);
|
||||
return [];
|
||||
return $this->filterArrayKeysByPrefix($keys, $prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all memcached keys.
|
||||
* Special function because getAllKeys() is broken since memcached 1.4.23.
|
||||
*
|
||||
* cleaned up version of code found on Stackoverflow.com by Maduka Jayalath
|
||||
* @see https://stackoverflow.com/a/34724821
|
||||
*
|
||||
* @return array|int - all retrieved keys (or negative number on error)
|
||||
*/
|
||||
private function getMemcachedKeys()
|
||||
{
|
||||
$mem = @fsockopen($this->firstServer, $this->firstPort);
|
||||
if ($mem === false) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// retrieve distinct slab
|
||||
$r = @fwrite($mem, 'stats items' . chr(10));
|
||||
if ($r === false) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
$slab = [];
|
||||
while (($l = @fgets($mem, 1024)) !== false) {
|
||||
// finished?
|
||||
$l = trim($l);
|
||||
if ($l == 'END') {
|
||||
break;
|
||||
}
|
||||
|
||||
$m = [];
|
||||
// <STAT items:22:evicted_nonzero 0>
|
||||
$r = preg_match('/^STAT\sitems\:(\d+)\:/', $l, $m);
|
||||
if ($r != 1) {
|
||||
return -3;
|
||||
}
|
||||
$a_slab = $m[1];
|
||||
|
||||
if (!array_key_exists($a_slab, $slab)) {
|
||||
$slab[$a_slab] = [];
|
||||
}
|
||||
}
|
||||
|
||||
reset($slab);
|
||||
foreach ($slab as $a_slab_key => &$a_slab) {
|
||||
$r = @fwrite($mem, 'stats cachedump ' . $a_slab_key . ' 100' . chr(10));
|
||||
if ($r === false) {
|
||||
return -4;
|
||||
}
|
||||
|
||||
while (($l = @fgets($mem, 1024)) !== false) {
|
||||
// finished?
|
||||
$l = trim($l);
|
||||
if ($l == 'END') {
|
||||
break;
|
||||
}
|
||||
|
||||
$m = [];
|
||||
// ITEM 42 [118 b; 1354717302 s]
|
||||
$r = preg_match('/^ITEM\s([^\s]+)\s/', $l, $m);
|
||||
if ($r != 1) {
|
||||
return -5;
|
||||
}
|
||||
$a_key = $m[1];
|
||||
|
||||
$a_slab[] = $a_key;
|
||||
}
|
||||
}
|
||||
|
||||
// close the connection
|
||||
@fclose($mem);
|
||||
unset($mem);
|
||||
|
||||
$keys = [];
|
||||
reset($slab);
|
||||
foreach ($slab AS &$a_slab) {
|
||||
reset($a_slab);
|
||||
foreach ($a_slab AS &$a_key) {
|
||||
$keys[] = $a_key;
|
||||
}
|
||||
}
|
||||
unset($slab);
|
||||
|
||||
return $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -38,6 +38,7 @@ Commands:
|
|||
archivecontact Archive a contact when you know that it isn't existing anymore
|
||||
help Show help about a command, e.g (bin/console help config)
|
||||
autoinstall Starts automatic installation of friendica based on values from htconfig.php
|
||||
lock Edit site locks
|
||||
maintenance Set maintenance mode for this node
|
||||
newpassword Set a new password for a given user
|
||||
php2po Generate a messages.po file from a strings.php file
|
||||
|
@ -65,6 +66,7 @@ HELP;
|
|||
'globalcommunitysilence' => Friendica\Console\GlobalCommunitySilence::class,
|
||||
'archivecontact' => Friendica\Console\ArchiveContact::class,
|
||||
'autoinstall' => Friendica\Console\AutomaticInstallation::class,
|
||||
'lock' => Friendica\Console\Lock::class,
|
||||
'maintenance' => Friendica\Console\Maintenance::class,
|
||||
'newpassword' => Friendica\Console\NewPassword::class,
|
||||
'php2po' => Friendica\Console\PhpToPo::class,
|
||||
|
|
|
@ -7,6 +7,11 @@ use Friendica\Core\Cache\IMemoryCache;
|
|||
|
||||
class CacheLock extends Lock
|
||||
{
|
||||
/**
|
||||
* @var string The static prefix of all locks inside the cache
|
||||
*/
|
||||
const CACHE_PREFIX = 'lock:';
|
||||
|
||||
/**
|
||||
* @var \Friendica\Core\Cache\ICache;
|
||||
*/
|
||||
|
@ -25,7 +30,7 @@ class CacheLock extends Lock
|
|||
/**
|
||||
* (@inheritdoc)
|
||||
*/
|
||||
public function acquireLock($key, $timeout = 120, $ttl = Cache::FIVE_MINUTES)
|
||||
public function acquireLock($key, $timeout = 120, $ttl = Cache\Cache::FIVE_MINUTES)
|
||||
{
|
||||
$got_lock = false;
|
||||
$start = time();
|
||||
|
@ -85,6 +90,46 @@ class CacheLock extends Lock
|
|||
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->releaseLock($lock, $override)) {
|
||||
$success = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key The original key
|
||||
*
|
||||
|
@ -92,6 +137,6 @@ class CacheLock extends Lock
|
|||
*/
|
||||
private static function getLockKey($key)
|
||||
{
|
||||
return "lock:" . $key;
|
||||
return self::CACHE_PREFIX . $key;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,9 +92,16 @@ class DatabaseLock extends Lock
|
|||
/**
|
||||
* (@inheritdoc)
|
||||
*/
|
||||
public function releaseAll()
|
||||
public function releaseAll($override = false)
|
||||
{
|
||||
$return = $this->dba->delete('locks', ['pid' => $this->pid]);
|
||||
$success = parent::releaseAll($override);
|
||||
|
||||
if ($override) {
|
||||
$where = ['1 = 1'];
|
||||
} else {
|
||||
$where = ['pid' => $this->pid];
|
||||
}
|
||||
$return = $this->dba->delete('locks', $where);
|
||||
|
||||
$this->acquiredLocks = [];
|
||||
|
||||
|
@ -114,4 +121,34 @@ class DatabaseLock extends Lock
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return self::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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,25 @@ interface ILock
|
|||
/**
|
||||
* 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?
|
||||
*/
|
||||
public function releaseAll();
|
||||
public function releaseAll($override = false);
|
||||
|
||||
/**
|
||||
* Returns the name of the current lock
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName();
|
||||
|
||||
/**
|
||||
* Lists all locks
|
||||
*
|
||||
* @param string prefix optional a prefix to search
|
||||
*
|
||||
* @return array Empty if it isn't supported by the cache driver
|
||||
*/
|
||||
public function getLocks(string $prefix = '');
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Friendica\Core\Lock;
|
||||
|
||||
use Friendica\Core\Cache\Cache;
|
||||
|
||||
/**
|
||||
* Class AbstractLock
|
||||
*
|
||||
|
@ -11,6 +13,9 @@ namespace Friendica\Core\Lock;
|
|||
*/
|
||||
abstract class Lock implements ILock
|
||||
{
|
||||
const TYPE_DATABASE = Cache::TYPE_DATABASE;
|
||||
const TYPE_SEMAPHORE = 'semaphore';
|
||||
|
||||
/**
|
||||
* @var array The local acquired locks
|
||||
*/
|
||||
|
@ -49,16 +54,14 @@ abstract class Lock implements ILock
|
|||
}
|
||||
|
||||
/**
|
||||
* Releases all lock that were set by us
|
||||
*
|
||||
* @return boolean Was the unlock of all locks successful?
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function releaseAll()
|
||||
public function releaseAll($override = false)
|
||||
{
|
||||
$return = true;
|
||||
|
||||
foreach ($this->acquiredLocks as $acquiredLock => $hasLock) {
|
||||
if (!$this->releaseLock($acquiredLock)) {
|
||||
if (!$this->releaseLock($acquiredLock, $override)) {
|
||||
$return = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,9 +20,7 @@ class SemaphoreLock extends Lock
|
|||
*/
|
||||
private static function semaphoreKey($key)
|
||||
{
|
||||
$temp = get_temppath();
|
||||
|
||||
$file = $temp . '/' . $key . '.sem';
|
||||
$file = self::keyToFile($key);
|
||||
|
||||
if (!file_exists($file)) {
|
||||
file_put_contents($file, $key);
|
||||
|
@ -31,10 +29,24 @@ class SemaphoreLock extends Lock
|
|||
return ftok($file, 'f');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full path to the semaphore file
|
||||
*
|
||||
* @param string $key The key of the semaphore
|
||||
*
|
||||
* @return string The full path
|
||||
*/
|
||||
private static function keyToFile($key)
|
||||
{
|
||||
$temp = get_temppath();
|
||||
|
||||
return $temp . '/' . $key . '.sem';
|
||||
}
|
||||
|
||||
/**
|
||||
* (@inheritdoc)
|
||||
*/
|
||||
public function acquireLock($key, $timeout = 120, $ttl = Cache::FIVE_MINUTES)
|
||||
public function acquireLock($key, $timeout = 120, $ttl = Cache\Cache::FIVE_MINUTES)
|
||||
{
|
||||
self::$semaphore[$key] = sem_get(self::semaphoreKey($key));
|
||||
if (self::$semaphore[$key]) {
|
||||
|
@ -52,14 +64,24 @@ class SemaphoreLock extends Lock
|
|||
*/
|
||||
public function releaseLock($key, $override = false)
|
||||
{
|
||||
if (empty(self::$semaphore[$key])) {
|
||||
return false;
|
||||
} else {
|
||||
$success = @sem_release(self::$semaphore[$key]);
|
||||
unset(self::$semaphore[$key]);
|
||||
$this->markRelease($key);
|
||||
return $success;
|
||||
$success = false;
|
||||
|
||||
if (!empty(self::$semaphore[$key])) {
|
||||
try {
|
||||
$success = @sem_release(self::$semaphore[$key]) &&
|
||||
unlink(self::keyToFile($key));
|
||||
unset(self::$semaphore[$key]);
|
||||
$this->markRelease($key);
|
||||
} catch (\Exception $exception) {
|
||||
$success = false;
|
||||
}
|
||||
} else if ($override) {
|
||||
if ($this->acquireLock($key)) {
|
||||
$success = $this->releaseLock($key, true);
|
||||
}
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -69,4 +91,47 @@ class SemaphoreLock extends Lock
|
|||
{
|
||||
return isset(self::$semaphore[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return self::TYPE_SEMAPHORE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getLocks(string $prefix = '')
|
||||
{
|
||||
$temp = get_temppath();
|
||||
$locks = [];
|
||||
foreach (glob(sprintf('%s/%s*.sem', $temp, $prefix)) as $lock) {
|
||||
$lock = pathinfo($lock, PATHINFO_FILENAME);
|
||||
if(sem_get(self::semaphoreKey($lock))) {
|
||||
$locks[] = $lock;
|
||||
}
|
||||
}
|
||||
|
||||
return $locks;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function releaseAll($override = false)
|
||||
{
|
||||
$success = parent::releaseAll($override);
|
||||
|
||||
$temp = get_temppath();
|
||||
foreach (glob(sprintf('%s/*.sem', $temp)) as $lock) {
|
||||
$lock = pathinfo($lock, PATHINFO_FILENAME);
|
||||
if (!$this->releaseLock($lock, true)) {
|
||||
$success = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
}
|
||||
|
|
215
tests/src/Console/LockConsoleTest.php
Normal file
215
tests/src/Console/LockConsoleTest.php
Normal file
|
@ -0,0 +1,215 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Test\src\Console;
|
||||
|
||||
use Friendica\App;
|
||||
use Friendica\App\Mode;
|
||||
use Friendica\Console\Lock;
|
||||
use Friendica\Core\Lock\ILock;
|
||||
use Mockery\MockInterface;
|
||||
|
||||
class LockConsoleTest extends ConsoleTest
|
||||
{
|
||||
/**
|
||||
* @var App\Mode|MockInterface $appMode
|
||||
*/
|
||||
private $appMode;
|
||||
|
||||
/**
|
||||
* @var ILock|MockInterface
|
||||
*/
|
||||
private $lockMock;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
\Mockery::getConfiguration()->setConstantsMap([
|
||||
Mode::class => [
|
||||
'DBCONFIGAVAILABLE' => 0
|
||||
]
|
||||
]);
|
||||
|
||||
$this->appMode = \Mockery::mock(App\Mode::class);
|
||||
$this->appMode->shouldReceive('has')
|
||||
->andReturn(true);
|
||||
|
||||
$this->lockMock = \Mockery::mock(ILock::class);
|
||||
}
|
||||
|
||||
public function testList()
|
||||
{
|
||||
$this->lockMock
|
||||
->shouldReceive('getLocks')
|
||||
->andReturn(['test', 'test2'])
|
||||
->once();
|
||||
|
||||
$console = new Lock($this->appMode, $this->lockMock, $this->consoleArgv);
|
||||
$console->setArgument(0, 'list');
|
||||
$txt = $this->dumpExecute($console);
|
||||
$this->assertEquals("Listing all Locks:\ntest\ntest2\n2 locks found\n", $txt);
|
||||
}
|
||||
|
||||
public function testListPrefix()
|
||||
{
|
||||
$this->lockMock
|
||||
->shouldReceive('getLocks')
|
||||
->with('test')
|
||||
->andReturn(['test', 'test2'])
|
||||
->once();
|
||||
|
||||
$console = new Lock($this->appMode, $this->lockMock, $this->consoleArgv);
|
||||
$console->setArgument(0, 'list');
|
||||
$console->setArgument(1, 'test');
|
||||
$txt = $this->dumpExecute($console);
|
||||
$this->assertEquals("Listing all Locks starting with \"test\":\ntest\ntest2\n2 locks found\n", $txt);
|
||||
}
|
||||
|
||||
public function testDelLock()
|
||||
{
|
||||
$this->lockMock
|
||||
->shouldReceive('releaseLock')
|
||||
->with('test', true)
|
||||
->andReturn(true)
|
||||
->once();
|
||||
|
||||
$console = new Lock($this->appMode, $this->lockMock, $this->consoleArgv);
|
||||
$console->setArgument(0, 'del');
|
||||
$console->setArgument(1, 'test');
|
||||
$txt = $this->dumpExecute($console);
|
||||
$this->assertEquals("Lock 'test' released.\n", $txt);
|
||||
}
|
||||
|
||||
public function testDelUnknownLock()
|
||||
{
|
||||
$this->lockMock
|
||||
->shouldReceive('releaseLock')
|
||||
->with('test', true)
|
||||
->andReturn(false)
|
||||
->once();
|
||||
|
||||
$console = new Lock($this->appMode, $this->lockMock, $this->consoleArgv);
|
||||
$console->setArgument(0, 'del');
|
||||
$console->setArgument(1, 'test');
|
||||
$txt = $this->dumpExecute($console);
|
||||
$this->assertEquals("Couldn't release Lock 'test'\n", $txt);
|
||||
}
|
||||
|
||||
public function testSetLock()
|
||||
{
|
||||
$this->lockMock
|
||||
->shouldReceive('isLocked')
|
||||
->with('test')
|
||||
->andReturn(false)
|
||||
->once();
|
||||
$this->lockMock
|
||||
->shouldReceive('acquireLock')
|
||||
->with('test')
|
||||
->andReturn(true)
|
||||
->once();
|
||||
|
||||
$console = new Lock($this->appMode, $this->lockMock, $this->consoleArgv);
|
||||
$console->setArgument(0, 'set');
|
||||
$console->setArgument(1, 'test');
|
||||
$txt = $this->dumpExecute($console);
|
||||
$this->assertEquals("Lock 'test' acquired.\n", $txt);
|
||||
}
|
||||
|
||||
public function testSetLockIsLocked()
|
||||
{
|
||||
$this->lockMock
|
||||
->shouldReceive('isLocked')
|
||||
->with('test')
|
||||
->andReturn(true)
|
||||
->once();
|
||||
|
||||
$console = new Lock($this->appMode, $this->lockMock, $this->consoleArgv);
|
||||
$console->setArgument(0, 'set');
|
||||
$console->setArgument(1, 'test');
|
||||
$txt = $this->dumpExecute($console);
|
||||
$this->assertEquals("[Error] 'test' is already set.\n", $txt);
|
||||
}
|
||||
|
||||
public function testSetLockNotWorking()
|
||||
{
|
||||
$this->lockMock
|
||||
->shouldReceive('isLocked')
|
||||
->with('test')
|
||||
->andReturn(false)
|
||||
->once();
|
||||
$this->lockMock
|
||||
->shouldReceive('acquireLock')
|
||||
->with('test')
|
||||
->andReturn(false)
|
||||
->once();
|
||||
|
||||
$console = new Lock($this->appMode, $this->lockMock, $this->consoleArgv);
|
||||
$console->setArgument(0, 'set');
|
||||
$console->setArgument(1, 'test');
|
||||
$txt = $this->dumpExecute($console);
|
||||
$this->assertEquals("[Error] Unable to lock 'test'.\n", $txt);
|
||||
}
|
||||
|
||||
public function testReleaseAll()
|
||||
{
|
||||
$this->lockMock
|
||||
->shouldReceive('releaseAll')
|
||||
->andReturn(true)
|
||||
->once();
|
||||
|
||||
$console = new Lock($this->appMode, $this->lockMock, $this->consoleArgv);
|
||||
$console->setArgument(0, 'clear');
|
||||
$txt = $this->dumpExecute($console);
|
||||
$this->assertEquals("Locks successfully cleared.\n", $txt);
|
||||
}
|
||||
|
||||
public function testReleaseAllFailed()
|
||||
{
|
||||
$this->lockMock
|
||||
->shouldReceive('releaseAll')
|
||||
->andReturn(false)
|
||||
->once();
|
||||
|
||||
$console = new Lock($this->appMode, $this->lockMock, $this->consoleArgv);
|
||||
$console->setArgument(0, 'clear');
|
||||
$txt = $this->dumpExecute($console);
|
||||
$this->assertEquals("[Error] Unable to clear the locks.\n", $txt);
|
||||
}
|
||||
|
||||
public function testGetHelp()
|
||||
{
|
||||
// Usable to purposely fail if new commands are added without taking tests into account
|
||||
$theHelp = <<<HELP
|
||||
console lock - Manage node locks
|
||||
Synopsis
|
||||
bin/console lock list [<prefix>] [-h|--help|-?] [-v]
|
||||
bin/console lock set <lock> [<timeout> [<ttl>]] [-h|--help|-?] [-v]
|
||||
bin/console lock del <lock> [-h|--help|-?] [-v]
|
||||
bin/console lock clear [-h|--help|-?] [-v]
|
||||
|
||||
Description
|
||||
bin/console lock list [<prefix>]
|
||||
List all locks, optionally filtered by a prefix
|
||||
|
||||
bin/console lock set <lock> [<timeout> [<ttl>]]
|
||||
Sets manually a lock, optionally with the provided TTL (time to live) with a default of five minutes.
|
||||
|
||||
bin/console lock del <lock>
|
||||
Deletes a lock.
|
||||
|
||||
bin/console lock clear
|
||||
Clears all locks
|
||||
|
||||
Options
|
||||
-h|--help|-? Show help information
|
||||
-v Show more debug information.
|
||||
|
||||
HELP;
|
||||
$console = new Lock($this->appMode, $this->lockMock, [$this->consoleArgv]);
|
||||
$console->setOption('help', true);
|
||||
|
||||
$txt = $this->dumpExecute($console);
|
||||
|
||||
$this->assertEquals($txt, $theHelp);
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Friendica\Test\src\Core\Cache;
|
||||
|
||||
use Friendica\Core\Cache\MemcachedCache;
|
||||
use Friendica\Test\MockedTest;
|
||||
use Friendica\Util\PidFile;
|
||||
|
||||
|
@ -202,10 +201,6 @@ abstract class CacheTest extends MockedTest
|
|||
*/
|
||||
public function testGetAllKeys($value1, $value2, $value3)
|
||||
{
|
||||
if ($this->cache instanceof MemcachedCache) {
|
||||
$this->markTestSkipped('Memcached doesn\'t support getAllKeys anymore');
|
||||
}
|
||||
|
||||
$this->assertTrue($this->instance->set('value1', $value1));
|
||||
$this->assertTrue($this->instance->set('value2', $value2));
|
||||
$this->assertTrue($this->instance->set('test_value3', $value3));
|
||||
|
@ -219,5 +214,7 @@ abstract class CacheTest extends MockedTest
|
|||
$list = $this->instance->getAllKeys('test');
|
||||
|
||||
$this->assertContains('test_value3', $list);
|
||||
$this->assertNotContains('value1', $list);
|
||||
$this->assertNotContains('value2', $list);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,12 +23,12 @@ abstract class LockTest extends MockedTest
|
|||
parent::setUp();
|
||||
|
||||
$this->instance = $this->getInstance();
|
||||
$this->instance->releaseAll();
|
||||
$this->instance->releaseAll(true);
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
$this->instance->releaseAll();
|
||||
$this->instance->releaseAll(true);
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
|
@ -123,6 +123,46 @@ abstract class LockTest extends MockedTest
|
|||
$this->assertFalse($this->instance->isLocked('test'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @small
|
||||
*/
|
||||
public function testGetLocks()
|
||||
{
|
||||
$this->assertTrue($this->instance->acquireLock('foo', 1));
|
||||
$this->assertTrue($this->instance->acquireLock('bar', 1));
|
||||
$this->assertTrue($this->instance->acquireLock('nice', 1));
|
||||
|
||||
$this->assertTrue($this->instance->isLocked('foo'));
|
||||
$this->assertTrue($this->instance->isLocked('bar'));
|
||||
$this->assertTrue($this->instance->isLocked('nice'));
|
||||
|
||||
$locks = $this->instance->getLocks();
|
||||
|
||||
$this->assertContains('foo', $locks);
|
||||
$this->assertContains('bar', $locks);
|
||||
$this->assertContains('nice', $locks);
|
||||
}
|
||||
|
||||
/**
|
||||
* @small
|
||||
*/
|
||||
public function testGetLocksWithPrefix()
|
||||
{
|
||||
$this->assertTrue($this->instance->acquireLock('foo', 1));
|
||||
$this->assertTrue($this->instance->acquireLock('test1', 1));
|
||||
$this->assertTrue($this->instance->acquireLock('test2', 1));
|
||||
|
||||
$this->assertTrue($this->instance->isLocked('foo'));
|
||||
$this->assertTrue($this->instance->isLocked('test1'));
|
||||
$this->assertTrue($this->instance->isLocked('test2'));
|
||||
|
||||
$locks = $this->instance->getLocks('test');
|
||||
|
||||
$this->assertContains('test1', $locks);
|
||||
$this->assertContains('test2', $locks);
|
||||
$this->assertNotContains('foo', $locks);
|
||||
}
|
||||
|
||||
/**
|
||||
* @medium
|
||||
*/
|
||||
|
|
|
@ -12,8 +12,6 @@ class SemaphoreLockTest extends LockTest
|
|||
{
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$dice = \Mockery::mock(Dice::class)->makePartial();
|
||||
|
||||
$app = \Mockery::mock(App::class);
|
||||
|
@ -29,6 +27,8 @@ class SemaphoreLockTest extends LockTest
|
|||
|
||||
// @todo Because "get_temppath()" is using static methods, we have to initialize the BaseObject
|
||||
BaseObject::setDependencyInjection($dice);
|
||||
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
protected function getInstance()
|
||||
|
|
Loading…
Reference in a new issue