Keep shared locks until the end of the request so we can reuse them

This commit is contained in:
Robin Appelman 2015-10-20 14:04:50 +02:00 committed by Thomas Müller
parent 74f41349b7
commit cc7bd53d17
2 changed files with 94 additions and 18 deletions

View file

@ -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
*

View file

@ -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();