From a1372b2fb5acc51eacf70012a6702a96c78e61ff Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 29 May 2015 14:34:21 +0200 Subject: [PATCH] add method to atomically change between shared and exclusive lock --- lib/private/lock/memcachelockingprovider.php | 20 ++++++++ lib/public/lock/ilockingprovider.php | 9 ++++ tests/lib/lock/lockingprovider.php | 53 ++++++++++++++++++++ 3 files changed, 82 insertions(+) diff --git a/lib/private/lock/memcachelockingprovider.php b/lib/private/lock/memcachelockingprovider.php index 1334d00731..368ff45032 100644 --- a/lib/private/lock/memcachelockingprovider.php +++ b/lib/private/lock/memcachelockingprovider.php @@ -98,6 +98,26 @@ class MemcacheLockingProvider implements ILockingProvider { } } + /** + * 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); + } + } + } + /** * release all lock acquired by this instance */ diff --git a/lib/public/lock/ilockingprovider.php b/lib/public/lock/ilockingprovider.php index 18fa47fa63..6a963b9d7a 100644 --- a/lib/public/lock/ilockingprovider.php +++ b/lib/public/lock/ilockingprovider.php @@ -45,6 +45,15 @@ interface ILockingProvider { */ public function releaseLock($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); + /** * release all lock acquired by this instance */ diff --git a/tests/lib/lock/lockingprovider.php b/tests/lib/lock/lockingprovider.php index 337aa4cea7..efd6e1939f 100644 --- a/tests/lib/lock/lockingprovider.php +++ b/tests/lib/lock/lockingprovider.php @@ -158,4 +158,57 @@ abstract class LockingProvider extends TestCase { $this->assertEquals('foo', $e->getPath()); } } + + public function testChangeLockToExclusive() { + $this->instance->acquireLock('foo', ILockingProvider::LOCK_SHARED); + $this->instance->changeLock('foo', ILockingProvider::LOCK_EXCLUSIVE); + $this->assertFalse($this->instance->isLocked('foo', ILockingProvider::LOCK_SHARED)); + $this->assertTrue($this->instance->isLocked('foo', ILockingProvider::LOCK_EXCLUSIVE)); + } + + public function testChangeLockToShared() { + $this->instance->acquireLock('foo', ILockingProvider::LOCK_EXCLUSIVE); + $this->instance->changeLock('foo', ILockingProvider::LOCK_SHARED); + $this->assertFalse($this->instance->isLocked('foo', ILockingProvider::LOCK_EXCLUSIVE)); + $this->assertTrue($this->instance->isLocked('foo', ILockingProvider::LOCK_SHARED)); + } + + /** + * @expectedException \OCP\Lock\LockedException + */ + public function testChangeLockToExclusiveDoubleShared() { + $this->instance->acquireLock('foo', ILockingProvider::LOCK_SHARED); + $this->instance->acquireLock('foo', ILockingProvider::LOCK_SHARED); + $this->instance->changeLock('foo', ILockingProvider::LOCK_EXCLUSIVE); + } + + /** + * @expectedException \OCP\Lock\LockedException + */ + public function testChangeLockToExclusiveNoShared() { + $this->instance->changeLock('foo', ILockingProvider::LOCK_EXCLUSIVE); + } + + /** + * @expectedException \OCP\Lock\LockedException + */ + public function testChangeLockToExclusiveFromExclusive() { + $this->instance->acquireLock('foo', ILockingProvider::LOCK_EXCLUSIVE); + $this->instance->changeLock('foo', ILockingProvider::LOCK_EXCLUSIVE); + } + + /** + * @expectedException \OCP\Lock\LockedException + */ + public function testChangeLockToSharedNoExclusive() { + $this->instance->changeLock('foo', ILockingProvider::LOCK_SHARED); + } + + /** + * @expectedException \OCP\Lock\LockedException + */ + public function testChangeLockToSharedFromShared() { + $this->instance->acquireLock('foo', ILockingProvider::LOCK_SHARED); + $this->instance->changeLock('foo', ILockingProvider::LOCK_SHARED); + } }