summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Müller <thomas.mueller@tmit.eu>2015-05-01 17:47:23 +0200
committerThomas Müller <thomas.mueller@tmit.eu>2015-05-01 17:47:23 +0200
commit6b691e3840ca5f536b1da9bf22cb90bb27968399 (patch)
treef040df1e000e6133463507b54c518162309cb7e3
parenteda4d4583645b49bd1e582f7396603ce6795e919 (diff)
parentba7d221cffab4b42871e5926dd6c3e0a2d2b98dc (diff)
downloadnextcloud-server-6b691e3840ca5f536b1da9bf22cb90bb27968399.tar.gz
nextcloud-server-6b691e3840ca5f536b1da9bf22cb90bb27968399.zip
Merge pull request #15937 from owncloud/file-locking
Add memcache based shared/exclusive locking
-rw-r--r--lib/private/lock/memcachelockingprovider.php86
-rw-r--r--lib/private/memcache/apc.php60
-rw-r--r--lib/private/memcache/arraycache.php72
-rw-r--r--lib/private/memcache/castrait.php56
-rw-r--r--lib/private/memcache/memcached.php41
-rw-r--r--lib/private/memcache/redis.php55
-rw-r--r--lib/private/memcache/xcache.php70
-rw-r--r--lib/public/imemcache.php78
-rw-r--r--lib/public/lock/ilockingprovider.php47
-rw-r--r--lib/public/lock/lockedexception.php46
-rw-r--r--tests/lib/lock/lockingprovider.php137
-rw-r--r--tests/lib/lock/memcachelockingprovider.php45
-rw-r--r--tests/lib/memcache/cache.php48
-rw-r--r--tests/lib/memcache/castrait.php73
14 files changed, 891 insertions, 23 deletions
diff --git a/lib/private/lock/memcachelockingprovider.php b/lib/private/lock/memcachelockingprovider.php
new file mode 100644
index 00000000000..9c8c7235462
--- /dev/null
+++ b/lib/private/lock/memcachelockingprovider.php
@@ -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);
+ }
+ }
+}
diff --git a/lib/private/memcache/apc.php b/lib/private/memcache/apc.php
index b8b2b608124..50b942e7297 100644
--- a/lib/private/memcache/apc.php
+++ b/lib/private/memcache/apc.php
@@ -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;
diff --git a/lib/private/memcache/arraycache.php b/lib/private/memcache/arraycache.php
index 939472dc518..2b1b87a9eb3 100644
--- a/lib/private/memcache/arraycache.php
+++ b/lib/private/memcache/arraycache.php
@@ -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();
@@ -77,6 +79,74 @@ class ArrayCache extends Cache {
}
/**
+ * 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}
*/
static public function isAvailable() {
diff --git a/lib/private/memcache/castrait.php b/lib/private/memcache/castrait.php
new file mode 100644
index 00000000000..c52538023fb
--- /dev/null
+++ b/lib/private/memcache/castrait.php
@@ -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;
+ }
+ }
+}
diff --git a/lib/private/memcache/memcached.php b/lib/private/memcache/memcached.php
index a2b3440317f..cf1d651b551 100644
--- a/lib/private/memcache/memcached.php
+++ b/lib/private/memcache/memcached.php
@@ -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');
}
diff --git a/lib/private/memcache/redis.php b/lib/private/memcache/redis.php
index e7425726b2b..78d061404ef 100644
--- a/lib/private/memcache/redis.php
+++ b/lib/private/memcache/redis.php
@@ -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');
}
diff --git a/lib/private/memcache/xcache.php b/lib/private/memcache/xcache.php
index 33cea23e62b..0be79d06ed9 100644
--- a/lib/private/memcache/xcache.php
+++ b/lib/private/memcache/xcache.php
@@ -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;
}
diff --git a/lib/public/imemcache.php b/lib/public/imemcache.php
new file mode 100644
index 00000000000..56a33c9572f
--- /dev/null
+++ b/lib/public/imemcache.php
@@ -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);
+}
diff --git a/lib/public/lock/ilockingprovider.php b/lib/public/lock/ilockingprovider.php
new file mode 100644
index 00000000000..a584ec02ef6
--- /dev/null
+++ b/lib/public/lock/ilockingprovider.php
@@ -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);
+}
diff --git a/lib/public/lock/lockedexception.php b/lib/public/lock/lockedexception.php
new file mode 100644
index 00000000000..87f7164b7e0
--- /dev/null
+++ b/lib/public/lock/lockedexception.php
@@ -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;
+ }
+}
diff --git a/tests/lib/lock/lockingprovider.php b/tests/lib/lock/lockingprovider.php
new file mode 100644
index 00000000000..08d879da8bb
--- /dev/null
+++ b/tests/lib/lock/lockingprovider.php
@@ -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());
+ }
+ }
+}
diff --git a/tests/lib/lock/memcachelockingprovider.php b/tests/lib/lock/memcachelockingprovider.php
new file mode 100644
index 00000000000..40478b22939
--- /dev/null
+++ b/tests/lib/lock/memcachelockingprovider.php
@@ -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();
+ }
+}
diff --git a/tests/lib/memcache/cache.php b/tests/lib/memcache/cache.php
index e5ceae52fb0..9d977cf0247 100644
--- a/tests/lib/memcache/cache.php
+++ b/tests/lib/memcache/cache.php
@@ -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();
diff --git a/tests/lib/memcache/castrait.php b/tests/lib/memcache/castrait.php
new file mode 100644
index 00000000000..17f412bb638
--- /dev/null
+++ b/tests/lib/memcache/castrait.php
@@ -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'));
+ }
+}