exec('PRAGMA busy_timeout = 5000'); $conn->exec("INSERT INTO processes (pid, tasktype, taskname, taskid) VALUES (999, '$tasktype', '$taskname', 1)"); $conn = null; usleep((int) ($hold_for * 1_000_000)); $conn = new PDO('sqlite:' . $db_file); $conn->exec('PRAGMA busy_timeout = 5000'); $conn->exec("DELETE FROM processes WHERE tasktype = '$tasktype' AND taskname = '$taskname' AND taskid = 1"); $conn = null; // A plain exit() here would run every shutdown function and // destructor this process inherited from the parent at fork // time, including Pest/PHPUnit's own teardown -- which then // races the real parent's teardown over shared fds/temp files // and surfaces as spurious warnings in the parent's test // results. SIGKILL terminates immediately with none of that. posix_kill(posix_getpid(), SIGKILL); exit(0); } return $pid; } } beforeEach(function () { global $database_sessions, $database_hostname, $database_port, $database_default; $this->db_globals = [$database_sessions, $database_hostname, $database_port, $database_default]; if (!function_exists('pcntl_fork')) { test()->markTestSkipped('ext-pcntl not available; cannot fork a real late-registering child.'); return; } // tempnam() creates an empty base file as a side effect; the real // database lives at $this->dbFile . '.sqlite', created fresh by PDO // below. Track the base path separately so afterEach can remove it // without ever calling unlink() on a path that may not exist -- // PHPUnit 10+ surfaces E_WARNING from a failed unlink() as a test // warning even under the "@" operator. $this->dbFileBase = tempnam(sys_get_temp_dir(), 'cacti_barrier_handoff_'); $this->dbFile = $this->dbFileBase . '.sqlite'; $setup = new PDO('sqlite:' . $this->dbFile); $setup->exec('PRAGMA busy_timeout = 5000'); $setup->exec('CREATE TABLE processes (pid INTEGER, tasktype TEXT, taskname TEXT, taskid INTEGER)'); $setup = null; }); afterEach(function () { global $database_sessions, $database_hostname, $database_port, $database_default; // Put the default db_* connection back. Left in place, the sqlite handle // answers every later read_config_option() in the run and throws on // Cacti's MySQL SQL, aborting the suite. [$database_sessions, $database_hostname, $database_port, $database_default] = $this->db_globals; if (isset($this->dbFile) && is_file($this->dbFile)) { unlink($this->dbFile); } if (isset($this->dbFileBase) && is_file($this->dbFileBase)) { unlink($this->dbFileBase); } }); test('dsstats_wait_for_children waits for a late-registering child instead of returning at time zero', function () { if (!isset($this->dbFile)) { return; } $childPid = barrier_handoff_spawn_late_child($this->dbFile, 'dsstats', 'bchild', 0.6, 0.6); $pdo = new PDO('sqlite:' . $this->dbFile); $pdo->exec('PRAGMA busy_timeout = 5000'); barrier_handoff_connect_default_session($pdo); $start = microtime(true); dsstats_wait_for_children('bmaster', 1); $elapsed = microtime(true) - $start; pcntl_waitpid($childPid, $status); // The pre-fix pattern reads $running = 0 immediately (the child hasn't // forked/booted/registered yet) and returns in well under 100ms. The // barrier must instead still be waiting when the child registers at // ~0.6s, and must not resolve before the child clears its row at ~1.2s. expect($elapsed)->toBeGreaterThan(1.0); expect($elapsed)->toBeLessThan(20.0); // No processes row should remain -- the child cleaned up and the parent // did not return before that happened. expect((int) dsstats_processes_running('bmaster'))->toBe(0); }); test('rrdcheck_wait_for_children waits for a late-registering child instead of returning at time zero', function () { if (!isset($this->dbFile)) { return; } $childPid = barrier_handoff_spawn_late_child($this->dbFile, 'rrdcheck', 'bchild', 0.6, 0.6); $pdo = new PDO('sqlite:' . $this->dbFile); $pdo->exec('PRAGMA busy_timeout = 5000'); barrier_handoff_connect_default_session($pdo); $start = microtime(true); rrdcheck_wait_for_children('bmaster', 1); $elapsed = microtime(true) - $start; pcntl_waitpid($childPid, $status); expect($elapsed)->toBeGreaterThan(1.0); expect($elapsed)->toBeLessThan(20.0); expect((int) rrdcheck_processes_running('bmaster'))->toBe(0); });