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