mirror of
https://git.sekbaer.de/Friendica/friendica.git
synced 2025-06-10 00:54:27 +02:00
210 lines
5.1 KiB
PHP
210 lines
5.1 KiB
PHP
<?php
|
|
|
|
// Copyright (C) 2010-2025, the Friendica project
|
|
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
|
|
//
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
namespace Friendica\System;
|
|
|
|
use Friendica\Database\Database;
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
/**
|
|
* class for direct interacting with the daemon commands
|
|
*/
|
|
final class Daemon
|
|
{
|
|
private LoggerInterface $logger;
|
|
private Database $dba;
|
|
private ?string $pidfile = null;
|
|
private ?int $pid = null;
|
|
|
|
/**
|
|
* The PID of the current daemon (null if either not set or not found)
|
|
*
|
|
* @return int|null
|
|
*/
|
|
public function getPid(): ?int
|
|
{
|
|
return $this->pid;
|
|
}
|
|
|
|
/**
|
|
* The path to the PID file (null if not set)
|
|
*
|
|
* @return string|null
|
|
*/
|
|
public function getPidfile(): ?string
|
|
{
|
|
return $this->pidfile;
|
|
}
|
|
|
|
public function __construct(LoggerInterface $logger, Database $dba)
|
|
{
|
|
$this->logger = $logger;
|
|
$this->dba = $dba;
|
|
}
|
|
|
|
/**
|
|
* Initialize the current daemon class with a given PID file
|
|
*
|
|
* @param string|null $pidfile the path to the PID file - using a given path if not directly set here
|
|
*
|
|
* @return void
|
|
*/
|
|
public function init(string $pidfile = null): void
|
|
{
|
|
if (!empty($pidfile)) {
|
|
$this->pid = null;
|
|
$this->pidfile = $pidfile;
|
|
}
|
|
|
|
if (!empty($this->pid)) {
|
|
return;
|
|
}
|
|
|
|
if (is_readable($this->pidfile)) {
|
|
$this->pid = intval(file_get_contents($this->pidfile));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Starts the daemon
|
|
*
|
|
* @param callable $daemonLogic the business logic of the daemon
|
|
* @param bool $foreground true, if started in foreground, otherwise spawned in the background
|
|
*
|
|
* @return bool true, if successfully started, otherwise false
|
|
*/
|
|
public function start(callable $daemonLogic, bool $foreground = false): bool
|
|
{
|
|
$this->init();
|
|
|
|
if (!empty($this->pid)) {
|
|
$this->logger->notice('process is already running', ['pid' => $this->pid, 'pidfile' => $this->pidfile]);
|
|
return false;
|
|
}
|
|
|
|
$this->logger->notice('starting daemon', ['pid' => $this->pid, 'pidfile' => $this->pidfile]);
|
|
|
|
if (!$foreground) {
|
|
$this->dba->disconnect();
|
|
|
|
// fork a daemon process
|
|
$this->pid = pcntl_fork();
|
|
if ($this->pid < 0) {
|
|
$this->logger->warning('Could not fork daemon');
|
|
return false;
|
|
} elseif ($this->pid) {
|
|
// The parent process continues here
|
|
if (!file_put_contents($this->pidfile, $this->pid)) {
|
|
$this->logger->warning('Could not store pid file', ['pid' => $this->pid, 'pidfile' => $this->pidfile]);
|
|
posix_kill($this->pid, SIGTERM);
|
|
return false;
|
|
}
|
|
$this->logger->notice('Child process started', ['pid' => $this->pid, 'pidfile' => $this->pidfile]);
|
|
return true;
|
|
}
|
|
|
|
// We now are in the child process
|
|
register_shutdown_function(function (): void {
|
|
posix_kill(posix_getpid(), SIGTERM);
|
|
posix_kill(posix_getpid(), SIGHUP);
|
|
});
|
|
|
|
// Make the child the main process, detach it from the terminal
|
|
if (posix_setsid() < 0) {
|
|
return true;
|
|
}
|
|
|
|
// Closing all existing connections with the outside
|
|
fclose(STDIN);
|
|
|
|
// And now connect the database again
|
|
$this->dba->connect();
|
|
}
|
|
|
|
// Just to be sure that this script really runs endlessly
|
|
set_time_limit(0);
|
|
|
|
$daemonLogic();
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Checks, if the current daemon is running
|
|
*
|
|
* @return bool true, if the daemon is running, otherwise false (f.e no PID found, no PID file found, PID is not bound to a running process))
|
|
*/
|
|
public function isRunning(): bool
|
|
{
|
|
$this->init();
|
|
|
|
if (empty($this->pid)) {
|
|
$this->logger->notice("Pid wasn't found");
|
|
|
|
if (is_readable($this->pidfile)) {
|
|
unlink($this->pidfile);
|
|
$this->logger->notice("Pidfile removed", ['pidfile' => $this->pidfile]);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (posix_kill($this->pid, 0)) {
|
|
$this->logger->notice("daemon process is running");
|
|
return true;
|
|
} else {
|
|
unlink($this->pidfile);
|
|
$this->logger->notice("daemon process isn't running");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stops the daemon, if running
|
|
*
|
|
* @return bool true, if the daemon was successfully stopped or is already stopped, otherwise false
|
|
*/
|
|
public function stop(): bool
|
|
{
|
|
$this->init();
|
|
|
|
if (empty($this->pid)) {
|
|
$this->logger->notice("Pidfile wasn't found", ['pidfile' => $this->pidfile]);
|
|
return true;
|
|
}
|
|
|
|
if (!posix_kill($this->pid, SIGTERM)) {
|
|
$this->logger->warning("Cannot kill the given PID", ['pid' => $this->pid, 'pidfile' => $this->pidfile]);
|
|
return false;
|
|
}
|
|
|
|
if (!unlink($this->pidfile)) {
|
|
$this->logger->warning("Cannot delete the given PID file", ['pid' => $this->pid, 'pidfile' => $this->pidfile]);
|
|
return false;
|
|
}
|
|
|
|
$this->logger->notice('daemon process was killed', ['pid' => $this->pid, 'pidfile' => $this->pidfile]);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Sets the current daemon to sleep and checks the status afterward
|
|
*
|
|
* @param int $duration the duration of time for sleeping (in milliseconds)
|
|
*
|
|
* @return void
|
|
*/
|
|
public function sleep(int $duration)
|
|
{
|
|
usleep($duration);
|
|
|
|
$this->pid = pcntl_waitpid(-1, $status, WNOHANG);
|
|
if ($this->pid > 0) {
|
|
$this->logger->info('Children quit via pcntl_waitpid', ['pid' => $this->pid, 'status' => $status]);
|
|
}
|
|
}
|
|
}
|