mirror of
https://github.com/Cacti/cacti.git
synced 2026-10-23 03:27:02 +09:00
* Serialize process registry admission with Symfony Lock Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * Fix PDO process lock mode handling Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * ci: run process lock coverage on PHP 8.2 Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix: confirm a pid is still ours before signalling it #7574 added the still-running guard to dsstats but left six byte-identical siblings signalling a pid straight out of the processes table. After a crash or a power loss the row survives, the pid is recycled by an unrelated daemon, and the next start SIGTERMs a stranger. That is issue #7425, still live in rrdcheck, boost, poller_commands, float_rrdfiles, poller_reindex_hosts and rebuild_poller_cache. Wrap each kill in cacti_process_still_running(), matching the dsstats shape, and leave unregister_process() outside the guard so a stale row is still cleaned up. Cover all seven with a test that walks every kill site, so a new variant cannot reintroduce it. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test: assert the guard plainly toContain() takes needles rather than a failure message, so the file name was being asserted as a second required substring and the test always failed. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * docs(poller): describe required lock parameters --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
90 lines
3.4 KiB
PHP
90 lines
3.4 KiB
PHP
<?php
|
|
/*
|
|
+-------------------------------------------------------------------------+
|
|
| Copyright (C) 2004-2026 The Cacti Group |
|
|
| |
|
|
| This program is free software; you can redistribute it and/or |
|
|
| modify it under the terms of the GNU General Public License |
|
|
| as published by the Free Software Foundation; either version 2 |
|
|
| 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 General Public License for more details. |
|
|
+-------------------------------------------------------------------------+
|
|
| Cacti: The Complete RRDtool-based Graphing Solution |
|
|
+-------------------------------------------------------------------------+
|
|
*/
|
|
|
|
use Symfony\Component\Lock\LockFactory;
|
|
use Symfony\Component\Lock\LockInterface;
|
|
use Symfony\Component\Lock\Store\PdoStore;
|
|
|
|
/**
|
|
* Short-lived, database-backed mutex for one Cacti process-registry key.
|
|
*
|
|
* The processes table remains the source of lifecycle, PID and timeout
|
|
* information. This lock only serializes admission for one logical registry
|
|
* row so two contenders cannot both observe an empty row and start.
|
|
*/
|
|
final class CactiProcessLock {
|
|
public const TABLE = 'process_locks';
|
|
public const TTL = 30;
|
|
|
|
private LockInterface $lock;
|
|
private ?PDO $connection;
|
|
|
|
public function __construct(LockFactory $factory, string $tasktype, string $taskname, int $taskid, ?PDO $connection = null) {
|
|
$this->lock = $factory->createLock(self::key($tasktype, $taskname, $taskid), self::TTL);
|
|
$this->connection = $connection;
|
|
}
|
|
|
|
public static function fromPdo(PDO $connection, string $tasktype, string $taskname, int $taskid) : self {
|
|
$store = self::withExceptionMode(
|
|
$connection,
|
|
static fn () : PdoStore => new PdoStore($connection, ['db_table' => self::TABLE], 0.01, self::TTL)
|
|
);
|
|
|
|
return new self(new LockFactory($store), $tasktype, $taskname, $taskid, $connection);
|
|
}
|
|
|
|
public static function key(string $tasktype, string $taskname, int $taskid) : string {
|
|
return 'cacti.process.' . hash('sha256', serialize([$tasktype, $taskname, $taskid]));
|
|
}
|
|
|
|
public function acquire() : bool {
|
|
return $this->runWithRequiredMode(fn () : bool => $this->lock->acquire());
|
|
}
|
|
|
|
public function release() : void {
|
|
$this->runWithRequiredMode(function () : void {
|
|
$this->lock->release();
|
|
});
|
|
}
|
|
|
|
private function runWithRequiredMode(callable $operation) : mixed {
|
|
if ($this->connection === null) {
|
|
return $operation();
|
|
}
|
|
|
|
return self::withExceptionMode($this->connection, $operation);
|
|
}
|
|
|
|
private static function withExceptionMode(PDO $connection, callable $operation) : mixed {
|
|
$original = $connection->getAttribute(PDO::ATTR_ERRMODE);
|
|
|
|
if ($original !== PDO::ERRMODE_EXCEPTION) {
|
|
$connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
}
|
|
|
|
try {
|
|
return $operation();
|
|
} finally {
|
|
if ($original !== PDO::ERRMODE_EXCEPTION) {
|
|
$connection->setAttribute(PDO::ATTR_ERRMODE, $original);
|
|
}
|
|
}
|
|
}
|
|
}
|