diff options
author | Robin Appelman <icewind@owncloud.com> | 2015-10-20 14:04:50 +0200 |
---|---|---|
committer | Thomas Müller <thomas.mueller@tmit.eu> | 2015-10-21 09:43:30 +0200 |
commit | cc7bd53d1719d17a9fe222c2f4ed9e207d10c805 (patch) | |
tree | 3b4dc61c0185443731d3c2a0bef417f1bbeabe41 | |
parent | 74f41349b76e258e17fe4a39d7845fdcca3d554f (diff) | |
download | nextcloud-server-cc7bd53d1719d17a9fe222c2f4ed9e207d10c805.tar.gz nextcloud-server-cc7bd53d1719d17a9fe222c2f4ed9e207d10c805.zip |
Keep shared locks until the end of the request so we can reuse them
-rw-r--r-- | lib/private/lock/abstractlockingprovider.php | 8 | ||||
-rw-r--r-- | lib/private/lock/dblockingprovider.php | 104 |
2 files changed, 94 insertions, 18 deletions
diff --git a/lib/private/lock/abstractlockingprovider.php b/lib/private/lock/abstractlockingprovider.php index eb86be68500..ae0face325e 100644 --- a/lib/private/lock/abstractlockingprovider.php +++ b/lib/private/lock/abstractlockingprovider.php @@ -33,6 +33,14 @@ abstract class AbstractLockingProvider implements ILockingProvider { 'exclusive' => [] ]; + 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 * diff --git a/lib/private/lock/dblockingprovider.php b/lib/private/lock/dblockingprovider.php index 450a0a27ab0..c1f24a9c132 100644 --- a/lib/private/lock/dblockingprovider.php +++ b/lib/private/lock/dblockingprovider.php @@ -25,6 +25,7 @@ namespace OC\Lock; use OCP\AppFramework\Utility\ITimeFactory; use OCP\IDBConnection; use OCP\ILogger; +use OCP\Lock\ILockingProvider; use OCP\Lock\LockedException; /** @@ -46,8 +47,42 @@ class DBLockingProvider extends AbstractLockingProvider { */ private $timeFactory; + private $sharedLocks = []; + const TTL = 3600; // how long until we clear stray locks in seconds + 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 @@ -85,11 +120,19 @@ class DBLockingProvider extends AbstractLockingProvider { * @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) { - return $lockValue > 0; + 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 { @@ -105,19 +148,27 @@ class DBLockingProvider extends AbstractLockingProvider { public function acquireLock($path, $type) { $expire = $this->getExpireTime(); if ($type === self::LOCK_SHARED) { - $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] - ); + 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 { - $result = $this->initLockField($path,-1); + $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` = 0', - [$expire, $path] + 'UPDATE `*PREFIX*file_locks` SET `lock` = -1, `ttl` = ? WHERE `key` = ? AND `lock` = ?', + [$expire, $path, $existing] ); } } @@ -132,19 +183,15 @@ class DBLockingProvider extends AbstractLockingProvider { * @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE */ public function releaseLock($path, $type) { - if ($type === self::LOCK_SHARED) { - $this->connection->executeUpdate( - 'UPDATE `*PREFIX*file_locks` SET `lock` = `lock` - 1 WHERE `key` = ? AND `lock` > 0', - [$path] - ); - } else { + $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] ); } - - $this->markRelease($path, $type); } /** @@ -162,6 +209,10 @@ class DBLockingProvider extends AbstractLockingProvider { [$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] @@ -184,6 +235,23 @@ class DBLockingProvider extends AbstractLockingProvider { ); } + /** + * 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] + ); + } + } + } + public function __destruct() { try { $this->cleanEmptyLocks(); |