mirror of
https://github.com/nextcloud/server.git
synced 2026-06-11 01:30:50 -04:00
Merge pull request #15937 from owncloud/file-locking
Add memcache based shared/exclusive locking
This commit is contained in:
commit
6b691e3840
14 changed files with 891 additions and 23 deletions
86
lib/private/lock/memcachelockingprovider.php
Normal file
86
lib/private/lock/memcachelockingprovider.php
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
/**
|
||||
* @author Robin Appelman <icewind@owncloud.com>
|
||||
*
|
||||
* @copyright Copyright (c) 2015, ownCloud, Inc.
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OC\Lock;
|
||||
|
||||
use OCP\Lock\ILockingProvider;
|
||||
use OCP\Lock\LockedException;
|
||||
use OCP\IMemcache;
|
||||
|
||||
class MemcacheLockingProvider implements ILockingProvider {
|
||||
/**
|
||||
* @var \OCP\IMemcache
|
||||
*/
|
||||
private $memcache;
|
||||
|
||||
/**
|
||||
* @param \OCP\IMemcache $memcache
|
||||
*/
|
||||
public function __construct(IMemcache $memcache) {
|
||||
$this->memcache = $memcache;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE
|
||||
* @return bool
|
||||
*/
|
||||
public function isLocked($path, $type) {
|
||||
$lockValue = $this->memcache->get($path);
|
||||
if ($type === self::LOCK_SHARED) {
|
||||
return $lockValue > 0;
|
||||
} else if ($type === self::LOCK_EXCLUSIVE) {
|
||||
return $lockValue === 'exclusive';
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE
|
||||
* @throws \OCP\Lock\LockedException
|
||||
*/
|
||||
public function acquireLock($path, $type) {
|
||||
if ($type === self::LOCK_SHARED) {
|
||||
if (!$this->memcache->inc($path)) {
|
||||
throw new LockedException($path);
|
||||
}
|
||||
} else {
|
||||
$this->memcache->add($path, 0);
|
||||
if (!$this->memcache->cas($path, 0, 'exclusive')) {
|
||||
throw new LockedException($path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE
|
||||
*/
|
||||
public function releaseLock($path, $type) {
|
||||
if ($type === self::LOCK_SHARED) {
|
||||
$this->memcache->dec($path);
|
||||
} else if ($type === self::LOCK_EXCLUSIVE) {
|
||||
$this->memcache->cas($path, 'exclusive', 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,13 @@
|
|||
|
||||
namespace OC\Memcache;
|
||||
|
||||
class APC extends Cache {
|
||||
use OCP\IMemcache;
|
||||
|
||||
class APC extends Cache implements IMemcache {
|
||||
use CASTrait {
|
||||
cas as casEmulated;
|
||||
}
|
||||
|
||||
public function get($key) {
|
||||
$result = apc_fetch($this->getPrefix() . $key, $success);
|
||||
if (!$success) {
|
||||
|
|
@ -52,6 +58,58 @@ class APC extends Cache {
|
|||
return apc_delete($iter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value in the cache if it's not already stored
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param int $ttl Time To Live in seconds. Defaults to 60*60*24
|
||||
* @return bool
|
||||
*/
|
||||
public function add($key, $value, $ttl = 0) {
|
||||
return apc_add($this->getPrefix() . $key, $value, $ttl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase a stored number
|
||||
*
|
||||
* @param string $key
|
||||
* @param int $step
|
||||
* @return int | bool
|
||||
*/
|
||||
public function inc($key, $step = 1) {
|
||||
$this->add($key, 0);
|
||||
return apc_inc($this->getPrefix() . $key, $step);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrease a stored number
|
||||
*
|
||||
* @param string $key
|
||||
* @param int $step
|
||||
* @return int | bool
|
||||
*/
|
||||
public function dec($key, $step = 1) {
|
||||
return apc_dec($this->getPrefix() . $key, $step);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare and set
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $old
|
||||
* @param mixed $new
|
||||
* @return bool
|
||||
*/
|
||||
public function cas($key, $old, $new) {
|
||||
// apc only does cas for ints
|
||||
if (is_int($old) and is_int($new)) {
|
||||
return apc_cas($this->getPrefix() . $key, $old, $new);
|
||||
} else {
|
||||
return $this->casEmulated($key, $old, $new);
|
||||
}
|
||||
}
|
||||
|
||||
static public function isAvailable() {
|
||||
if (!extension_loaded('apc')) {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@
|
|||
|
||||
namespace OC\Memcache;
|
||||
|
||||
class ArrayCache extends Cache {
|
||||
use OCP\IMemcache;
|
||||
|
||||
class ArrayCache extends Cache implements IMemcache {
|
||||
/** @var array Array with the cached data */
|
||||
protected $cachedData = array();
|
||||
|
||||
|
|
@ -76,6 +78,74 @@ class ArrayCache extends Cache {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value in the cache if it's not already stored
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param int $ttl Time To Live in seconds. Defaults to 60*60*24
|
||||
* @return bool
|
||||
*/
|
||||
public function add($key, $value, $ttl = 0) {
|
||||
// since this cache is not shared race conditions aren't an issue
|
||||
if ($this->hasKey($key)) {
|
||||
return false;
|
||||
} else {
|
||||
return $this->set($key, $value, $ttl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase a stored number
|
||||
*
|
||||
* @param string $key
|
||||
* @param int $step
|
||||
* @return int | bool
|
||||
*/
|
||||
public function inc($key, $step = 1) {
|
||||
$oldValue = $this->get($key);
|
||||
if (is_int($oldValue)) {
|
||||
$this->set($key, $oldValue + $step);
|
||||
return $oldValue + $step;
|
||||
} else {
|
||||
$success = $this->add($key, $step);
|
||||
return ($success) ? $step : false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrease a stored number
|
||||
*
|
||||
* @param string $key
|
||||
* @param int $step
|
||||
* @return int | bool
|
||||
*/
|
||||
public function dec($key, $step = 1) {
|
||||
$oldValue = $this->get($key);
|
||||
if (is_int($oldValue)) {
|
||||
$this->set($key, $oldValue - $step);
|
||||
return $oldValue - $step;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare and set
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $old
|
||||
* @param mixed $new
|
||||
* @return bool
|
||||
*/
|
||||
public function cas($key, $old, $new) {
|
||||
if ($this->get($key) === $old) {
|
||||
return $this->set($key, $new);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
|
|
|||
56
lib/private/memcache/castrait.php
Normal file
56
lib/private/memcache/castrait.php
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
/**
|
||||
* @author Robin Appelman <icewind@owncloud.com>
|
||||
*
|
||||
* @copyright Copyright (c) 2015, ownCloud, Inc.
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OC\Memcache;
|
||||
|
||||
trait CASTrait {
|
||||
abstract public function get($key);
|
||||
|
||||
abstract public function set($key, $value, $ttl = 0);
|
||||
|
||||
abstract public function remove($key);
|
||||
|
||||
abstract public function add($key, $value, $ttl = 0);
|
||||
|
||||
/**
|
||||
* Compare and set
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $old
|
||||
* @param mixed $new
|
||||
* @return bool
|
||||
*/
|
||||
public function cas($key, $old, $new) {
|
||||
//no native cas, emulate with locking
|
||||
if ($this->add($key . '_lock', true)) {
|
||||
if ($this->get($key) === $old) {
|
||||
$this->set($key, $new);
|
||||
$this->remove($key . '_lock');
|
||||
return true;
|
||||
} else {
|
||||
$this->remove($key . '_lock');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,11 @@
|
|||
|
||||
namespace OC\Memcache;
|
||||
|
||||
class Memcached extends Cache {
|
||||
use OCP\IMemcache;
|
||||
|
||||
class Memcached extends Cache implements IMemcache {
|
||||
use CASTrait;
|
||||
|
||||
/**
|
||||
* @var \Memcached $cache
|
||||
*/
|
||||
|
|
@ -100,6 +104,41 @@ class Memcached extends Cache {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value in the cache if it's not already stored
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param int $ttl Time To Live in seconds. Defaults to 60*60*24
|
||||
* @return bool
|
||||
*/
|
||||
public function add($key, $value, $ttl = 0) {
|
||||
return self::$cache->add($this->getPrefix() . $key, $value, $ttl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase a stored number
|
||||
*
|
||||
* @param string $key
|
||||
* @param int $step
|
||||
* @return int | bool
|
||||
*/
|
||||
public function inc($key, $step = 1) {
|
||||
$this->add($key, 0);
|
||||
return self::$cache->increment($this->getPrefix() . $key, $step);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrease a stored number
|
||||
*
|
||||
* @param string $key
|
||||
* @param int $step
|
||||
* @return int | bool
|
||||
*/
|
||||
public function dec($key, $step = 1) {
|
||||
return self::$cache->decrement($this->getPrefix() . $key, $step);
|
||||
}
|
||||
|
||||
static public function isAvailable() {
|
||||
return extension_loaded('memcached');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,10 @@
|
|||
|
||||
namespace OC\Memcache;
|
||||
|
||||
class Redis extends Cache {
|
||||
use OCP\IMemcache;
|
||||
|
||||
class Redis extends Cache implements IMemcache {
|
||||
use CASTrait;
|
||||
|
||||
/**
|
||||
* @var \Redis $cache
|
||||
|
|
@ -52,10 +55,10 @@ class Redis extends Cache {
|
|||
$timeout = 0.0; // unlimited
|
||||
}
|
||||
|
||||
self::$cache->connect( $host, $port, $timeout );
|
||||
self::$cache->connect($host, $port, $timeout);
|
||||
|
||||
if (isset($config['dbindex'])) {
|
||||
self::$cache->select( $config['dbindex'] );
|
||||
self::$cache->select($config['dbindex']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -94,19 +97,59 @@ class Redis extends Cache {
|
|||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function clear($prefix = '') {
|
||||
$prefix = $this->getNamespace() . $prefix.'*';
|
||||
$prefix = $this->getNamespace() . $prefix . '*';
|
||||
$it = null;
|
||||
self::$cache->setOption(\Redis::OPT_SCAN, \Redis::SCAN_RETRY);
|
||||
while($keys = self::$cache->scan($it, $prefix)) {
|
||||
while ($keys = self::$cache->scan($it, $prefix)) {
|
||||
self::$cache->delete($keys);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value in the cache if it's not already stored
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param int $ttl Time To Live in seconds. Defaults to 60*60*24
|
||||
* @return bool
|
||||
*/
|
||||
public function add($key, $value, $ttl = 0) {
|
||||
// dont encode ints for inc/dec
|
||||
if (!is_int($value)) {
|
||||
$value = json_encode($value);
|
||||
}
|
||||
return self::$cache->setnx($this->getPrefix() . $key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase a stored number
|
||||
*
|
||||
* @param string $key
|
||||
* @param int $step
|
||||
* @return int | bool
|
||||
*/
|
||||
public function inc($key, $step = 1) {
|
||||
return self::$cache->incrBy($this->getNamespace() . $key, $step);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrease a stored number
|
||||
*
|
||||
* @param string $key
|
||||
* @param int $step
|
||||
* @return int | bool
|
||||
*/
|
||||
public function dec($key, $step = 1) {
|
||||
if (!$this->hasKey($key)) {
|
||||
return false;
|
||||
}
|
||||
return self::$cache->decrBy($this->getNamespace() . $key, $step);
|
||||
}
|
||||
|
||||
static public function isAvailable() {
|
||||
return extension_loaded('redis');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,11 +25,15 @@
|
|||
|
||||
namespace OC\Memcache;
|
||||
|
||||
use OCP\IMemcache;
|
||||
|
||||
/**
|
||||
* See http://xcache.lighttpd.net/wiki/XcacheApi for provided constants and
|
||||
* functions etc.
|
||||
*/
|
||||
class XCache extends Cache {
|
||||
class XCache extends Cache implements IMemcache {
|
||||
use CASTrait;
|
||||
|
||||
/**
|
||||
* entries in XCache gets namespaced to prevent collisions between ownCloud instances and users
|
||||
*/
|
||||
|
|
@ -38,28 +42,28 @@ class XCache extends Cache {
|
|||
}
|
||||
|
||||
public function get($key) {
|
||||
return xcache_get($this->getNamespace().$key);
|
||||
return xcache_get($this->getNamespace() . $key);
|
||||
}
|
||||
|
||||
public function set($key, $value, $ttl=0) {
|
||||
if($ttl>0) {
|
||||
return xcache_set($this->getNamespace().$key, $value, $ttl);
|
||||
}else{
|
||||
return xcache_set($this->getNamespace().$key, $value);
|
||||
public function set($key, $value, $ttl = 0) {
|
||||
if ($ttl > 0) {
|
||||
return xcache_set($this->getNamespace() . $key, $value, $ttl);
|
||||
} else {
|
||||
return xcache_set($this->getNamespace() . $key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
public function hasKey($key) {
|
||||
return xcache_isset($this->getNamespace().$key);
|
||||
return xcache_isset($this->getNamespace() . $key);
|
||||
}
|
||||
|
||||
public function remove($key) {
|
||||
return xcache_unset($this->getNamespace().$key);
|
||||
return xcache_unset($this->getNamespace() . $key);
|
||||
}
|
||||
|
||||
public function clear($prefix='') {
|
||||
public function clear($prefix = '') {
|
||||
if (function_exists('xcache_unset_by_prefix')) {
|
||||
return xcache_unset_by_prefix($this->getNamespace().$prefix);
|
||||
return xcache_unset_by_prefix($this->getNamespace() . $prefix);
|
||||
} else {
|
||||
// Since we can not clear by prefix, we just clear the whole cache.
|
||||
xcache_clear_cache(\XC_TYPE_VAR, 0);
|
||||
|
|
@ -67,11 +71,49 @@ class XCache extends Cache {
|
|||
return true;
|
||||
}
|
||||
|
||||
static public function isAvailable(){
|
||||
/**
|
||||
* Set a value in the cache if it's not already stored
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param int $ttl Time To Live in seconds. Defaults to 60*60*24
|
||||
* @return bool
|
||||
*/
|
||||
public function add($key, $value, $ttl = 0) {
|
||||
if ($this->hasKey($key)) {
|
||||
return false;
|
||||
} else {
|
||||
return $this->set($key, $value, $ttl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase a stored number
|
||||
*
|
||||
* @param string $key
|
||||
* @param int $step
|
||||
* @return int | bool
|
||||
*/
|
||||
public function inc($key, $step = 1) {
|
||||
return xcache_inc($this->getPrefix() . $key, $step);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrease a stored number
|
||||
*
|
||||
* @param string $key
|
||||
* @param int $step
|
||||
* @return int | bool
|
||||
*/
|
||||
public function dec($key, $step = 1) {
|
||||
return xcache_dec($this->getPrefix() . $key, $step);
|
||||
}
|
||||
|
||||
static public function isAvailable() {
|
||||
if (!extension_loaded('xcache')) {
|
||||
return false;
|
||||
}
|
||||
if (\OC::$CLI) {
|
||||
if (\OC::$CLI && !getenv('XCACHE_TEST')) {
|
||||
return false;
|
||||
}
|
||||
if (!function_exists('xcache_unset_by_prefix') && ini_get('xcache.admin.enable_auth')) {
|
||||
|
|
@ -80,7 +122,7 @@ class XCache extends Cache {
|
|||
// AND administration functions are password-protected.
|
||||
return false;
|
||||
}
|
||||
$var_size = (int) ini_get('xcache.var_size');
|
||||
$var_size = (int)ini_get('xcache.var_size');
|
||||
if (!$var_size) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
78
lib/public/imemcache.php
Normal file
78
lib/public/imemcache.php
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
/**
|
||||
* @author Robin Appelman <icewind@owncloud.com>
|
||||
*
|
||||
* @copyright Copyright (c) 2015, ownCloud, Inc.
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Public interface of ownCloud for apps to use.
|
||||
* Cache interface
|
||||
*
|
||||
*/
|
||||
|
||||
// use OCP namespace for all classes that are considered public.
|
||||
// This means that they should be used by apps instead of the internal ownCloud classes
|
||||
namespace OCP;
|
||||
|
||||
/**
|
||||
* This interface defines method for accessing the file based user cache.
|
||||
*
|
||||
* @since 8.1.0
|
||||
*/
|
||||
interface IMemcache extends ICache {
|
||||
/**
|
||||
* Set a value in the cache if it's not already stored
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param int $ttl Time To Live in seconds. Defaults to 60*60*24
|
||||
* @return bool
|
||||
* @since 8.0.0
|
||||
*/
|
||||
public function add($key, $value, $ttl = 0);
|
||||
|
||||
/**
|
||||
* Increase a stored number
|
||||
*
|
||||
* @param string $key
|
||||
* @param int $step
|
||||
* @return int | bool
|
||||
* @since 8.0.0
|
||||
*/
|
||||
public function inc($key, $step = 1);
|
||||
|
||||
/**
|
||||
* Decrease a stored number
|
||||
*
|
||||
* @param string $key
|
||||
* @param int $step
|
||||
* @return int | bool
|
||||
* @since 8.0.0
|
||||
*/
|
||||
public function dec($key, $step = 1);
|
||||
|
||||
/**
|
||||
* Compare and set
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $old
|
||||
* @param mixed $new
|
||||
* @return bool
|
||||
*/
|
||||
public function cas($key, $old, $new);
|
||||
}
|
||||
47
lib/public/lock/ilockingprovider.php
Normal file
47
lib/public/lock/ilockingprovider.php
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
/**
|
||||
* @author Robin Appelman <icewind@owncloud.com>
|
||||
*
|
||||
* @copyright Copyright (c) 2015, ownCloud, Inc.
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCP\Lock;
|
||||
|
||||
interface ILockingProvider {
|
||||
const LOCK_SHARED = 1;
|
||||
const LOCK_EXCLUSIVE = 2;
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE
|
||||
* @return bool
|
||||
*/
|
||||
public function isLocked($path, $type);
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE
|
||||
* @throws \OCP\Files\Lock\LockedException
|
||||
*/
|
||||
public function acquireLock($path, $type);
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE
|
||||
*/
|
||||
public function releaseLock($path, $type);
|
||||
}
|
||||
46
lib/public/lock/lockedexception.php
Normal file
46
lib/public/lock/lockedexception.php
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
/**
|
||||
* @author Robin Appelman <icewind@owncloud.com>
|
||||
*
|
||||
* @copyright Copyright (c) 2015, ownCloud, Inc.
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCP\Lock;
|
||||
|
||||
class LockedException extends \Exception {
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $path;
|
||||
|
||||
/**
|
||||
* LockedException constructor.
|
||||
*
|
||||
* @param string $path
|
||||
*/
|
||||
public function __construct($path) {
|
||||
parent::__construct($path . ' is locked');
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPath() {
|
||||
return $this->path;
|
||||
}
|
||||
}
|
||||
137
tests/lib/lock/lockingprovider.php
Normal file
137
tests/lib/lock/lockingprovider.php
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
<?php
|
||||
/**
|
||||
* @author Robin Appelman <icewind@owncloud.com>
|
||||
*
|
||||
* @copyright Copyright (c) 2015, ownCloud, Inc.
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Test\Lock;
|
||||
|
||||
use OCP\Lock\ILockingProvider;
|
||||
use OCP\Lock\LockedException;
|
||||
use Test\TestCase;
|
||||
|
||||
abstract class LockingProvider extends TestCase {
|
||||
/**
|
||||
* @var \OCP\Lock\ILockingProvider
|
||||
*/
|
||||
protected $instance;
|
||||
|
||||
/**
|
||||
* @return \OCP\Lock\ILockingProvider
|
||||
*/
|
||||
abstract protected function getInstance();
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->instance = $this->getInstance();
|
||||
}
|
||||
|
||||
public function testExclusiveLock() {
|
||||
$this->instance->acquireLock('foo', ILockingProvider::LOCK_EXCLUSIVE);
|
||||
$this->assertTrue($this->instance->isLocked('foo', ILockingProvider::LOCK_EXCLUSIVE));
|
||||
$this->assertFalse($this->instance->isLocked('foo', ILockingProvider::LOCK_SHARED));
|
||||
}
|
||||
|
||||
public function testSharedLock() {
|
||||
$this->instance->acquireLock('foo', ILockingProvider::LOCK_SHARED);
|
||||
$this->assertFalse($this->instance->isLocked('foo', ILockingProvider::LOCK_EXCLUSIVE));
|
||||
$this->assertTrue($this->instance->isLocked('foo', ILockingProvider::LOCK_SHARED));
|
||||
}
|
||||
|
||||
public function testDoubleSharedLock() {
|
||||
$this->instance->acquireLock('foo', ILockingProvider::LOCK_SHARED);
|
||||
$this->assertFalse($this->instance->isLocked('foo', ILockingProvider::LOCK_EXCLUSIVE));
|
||||
$this->assertTrue($this->instance->isLocked('foo', ILockingProvider::LOCK_SHARED));
|
||||
$this->instance->acquireLock('foo', ILockingProvider::LOCK_SHARED);
|
||||
$this->assertTrue($this->instance->isLocked('foo', ILockingProvider::LOCK_SHARED));
|
||||
}
|
||||
|
||||
public function testReleaseSharedLock() {
|
||||
$this->instance->acquireLock('foo', ILockingProvider::LOCK_SHARED);
|
||||
$this->assertFalse($this->instance->isLocked('foo', ILockingProvider::LOCK_EXCLUSIVE));
|
||||
$this->assertTrue($this->instance->isLocked('foo', ILockingProvider::LOCK_SHARED));
|
||||
$this->instance->acquireLock('foo', ILockingProvider::LOCK_SHARED);
|
||||
$this->assertTrue($this->instance->isLocked('foo', ILockingProvider::LOCK_SHARED));
|
||||
$this->instance->releaseLock('foo', ILockingProvider::LOCK_SHARED);
|
||||
$this->assertTrue($this->instance->isLocked('foo', ILockingProvider::LOCK_SHARED));
|
||||
$this->instance->releaseLock('foo', ILockingProvider::LOCK_SHARED);
|
||||
$this->assertFalse($this->instance->isLocked('foo', ILockingProvider::LOCK_SHARED));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \OCP\Lock\LockedException
|
||||
*/
|
||||
public function testDoubleExclusiveLock() {
|
||||
$this->instance->acquireLock('foo', ILockingProvider::LOCK_EXCLUSIVE);
|
||||
$this->assertTrue($this->instance->isLocked('foo', ILockingProvider::LOCK_EXCLUSIVE));
|
||||
$this->instance->acquireLock('foo', ILockingProvider::LOCK_EXCLUSIVE);
|
||||
}
|
||||
|
||||
public function testReleaseExclusiveLock() {
|
||||
$this->instance->acquireLock('foo', ILockingProvider::LOCK_EXCLUSIVE);
|
||||
$this->assertTrue($this->instance->isLocked('foo', ILockingProvider::LOCK_EXCLUSIVE));
|
||||
$this->instance->releaseLock('foo', ILockingProvider::LOCK_EXCLUSIVE);
|
||||
$this->assertFalse($this->instance->isLocked('foo', ILockingProvider::LOCK_EXCLUSIVE));
|
||||
$this->instance->acquireLock('foo', ILockingProvider::LOCK_EXCLUSIVE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \OCP\Lock\LockedException
|
||||
*/
|
||||
public function testExclusiveLockAfterShared() {
|
||||
$this->instance->acquireLock('foo', ILockingProvider::LOCK_SHARED);
|
||||
$this->assertTrue($this->instance->isLocked('foo', ILockingProvider::LOCK_SHARED));
|
||||
$this->instance->acquireLock('foo', ILockingProvider::LOCK_EXCLUSIVE);
|
||||
}
|
||||
|
||||
public function testExclusiveLockAfterSharedReleased() {
|
||||
$this->instance->acquireLock('foo', ILockingProvider::LOCK_SHARED);
|
||||
$this->assertTrue($this->instance->isLocked('foo', ILockingProvider::LOCK_SHARED));
|
||||
$this->instance->releaseLock('foo', ILockingProvider::LOCK_SHARED);
|
||||
$this->instance->acquireLock('foo', ILockingProvider::LOCK_EXCLUSIVE);
|
||||
$this->assertTrue($this->instance->isLocked('foo', ILockingProvider::LOCK_EXCLUSIVE));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @expectedException \OCP\Lock\LockedException
|
||||
*/
|
||||
public function testSharedLockAfterExclusive() {
|
||||
$this->instance->acquireLock('foo', ILockingProvider::LOCK_EXCLUSIVE);
|
||||
$this->assertTrue($this->instance->isLocked('foo', ILockingProvider::LOCK_EXCLUSIVE));
|
||||
$this->instance->acquireLock('foo', ILockingProvider::LOCK_SHARED);
|
||||
}
|
||||
|
||||
public function testLockedExceptionHasPathForShared() {
|
||||
try {
|
||||
$this->testSharedLockAfterExclusive();
|
||||
$this->fail('Expected locked exception');
|
||||
} catch (LockedException $e) {
|
||||
$this->assertEquals('foo', $e->getPath());
|
||||
}
|
||||
}
|
||||
|
||||
public function testLockedExceptionHasPathForExclusive() {
|
||||
try {
|
||||
$this->testExclusiveLockAfterShared();
|
||||
$this->fail('Expected locked exception');
|
||||
} catch (LockedException $e) {
|
||||
$this->assertEquals('foo', $e->getPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
45
tests/lib/lock/memcachelockingprovider.php
Normal file
45
tests/lib/lock/memcachelockingprovider.php
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
/**
|
||||
* @author Robin Appelman <icewind@owncloud.com>
|
||||
*
|
||||
* @copyright Copyright (c) 2015, ownCloud, Inc.
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Test\Lock;
|
||||
|
||||
use OC\Memcache\ArrayCache;
|
||||
|
||||
class MemcacheLockingProvider extends LockingProvider {
|
||||
|
||||
/**
|
||||
* @var \OCP\IMemcache
|
||||
*/
|
||||
private $memcache;
|
||||
|
||||
/**
|
||||
* @return \OCP\Lock\ILockingProvider
|
||||
*/
|
||||
protected function getInstance() {
|
||||
$this->memcache = new ArrayCache();
|
||||
return new \OC\Lock\MemcacheLockingProvider($this->memcache);
|
||||
}
|
||||
|
||||
public function tearDown() {
|
||||
$this->memcache->clear();
|
||||
parent::tearDown();
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,11 @@
|
|||
namespace Test\Memcache;
|
||||
|
||||
abstract class Cache extends \Test_Cache {
|
||||
/**
|
||||
* @var \OCP\IMemcache cache;
|
||||
*/
|
||||
protected $instance;
|
||||
|
||||
public function testExistsAfterSet() {
|
||||
$this->assertFalse($this->instance->hasKey('foo'));
|
||||
$this->instance->set('foo', 'bar');
|
||||
|
|
@ -56,6 +61,49 @@ abstract class Cache extends \Test_Cache {
|
|||
$this->assertFalse($this->instance->hasKey('foo'));
|
||||
}
|
||||
|
||||
public function testAdd() {
|
||||
$this->assertTrue($this->instance->add('foo', 'bar'));
|
||||
$this->assertEquals('bar', $this->instance->get('foo'));
|
||||
$this->assertFalse($this->instance->add('foo', 'asd'));
|
||||
$this->assertEquals('bar', $this->instance->get('foo'));
|
||||
}
|
||||
|
||||
public function testInc() {
|
||||
$this->assertEquals(1, $this->instance->inc('foo'));
|
||||
$this->assertEquals(1, $this->instance->get('foo'));
|
||||
$this->assertEquals(2, $this->instance->inc('foo'));
|
||||
$this->assertEquals(12, $this->instance->inc('foo', 10));
|
||||
|
||||
$this->instance->set('foo', 'bar');
|
||||
$this->assertFalse($this->instance->inc('foo'));
|
||||
$this->assertEquals('bar', $this->instance->get('foo'));
|
||||
}
|
||||
|
||||
public function testDec() {
|
||||
$this->assertEquals(false, $this->instance->dec('foo'));
|
||||
$this->instance->set('foo', 20);
|
||||
$this->assertEquals(19, $this->instance->dec('foo'));
|
||||
$this->assertEquals(19, $this->instance->get('foo'));
|
||||
$this->assertEquals(9, $this->instance->dec('foo', 10));
|
||||
|
||||
$this->instance->set('foo', 'bar');
|
||||
$this->assertFalse($this->instance->dec('foo'));
|
||||
$this->assertEquals('bar', $this->instance->get('foo'));
|
||||
}
|
||||
|
||||
public function testCasNotChanged() {
|
||||
$this->instance->set('foo', 'bar');
|
||||
$this->assertTrue($this->instance->cas('foo', 'bar', 'asd'));
|
||||
$this->assertEquals('asd', $this->instance->get('foo'));
|
||||
}
|
||||
|
||||
public function testCasChanged() {
|
||||
$this->instance->set('foo', 'bar1');
|
||||
$this->assertFalse($this->instance->cas('foo', 'bar', 'asd'));
|
||||
$this->assertEquals('bar1', $this->instance->get('foo'));
|
||||
}
|
||||
|
||||
|
||||
protected function tearDown() {
|
||||
if ($this->instance) {
|
||||
$this->instance->clear();
|
||||
|
|
|
|||
73
tests/lib/memcache/castrait.php
Normal file
73
tests/lib/memcache/castrait.php
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
/**
|
||||
* @author Robin Appelman <icewind@owncloud.com>
|
||||
*
|
||||
* @copyright Copyright (c) 2015, ownCloud, Inc.
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Test\Memcache;
|
||||
|
||||
use Test\TestCase;
|
||||
|
||||
class CasTrait extends TestCase {
|
||||
/**
|
||||
* @return \OC\Memcache\CasTrait
|
||||
*/
|
||||
private function getCache() {
|
||||
$sourceCache = new \OC\Memcache\ArrayCache();
|
||||
$mock = $this->getMockForTrait('\OC\Memcache\CasTrait');
|
||||
|
||||
$mock->expects($this->any())
|
||||
->method('set')
|
||||
->will($this->returnCallback(function ($key, $value, $ttl) use ($sourceCache) {
|
||||
return $sourceCache->set($key, $value, $ttl);
|
||||
}));
|
||||
|
||||
$mock->expects($this->any())
|
||||
->method('get')
|
||||
->will($this->returnCallback(function ($key) use ($sourceCache) {
|
||||
return $sourceCache->get($key);
|
||||
}));
|
||||
|
||||
$mock->expects($this->any())
|
||||
->method('add')
|
||||
->will($this->returnCallback(function ($key, $value, $ttl) use ($sourceCache) {
|
||||
return $sourceCache->add($key, $value, $ttl);
|
||||
}));
|
||||
|
||||
$mock->expects($this->any())
|
||||
->method('remove')
|
||||
->will($this->returnCallback(function ($key) use ($sourceCache) {
|
||||
return $sourceCache->remove($key);
|
||||
}));
|
||||
return $mock;
|
||||
}
|
||||
|
||||
public function testCasNotChanged() {
|
||||
$cache = $this->getCache();
|
||||
$cache->set('foo', 'bar');
|
||||
$this->assertTrue($cache->cas('foo', 'bar', 'asd'));
|
||||
$this->assertEquals('asd', $cache->get('foo'));
|
||||
}
|
||||
|
||||
public function testCasChanged() {
|
||||
$cache = $this->getCache();
|
||||
$cache->set('foo', 'bar1');
|
||||
$this->assertFalse($cache->cas('foo', 'bar', 'asd'));
|
||||
$this->assertEquals('bar1', $cache->get('foo'));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue