Files
cacti/tests/Unit/Core/CactiProcessLockTest.php
Thomas Vincent a155419ff3 Serialize process registry admission with portable locks (#7771)
* 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>
2026-09-04 19:36:42 -07:00

72 lines
3.1 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\Store\InMemoryStore;
require_once dirname(__DIR__, 3) . '/lib/CactiProcessLock.php';
test('task keys are deterministic and preserve field boundaries', function () {
$key = CactiProcessLock::key('poller', 'child', 7);
expect($key)->toBe(CactiProcessLock::key('poller', 'child', 7))
->and($key)->toStartWith('cacti.process.')
->and(strlen($key))->toBe(78)
->and($key)->not->toBe(CactiProcessLock::key('poller:child', '7', 0))
->and($key)->not->toBe(CactiProcessLock::key('poller', 'child', 8));
});
test('only one owner can hold a task lock at a time', function () {
$factory = new LockFactory(new InMemoryStore());
$first = new CactiProcessLock($factory, 'poller', 'master', 0);
$second = new CactiProcessLock($factory, 'poller', 'master', 0);
expect($first->acquire())->toBeTrue()
->and($second->acquire())->toBeFalse();
$first->release();
expect($second->acquire())->toBeTrue();
$second->release();
});
test('pdo locks use the declared schema and release ownership', function () {
$connection = new PDO('sqlite::memory:');
$connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
$first = CactiProcessLock::fromPdo($connection, 'reports', 'master', 0);
$second = CactiProcessLock::fromPdo($connection, 'reports', 'master', 0);
expect($connection->getAttribute(PDO::ATTR_ERRMODE))->toBe(PDO::ERRMODE_SILENT)
->and($first->acquire())->toBeTrue()
->and($connection->getAttribute(PDO::ATTR_ERRMODE))->toBe(PDO::ERRMODE_SILENT)
->and($connection->query("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'process_locks'")->fetchColumn())->toBe(CactiProcessLock::TABLE)
->and($second->acquire())->toBeFalse();
$first->release();
expect($connection->getAttribute(PDO::ATTR_ERRMODE))->toBe(PDO::ERRMODE_SILENT)
->and($second->acquire())->toBeTrue();
$second->release();
expect($connection->getAttribute(PDO::ATTR_ERRMODE))->toBe(PDO::ERRMODE_SILENT);
});