summaryrefslogtreecommitdiffstats
path: root/lib
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 /lib
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
Diffstat (limited to 'lib')
-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
10 files changed, 588 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;
+ }
+}