Merge pull request #15937 from owncloud/file-locking

Add memcache based shared/exclusive locking
This commit is contained in:
Thomas Müller 2015-05-01 17:47:23 +02:00
commit 6b691e3840
14 changed files with 891 additions and 23 deletions

View 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);
}
}
}

View file

@ -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;

View file

@ -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}
*/

View 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;
}
}
}

View file

@ -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');
}

View file

@ -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');
}

View file

@ -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
View 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);
}

View 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);
}

View 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;
}
}

View 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());
}
}
}

View 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();
}
}

View file

@ -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();

View 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'));
}
}