diff options
author | Roeland Jago Douma <rullzer@owncloud.com> | 2016-04-14 08:28:16 +0200 |
---|---|---|
committer | Roeland Jago Douma <rullzer@owncloud.com> | 2016-04-14 08:28:16 +0200 |
commit | 86e757d2b3d7cc76bfa5bd22b5b06e7f963cda4f (patch) | |
tree | 2349f3d182d2120d9b5f602865d81583fc300e56 /lib/private/Lock | |
parent | 54f6c05c79a1d01c32c016477c6ae2220e754b13 (diff) | |
download | nextcloud-server-86e757d2b3d7cc76bfa5bd22b5b06e7f963cda4f.tar.gz nextcloud-server-86e757d2b3d7cc76bfa5bd22b5b06e7f963cda4f.zip |
Move \OC\Lock to PSR-4
Diffstat (limited to 'lib/private/Lock')
-rw-r--r-- | lib/private/Lock/AbstractLockingProvider.php | 119 | ||||
-rw-r--r-- | lib/private/Lock/DBLockingProvider.php | 269 | ||||
-rw-r--r-- | lib/private/Lock/MemcacheLockingProvider.php | 122 | ||||
-rw-r--r-- | lib/private/Lock/NoopLockingProvider.php | 68 |
4 files changed, 578 insertions, 0 deletions
diff --git a/lib/private/Lock/AbstractLockingProvider.php b/lib/private/Lock/AbstractLockingProvider.php new file mode 100644 index 00000000000..f96358778c1 --- /dev/null +++ b/lib/private/Lock/AbstractLockingProvider.php @@ -0,0 +1,119 @@ +<?php +/** + * @author Robin Appelman <icewind@owncloud.com> + * + * @copyright Copyright (c) 2016, 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; + +/** + * Base locking provider that keeps track of locks acquired during the current request + * to release any left over locks at the end of the request + */ +abstract class AbstractLockingProvider implements ILockingProvider { + protected $ttl; // how long until we clear stray locks in seconds + + protected $acquiredLocks = [ + 'shared' => [], + 'exclusive' => [] + ]; + + /** + * Check if we've locally acquired a lock + * + * @param string $path + * @param int $type + * @return bool + */ + protected function hasAcquiredLock($path, $type) { + if ($type === self::LOCK_SHARED) { + return isset($this->acquiredLocks['shared'][$path]) && $this->acquiredLocks['shared'][$path] > 0; + } else { + return isset($this->acquiredLocks['exclusive'][$path]) && $this->acquiredLocks['exclusive'][$path] === true; + } + } + + /** + * Mark a locally acquired lock + * + * @param string $path + * @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE + */ + protected function markAcquire($path, $type) { + if ($type === self::LOCK_SHARED) { + if (!isset($this->acquiredLocks['shared'][$path])) { + $this->acquiredLocks['shared'][$path] = 0; + } + $this->acquiredLocks['shared'][$path]++; + } else { + $this->acquiredLocks['exclusive'][$path] = true; + } + } + + /** + * Mark a release of a locally acquired lock + * + * @param string $path + * @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE + */ + protected function markRelease($path, $type) { + if ($type === self::LOCK_SHARED) { + if (isset($this->acquiredLocks['shared'][$path]) and $this->acquiredLocks['shared'][$path] > 0) { + $this->acquiredLocks['shared'][$path]--; + } + } else if ($type === self::LOCK_EXCLUSIVE) { + unset($this->acquiredLocks['exclusive'][$path]); + } + } + + /** + * Change the type of an existing tracked lock + * + * @param string $path + * @param int $targetType self::LOCK_SHARED or self::LOCK_EXCLUSIVE + */ + protected function markChange($path, $targetType) { + if ($targetType === self::LOCK_SHARED) { + unset($this->acquiredLocks['exclusive'][$path]); + if (!isset($this->acquiredLocks['shared'][$path])) { + $this->acquiredLocks['shared'][$path] = 0; + } + $this->acquiredLocks['shared'][$path]++; + } else if ($targetType === self::LOCK_EXCLUSIVE) { + $this->acquiredLocks['exclusive'][$path] = true; + $this->acquiredLocks['shared'][$path]--; + } + } + + /** + * release all lock acquired by this instance which were marked using the mark* methods + */ + public function releaseAll() { + foreach ($this->acquiredLocks['shared'] as $path => $count) { + for ($i = 0; $i < $count; $i++) { + $this->releaseLock($path, self::LOCK_SHARED); + } + } + + foreach ($this->acquiredLocks['exclusive'] as $path => $hasLock) { + $this->releaseLock($path, self::LOCK_EXCLUSIVE); + } + } +} diff --git a/lib/private/Lock/DBLockingProvider.php b/lib/private/Lock/DBLockingProvider.php new file mode 100644 index 00000000000..9e97df44d3f --- /dev/null +++ b/lib/private/Lock/DBLockingProvider.php @@ -0,0 +1,269 @@ +<?php +/** + * @author Björn Schießle <schiessle@owncloud.com> + * @author Individual IT Services <info@individual-it.net> + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Robin Appelman <icewind@owncloud.com> + * + * @copyright Copyright (c) 2016, 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\AppFramework\Utility\ITimeFactory; +use OCP\IDBConnection; +use OCP\ILogger; +use OCP\Lock\ILockingProvider; +use OCP\Lock\LockedException; + +/** + * Locking provider that stores the locks in the database + */ +class DBLockingProvider extends AbstractLockingProvider { + /** + * @var \OCP\IDBConnection + */ + private $connection; + + /** + * @var \OCP\ILogger + */ + private $logger; + + /** + * @var \OCP\AppFramework\Utility\ITimeFactory + */ + private $timeFactory; + + private $sharedLocks = []; + + /** + * Check if we have an open shared lock for a path + * + * @param string $path + * @return bool + */ + protected function isLocallyLocked($path) { + return isset($this->sharedLocks[$path]) && $this->sharedLocks[$path]; + } + + /** + * Mark a locally acquired lock + * + * @param string $path + * @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE + */ + protected function markAcquire($path, $type) { + parent::markAcquire($path, $type); + if ($type === self::LOCK_SHARED) { + $this->sharedLocks[$path] = true; + } + } + + /** + * Change the type of an existing tracked lock + * + * @param string $path + * @param int $targetType self::LOCK_SHARED or self::LOCK_EXCLUSIVE + */ + protected function markChange($path, $targetType) { + parent::markChange($path, $targetType); + if ($targetType === self::LOCK_SHARED) { + $this->sharedLocks[$path] = true; + } else if ($targetType === self::LOCK_EXCLUSIVE) { + $this->sharedLocks[$path] = false; + } + } + + /** + * @param \OCP\IDBConnection $connection + * @param \OCP\ILogger $logger + * @param \OCP\AppFramework\Utility\ITimeFactory $timeFactory + * @param int $ttl + */ + public function __construct(IDBConnection $connection, ILogger $logger, ITimeFactory $timeFactory, $ttl = 3600) { + $this->connection = $connection; + $this->logger = $logger; + $this->timeFactory = $timeFactory; + $this->ttl = $ttl; + } + + /** + * Insert a file locking row if it does not exists. + * + * @param string $path + * @param int $lock + * @return int number of inserted rows + */ + + protected function initLockField($path, $lock = 0) { + $expire = $this->getExpireTime(); + return $this->connection->insertIfNotExist('*PREFIX*file_locks', ['key' => $path, 'lock' => $lock, 'ttl' => $expire], ['key']); + } + + /** + * @return int + */ + protected function getExpireTime() { + return $this->timeFactory->getTime() + $this->ttl; + } + + /** + * @param string $path + * @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE + * @return bool + */ + public function isLocked($path, $type) { + if ($this->hasAcquiredLock($path, $type)) { + return true; + } + $query = $this->connection->prepare('SELECT `lock` from `*PREFIX*file_locks` WHERE `key` = ?'); + $query->execute([$path]); + $lockValue = (int)$query->fetchColumn(); + if ($type === self::LOCK_SHARED) { + if ($this->isLocallyLocked($path)) { + // if we have a shared lock we kept open locally but it's released we always have at least 1 shared lock in the db + return $lockValue > 1; + } else { + return $lockValue > 0; + } + } else if ($type === self::LOCK_EXCLUSIVE) { + return $lockValue === -1; + } 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) { + $expire = $this->getExpireTime(); + if ($type === self::LOCK_SHARED) { + if (!$this->isLocallyLocked($path)) { + $result = $this->initLockField($path, 1); + if ($result <= 0) { + $result = $this->connection->executeUpdate( + 'UPDATE `*PREFIX*file_locks` SET `lock` = `lock` + 1, `ttl` = ? WHERE `key` = ? AND `lock` >= 0', + [$expire, $path] + ); + } + } else { + $result = 1; + } + } else { + $existing = 0; + if ($this->hasAcquiredLock($path, ILockingProvider::LOCK_SHARED) === false && $this->isLocallyLocked($path)) { + $existing = 1; + } + $result = $this->initLockField($path, -1); + if ($result <= 0) { + $result = $this->connection->executeUpdate( + 'UPDATE `*PREFIX*file_locks` SET `lock` = -1, `ttl` = ? WHERE `key` = ? AND `lock` = ?', + [$expire, $path, $existing] + ); + } + } + if ($result !== 1) { + throw new LockedException($path); + } + $this->markAcquire($path, $type); + } + + /** + * @param string $path + * @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE + */ + public function releaseLock($path, $type) { + $this->markRelease($path, $type); + + // we keep shared locks till the end of the request so we can re-use them + if ($type === self::LOCK_EXCLUSIVE) { + $this->connection->executeUpdate( + 'UPDATE `*PREFIX*file_locks` SET `lock` = 0 WHERE `key` = ? AND `lock` = -1', + [$path] + ); + } + } + + /** + * Change the type of an existing lock + * + * @param string $path + * @param int $targetType self::LOCK_SHARED or self::LOCK_EXCLUSIVE + * @throws \OCP\Lock\LockedException + */ + public function changeLock($path, $targetType) { + $expire = $this->getExpireTime(); + if ($targetType === self::LOCK_SHARED) { + $result = $this->connection->executeUpdate( + 'UPDATE `*PREFIX*file_locks` SET `lock` = 1, `ttl` = ? WHERE `key` = ? AND `lock` = -1', + [$expire, $path] + ); + } else { + // since we only keep one shared lock in the db we need to check if we have more then one shared lock locally manually + if (isset($this->acquiredLocks['shared'][$path]) && $this->acquiredLocks['shared'][$path] > 1) { + throw new LockedException($path); + } + $result = $this->connection->executeUpdate( + 'UPDATE `*PREFIX*file_locks` SET `lock` = -1, `ttl` = ? WHERE `key` = ? AND `lock` = 1', + [$expire, $path] + ); + } + if ($result !== 1) { + throw new LockedException($path); + } + $this->markChange($path, $targetType); + } + + /** + * cleanup empty locks + */ + public function cleanExpiredLocks() { + $expire = $this->timeFactory->getTime(); + try { + $this->connection->executeUpdate( + 'DELETE FROM `*PREFIX*file_locks` WHERE `ttl` < ?', + [$expire] + ); + } catch (\Exception $e) { + // If the table is missing, the clean up was successful + if ($this->connection->tableExists('file_locks')) { + throw $e; + } + } + } + + /** + * release all lock acquired by this instance which were marked using the mark* methods + */ + public function releaseAll() { + parent::releaseAll(); + + // since we keep shared locks we need to manually clean those + foreach ($this->sharedLocks as $path => $lock) { + if ($lock) { + $this->connection->executeUpdate( + 'UPDATE `*PREFIX*file_locks` SET `lock` = `lock` - 1 WHERE `key` = ? AND `lock` > 0', + [$path] + ); + } + } + } +} diff --git a/lib/private/Lock/MemcacheLockingProvider.php b/lib/private/Lock/MemcacheLockingProvider.php new file mode 100644 index 00000000000..536b29e2c28 --- /dev/null +++ b/lib/private/Lock/MemcacheLockingProvider.php @@ -0,0 +1,122 @@ +<?php +/** + * @author Robin Appelman <icewind@owncloud.com> + * + * @copyright Copyright (c) 2016, 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\IMemcacheTTL; +use OCP\Lock\LockedException; +use OCP\IMemcache; + +class MemcacheLockingProvider extends AbstractLockingProvider { + /** + * @var \OCP\IMemcache + */ + private $memcache; + + /** + * @param \OCP\IMemcache $memcache + * @param int $ttl + */ + public function __construct(IMemcache $memcache, $ttl = 3600) { + $this->memcache = $memcache; + $this->ttl = $ttl; + } + + private function setTTL($path) { + if ($this->memcache instanceof IMemcacheTTL) { + $this->memcache->setTTL($path, $this->ttl); + } + } + + /** + * @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); + } + } + $this->setTTL($path); + $this->markAcquire($path, $type); + } + + /** + * @param string $path + * @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE + */ + public function releaseLock($path, $type) { + if ($type === self::LOCK_SHARED) { + if (isset($this->acquiredLocks['shared'][$path]) and $this->acquiredLocks['shared'][$path] > 0) { + $this->memcache->dec($path); + $this->memcache->cad($path, 0); + } + } else if ($type === self::LOCK_EXCLUSIVE) { + $this->memcache->cad($path, 'exclusive'); + } + $this->markRelease($path, $type); + } + + /** + * Change the type of an existing lock + * + * @param string $path + * @param int $targetType self::LOCK_SHARED or self::LOCK_EXCLUSIVE + * @throws \OCP\Lock\LockedException + */ + public function changeLock($path, $targetType) { + if ($targetType === self::LOCK_SHARED) { + if (!$this->memcache->cas($path, 'exclusive', 1)) { + throw new LockedException($path); + } + } else if ($targetType === self::LOCK_EXCLUSIVE) { + // we can only change a shared lock to an exclusive if there's only a single owner of the shared lock + if (!$this->memcache->cas($path, 1, 'exclusive')) { + throw new LockedException($path); + } + } + $this->setTTL($path); + $this->markChange($path, $targetType); + } +} diff --git a/lib/private/Lock/NoopLockingProvider.php b/lib/private/Lock/NoopLockingProvider.php new file mode 100644 index 00000000000..dc58230f77e --- /dev/null +++ b/lib/private/Lock/NoopLockingProvider.php @@ -0,0 +1,68 @@ +<?php +/** + * @author Robin Appelman <icewind@owncloud.com> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2016, 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; + +/** + * Locking provider that does nothing. + * + * To be used when locking is disabled. + */ +class NoopLockingProvider implements ILockingProvider { + + /** + * {@inheritdoc} + */ + public function isLocked($path, $type) { + return false; + } + + /** + * {@inheritdoc} + */ + public function acquireLock($path, $type) { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function releaseLock($path, $type) { + // do nothing + } + + /**1 + * {@inheritdoc} + */ + public function releaseAll() { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function changeLock($path, $targetType) { + // do nothing + } +} |