Merge pull request #19098 from owncloud/db-lock-no-transaction

Remove the need for the transaction in the database locking backend
This commit is contained in:
Thomas Müller 2015-09-18 17:02:18 +02:00
commit bbf128f1b2
3 changed files with 90 additions and 17 deletions

View File

@ -21,6 +21,7 @@
namespace OC\Lock; namespace OC\Lock;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IDBConnection; use OCP\IDBConnection;
use OCP\ILogger; use OCP\ILogger;
use OCP\Lock\LockedException; use OCP\Lock\LockedException;
@ -39,17 +40,34 @@ class DBLockingProvider extends AbstractLockingProvider {
*/ */
private $logger; private $logger;
/**
* @var \OCP\AppFramework\Utility\ITimeFactory
*/
private $timeFactory;
const TTL = 3600; // how long until we clear stray locks in seconds
/** /**
* @param \OCP\IDBConnection $connection * @param \OCP\IDBConnection $connection
* @param \OCP\ILogger $logger * @param \OCP\ILogger $logger
* @param \OCP\AppFramework\Utility\ITimeFactory $timeFactory
*/ */
public function __construct(IDBConnection $connection, ILogger $logger) { public function __construct(IDBConnection $connection, ILogger $logger, ITimeFactory $timeFactory) {
$this->connection = $connection; $this->connection = $connection;
$this->logger = $logger; $this->logger = $logger;
$this->timeFactory = $timeFactory;
} }
protected function initLockField($path) { protected function initLockField($path) {
$this->connection->insertIfNotExist('*PREFIX*file_locks', ['key' => $path, 'lock' => 0, 'ttl' => 0], ['key']); $expire = $this->getExpireTime();
$this->connection->insertIfNotExist('*PREFIX*file_locks', ['key' => $path, 'lock' => 0, 'ttl' => $expire], ['key']);
}
/**
* @return int
*/
protected function getExpireTime() {
return $this->timeFactory->getTime() + self::TTL;
} }
/** /**
@ -76,24 +94,23 @@ class DBLockingProvider extends AbstractLockingProvider {
* @throws \OCP\Lock\LockedException * @throws \OCP\Lock\LockedException
*/ */
public function acquireLock($path, $type) { public function acquireLock($path, $type) {
if ($this->connection->inTransaction()){ if ($this->connection->inTransaction()) {
$this->logger->warning("Trying to acquire a lock for '$path' while inside a transition"); $this->logger->warning("Trying to acquire a lock for '$path' while inside a transition");
} }
$this->connection->beginTransaction();
$this->initLockField($path); $this->initLockField($path);
$expire = $this->getExpireTime();
if ($type === self::LOCK_SHARED) { if ($type === self::LOCK_SHARED) {
$result = $this->connection->executeUpdate( $result = $this->connection->executeUpdate(
'UPDATE `*PREFIX*file_locks` SET `lock` = `lock` + 1 WHERE `key` = ? AND `lock` >= 0', 'UPDATE `*PREFIX*file_locks` SET `lock` = `lock` + 1, `ttl` = ? WHERE `key` = ? AND `lock` >= 0',
[$path] [$expire, $path]
); );
} else { } else {
$result = $this->connection->executeUpdate( $result = $this->connection->executeUpdate(
'UPDATE `*PREFIX*file_locks` SET `lock` = -1 WHERE `key` = ? AND `lock` = 0', 'UPDATE `*PREFIX*file_locks` SET `lock` = -1, `ttl` = ? WHERE `key` = ? AND `lock` = 0',
[$path] [$expire, $path]
); );
} }
$this->connection->commit();
if ($result !== 1) { if ($result !== 1) {
throw new LockedException($path); throw new LockedException($path);
} }
@ -129,16 +146,16 @@ class DBLockingProvider extends AbstractLockingProvider {
* @throws \OCP\Lock\LockedException * @throws \OCP\Lock\LockedException
*/ */
public function changeLock($path, $targetType) { public function changeLock($path, $targetType) {
$this->initLockField($path); $expire = $this->getExpireTime();
if ($targetType === self::LOCK_SHARED) { if ($targetType === self::LOCK_SHARED) {
$result = $this->connection->executeUpdate( $result = $this->connection->executeUpdate(
'UPDATE `*PREFIX*file_locks` SET `lock` = 1 WHERE `key` = ? AND `lock` = -1', 'UPDATE `*PREFIX*file_locks` SET `lock` = 1, `ttl` = ? WHERE `key` = ? AND `lock` = -1',
[$path] [$expire, $path]
); );
} else { } else {
$result = $this->connection->executeUpdate( $result = $this->connection->executeUpdate(
'UPDATE `*PREFIX*file_locks` SET `lock` = -1 WHERE `key` = ? AND `lock` = 1', 'UPDATE `*PREFIX*file_locks` SET `lock` = -1, `ttl` = ? WHERE `key` = ? AND `lock` = 1',
[$path] [$expire, $path]
); );
} }
if ($result !== 1) { if ($result !== 1) {
@ -151,8 +168,10 @@ class DBLockingProvider extends AbstractLockingProvider {
* cleanup empty locks * cleanup empty locks
*/ */
public function cleanEmptyLocks() { public function cleanEmptyLocks() {
$expire = $this->timeFactory->getTime();
$this->connection->executeUpdate( $this->connection->executeUpdate(
'DELETE FROM `*PREFIX*file_locks` WHERE `lock` = 0' 'DELETE FROM `*PREFIX*file_locks` WHERE `lock` = 0 AND `ttl` < ?',
[$expire]
); );
} }

View File

@ -460,7 +460,7 @@ class Server extends SimpleContainer implements IServerContainer {
if (!($memcache instanceof \OC\Memcache\NullCache)) { if (!($memcache instanceof \OC\Memcache\NullCache)) {
return new MemcacheLockingProvider($memcache); return new MemcacheLockingProvider($memcache);
} }
return new DBLockingProvider($c->getDatabaseConnection(), $c->getLogger()); return new DBLockingProvider($c->getDatabaseConnection(), $c->getLogger(), new TimeFactory());
} }
return new NoopLockingProvider(); return new NoopLockingProvider();
}); });

View File

@ -21,23 +21,77 @@
namespace Test\Lock; namespace Test\Lock;
use OCP\Lock\ILockingProvider;
class DBLockingProvider extends LockingProvider { class DBLockingProvider extends LockingProvider {
/**
* @var \OC\Lock\DBLockingProvider
*/
protected $instance;
/** /**
* @var \OCP\IDBConnection * @var \OCP\IDBConnection
*/ */
private $connection; private $connection;
/**
* @var \OCP\AppFramework\Utility\ITimeFactory
*/
private $timeFactory;
private $currentTime;
public function setUp() {
$this->currentTime = time();
$this->timeFactory = $this->getMock('\OCP\AppFramework\Utility\ITimeFactory');
$this->timeFactory->expects($this->any())
->method('getTime')
->will($this->returnCallback(function () {
return $this->currentTime;
}));
parent::setUp();
}
/** /**
* @return \OCP\Lock\ILockingProvider * @return \OCP\Lock\ILockingProvider
*/ */
protected function getInstance() { protected function getInstance() {
$this->connection = \OC::$server->getDatabaseConnection(); $this->connection = \OC::$server->getDatabaseConnection();
return new \OC\Lock\DBLockingProvider($this->connection, \OC::$server->getLogger()); return new \OC\Lock\DBLockingProvider($this->connection, \OC::$server->getLogger(), $this->timeFactory);
} }
public function tearDown() { public function tearDown() {
$this->connection->executeQuery('DELETE FROM `*PREFIX*file_locks`'); $this->connection->executeQuery('DELETE FROM `*PREFIX*file_locks`');
parent::tearDown(); parent::tearDown();
} }
public function testCleanEmptyLocks() {
$this->currentTime = 100;
$this->instance->acquireLock('foo', ILockingProvider::LOCK_EXCLUSIVE);
$this->instance->acquireLock('asd', ILockingProvider::LOCK_EXCLUSIVE);
$this->currentTime = 200;
$this->instance->acquireLock('bar', ILockingProvider::LOCK_EXCLUSIVE);
$this->instance->changeLock('asd', ILockingProvider::LOCK_SHARED);
$this->currentTime = 150 + \OC\Lock\DBLockingProvider::TTL;
$this->assertEquals(3, $this->getLockEntryCount());
$this->instance->cleanEmptyLocks();
$this->assertEquals(3, $this->getLockEntryCount());
$this->instance->releaseAll();
$this->instance->cleanEmptyLocks();
$this->assertEquals(2, $this->getLockEntryCount());
}
private function getLockEntryCount() {
$query = $this->connection->prepare('SELECT count(*) FROM `*PREFIX*file_locks`');
$query->execute();
return $query->fetchColumn();
}
} }