friendica/src/Core/Lock/Type/DatabaseLock.php

213 lines
5.2 KiB
PHP
Raw Normal View History

<?php
2020-02-09 15:45:36 +01:00
/**
2021-03-29 08:40:20 +02:00
* @copyright Copyright (C) 2010-2021, the Friendica project
2020-02-09 15:45:36 +01:00
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Core\Lock\Type;
use Friendica\Core\Cache\Enum\Duration;
use Friendica\Core\Lock\Enum\Type;
2021-10-26 21:44:29 +02:00
use Friendica\Core\Lock\Exception\LockPersistenceException;
use Friendica\Database\Database;
use Friendica\Util\DateTimeFormat;
/**
* Locking driver that stores the locks in the database
*/
2021-10-26 21:44:29 +02:00
class DatabaseLock extends AbstractLock
{
/**
* The current ID of the process
*
* @var int
*/
private $pid;
/**
* @var Database The database connection of Friendica
*/
private $dba;
/**
2021-10-26 21:44:29 +02:00
* @param int|null $pid The id of the current process (null means determine automatically)
*/
2021-10-26 21:44:29 +02:00
public function __construct(Database $dba, ?int $pid = null)
{
$this->dba = $dba;
2021-10-26 21:44:29 +02:00
$this->pid = $pid ?? getmypid();
}
/**
* (@inheritdoc)
*/
2021-10-26 21:44:29 +02:00
public function acquire(string $key, int $timeout = 120, int $ttl = Duration::FIVE_MINUTES): bool
{
$got_lock = false;
$start = time();
2021-10-26 21:44:29 +02:00
try {
do {
$this->dba->lock('locks');
$lock = $this->dba->selectFirst('locks', ['locked', 'pid'], [
'`name` = ? AND `expires` >= ?', $key,DateTimeFormat::utcNow()
]);
if ($this->dba->isResult($lock)) {
if ($lock['locked']) {
// We want to lock something that was already locked by us? So we got the lock.
if ($lock['pid'] == $this->pid) {
$got_lock = true;
}
}
if (!$lock['locked']) {
$this->dba->update('locks', [
'locked' => true,
'pid' => $this->pid,
'expires' => DateTimeFormat::utc('now + ' . $ttl . 'seconds')
], ['name' => $key]);
$got_lock = true;
}
2021-10-26 21:44:29 +02:00
} else {
$this->dba->insert('locks', [
'name' => $key,
'locked' => true,
'pid' => $this->pid,
'expires' => DateTimeFormat::utc('now + ' . $ttl . 'seconds')]);
$got_lock = true;
2021-10-26 21:44:29 +02:00
$this->markAcquire($key);
}
2021-10-26 21:44:29 +02:00
$this->dba->unlock();
2021-10-26 21:44:29 +02:00
if (!$got_lock && ($timeout > 0)) {
usleep(rand(100000, 2000000));
}
} while (!$got_lock && ((time() - $start) < $timeout));
} catch (\Exception $exception) {
throw new LockPersistenceException(sprintf('Cannot acquire lock for key %s', $key), $exception);
}
return $got_lock;
}
/**
* (@inheritdoc)
*/
2021-10-26 21:44:29 +02:00
public function release(string $key, bool $override = false): bool
{
if ($override) {
2019-02-24 10:08:28 +01:00
$where = ['name' => $key];
} else {
$where = ['name' => $key, 'pid' => $this->pid];
}
2021-10-26 21:44:29 +02:00
try {
if ($this->dba->exists('locks', $where)) {
$return = $this->dba->delete('locks', $where);
} else {
$return = false;
}
} catch (\Exception $exception) {
throw new LockPersistenceException(sprintf('Cannot release lock for key %s (override %b)', $key, $override), $exception);
}
$this->markRelease($key);
2019-03-04 21:28:36 +01:00
return $return;
}
/**
* (@inheritdoc)
*/
2021-10-26 21:44:29 +02:00
public function releaseAll(bool $override = false): bool
{
2019-08-13 21:20:41 +02:00
$success = parent::releaseAll($override);
if ($override) {
$where = ['1 = 1'];
} else {
$where = ['pid' => $this->pid];
}
2021-10-26 21:44:29 +02:00
try {
$return = $this->dba->delete('locks', $where);
} catch (\Exception $exception) {
throw new LockPersistenceException(sprintf('Cannot release all lock (override %b)', $override), $exception);
}
$this->acquiredLocks = [];
2019-03-04 21:28:36 +01:00
return $return && $success;
}
/**
* (@inheritdoc)
*/
2021-10-26 21:44:29 +02:00
public function isLocked(string $key): bool
{
2021-10-26 21:44:29 +02:00
try {
$lock = $this->dba->selectFirst('locks', ['locked'], [
'`name` = ? AND `expires` >= ?', $key, DateTimeFormat::utcNow()]);
} catch (\Exception $exception) {
throw new LockPersistenceException(sprintf('Cannot check lock state for key %s', $key), $exception);
}
if ($this->dba->isResult($lock)) {
return $lock['locked'] !== false;
} else {
return false;
}
}
2019-08-13 21:20:41 +02:00
/**
* {@inheritDoc}
*/
2021-10-26 21:44:29 +02:00
public function getName(): string
2019-08-13 21:20:41 +02:00
{
return Type::DATABASE;
2019-08-13 21:20:41 +02:00
}
/**
* {@inheritDoc}
*/
2021-10-26 21:44:29 +02:00
public function getLocks(string $prefix = ''): array
2019-08-13 21:20:41 +02:00
{
2021-10-26 21:44:29 +02:00
try {
if (empty($prefix)) {
$where = ['`expires` >= ?', DateTimeFormat::utcNow()];
} else {
$where = ['`expires` >= ? AND `name` LIKE CONCAT(?, \'%\')', DateTimeFormat::utcNow(), $prefix];
}
2019-08-13 21:20:41 +02:00
2021-10-26 21:44:29 +02:00
$stmt = $this->dba->select('locks', ['name'], $where);
2019-08-13 21:20:41 +02:00
2021-10-26 21:44:29 +02:00
$keys = [];
while ($key = $this->dba->fetch($stmt)) {
array_push($keys, $key['name']);
}
} catch (\Exception $exception) {
throw new LockPersistenceException(sprintf('Cannot get lock with prefix %s', $prefix), $exception);
} finally {
$this->dba->close($stmt);
2019-08-13 21:20:41 +02:00
}
return $keys;
}
}