diff --git a/bin/daemon.php b/bin/daemon.php index d08aa37d15..ec507305cc 100755 --- a/bin/daemon.php +++ b/bin/daemon.php @@ -29,6 +29,7 @@ if (php_sapi_name() !== 'cli') { } use Dice\Dice; +use Friendica\App\Mode; use Friendica\Core\Logger; use Friendica\Core\Worker; use Friendica\Database\DBA; @@ -65,6 +66,8 @@ if (DI::mode()->isInstall()) { die("Friendica isn't properly installed yet.\n"); } +DI::mode()->setExecutor(Mode::DAEMON); + DI::config()->load(); if (empty(DI::config()->get('system', 'pidfile'))) { @@ -144,34 +147,35 @@ Logger::notice('Starting worker daemon.', ["pid" => $pid]); if (!$foreground) { echo "Starting worker daemon.\n"; - // Switch over to daemon mode. - if ($pid = pcntl_fork()) { - return; // Parent - } - - fclose(STDIN); // Close all of the standard - - // Enabling this seem to block a running php process with 100% CPU usage when there is an outpout - // fclose(STDOUT); // file descriptors as we - // fclose(STDERR); // are running as a daemon. - DBA::disconnect(); + // Fork a daemon process + $pid = pcntl_fork(); + if ($pid == -1) { + echo "Daemon couldn't be forked.\n"; + Logger::warning('Could not fork daemon'); + exit(1); + } elseif ($pid) { + // The parent process continues here + echo 'Child process started with pid ' . $pid . ".\n"; + Logger::notice('Child process started', ['pid' => $pid]); + file_put_contents($pidfile, $pid); + exit(0); + } + + // We now are in the child process register_shutdown_function('shutdown'); + // Make the child the main process, detach it from the terminal if (posix_setsid() < 0) { return; } - if ($pid = pcntl_fork()) { - return; // Parent - } + // Closing all existing connections with the outside + fclose(STDIN); - $pid = getmypid(); - file_put_contents($pidfile, $pid); - - // We lose the database connection upon forking - DBA::reconnect(); + // And now connect the database again + DBA::connect(); } DI::config()->set('system', 'worker_daemon_mode', true); diff --git a/bin/worker.php b/bin/worker.php index 5698cf16dd..52400a045a 100755 --- a/bin/worker.php +++ b/bin/worker.php @@ -28,7 +28,7 @@ if (php_sapi_name() !== 'cli') { use Dice\Dice; use Friendica\App; -use Friendica\Core\Process; +use Friendica\App\Mode; use Friendica\Core\Update; use Friendica\Core\Worker; use Friendica\DI; @@ -59,6 +59,8 @@ $dice = $dice->addRule(LoggerInterface::class,['constructParams' => ['worker']]) DI::init($dice); $a = DI::app(); +DI::mode()->setExecutor(Mode::WORKER); + // Check the database structure and possibly fixes it Update::check($a->getBasePath(), true, DI::mode()); diff --git a/index.php b/index.php index fdb15fdd88..baa6818b09 100644 --- a/index.php +++ b/index.php @@ -36,6 +36,8 @@ $dice = $dice->addRule(Friendica\App\Mode::class, ['call' => [['determineRunMode $a = \Friendica\DI::app(); +\Friendica\DI::mode()->setExecutor(\Friendica\App\Mode::INDEX); + $a->runFrontend( $dice->create(\Friendica\App\Module::class), $dice->create(\Friendica\App\Router::class), diff --git a/src/App/Mode.php b/src/App/Mode.php index e19de8f07f..8aa812c93d 100644 --- a/src/App/Mode.php +++ b/src/App/Mode.php @@ -38,6 +38,11 @@ class Mode const DBCONFIGAVAILABLE = 4; const MAINTENANCEDISABLED = 8; + const UNDEFINED = 0; + const INDEX = 1; + const DAEMON = 2; + const WORKER = 3; + const BACKEND_CONTENT_TYPES = ['application/jrd+json', 'text/xml', 'application/rss+xml', 'application/atom+xml', 'application/activity+json']; @@ -47,6 +52,12 @@ class Mode */ private $mode; + /*** + * @var int Who executes this Application + * + */ + private $executor = self::UNDEFINED; + /** * @var bool True, if the call is a backend call */ @@ -163,6 +174,31 @@ class Mode return ($this->mode & $mode) > 0; } + /** + * Set the execution mode + * + * @param integer $executor Execution Mode + * @return void + */ + public function setExecutor(int $executor) + { + $this->executor = $executor; + + // Daemon and worker are always backend + if (in_array($executor, [self::DAEMON, self::WORKER])) { + $this->isBackend = true; + } + } + + /*isBackend = true;* + * get the execution mode + * + * @return int Execution Mode + */ + public function getExecutor() + { + return $this->executor; + } /** * Install mode is when the local config file is missing or the DB schema hasn't been installed yet. diff --git a/src/Core/Worker.php b/src/Core/Worker.php index 2f39a82fe5..e90747e41a 100644 --- a/src/Core/Worker.php +++ b/src/Core/Worker.php @@ -21,6 +21,7 @@ namespace Friendica\Core; +use Friendica\App\Mode; use Friendica\Core; use Friendica\Database\DBA; use Friendica\DI; @@ -1180,6 +1181,44 @@ class Worker self::killStaleWorkers(); } + /** + * Fork a child process + * + * @param boolean $do_cron + * @return void + */ + private static function forkProcess(bool $do_cron) + { + // Children inherit their parent's database connection. + // To avoid problems we disconnect and connect both parent and child + DBA::disconnect(); + $pid = pcntl_fork(); + if ($pid == -1) { + DBA::connect(); + Logger::warning('Could not spawn worker'); + return; + } elseif ($pid) { + // The parent process continues here + DBA::connect(); + Logger::info('Spawned new worker', ['cron' => $do_cron, 'pid' => $pid]); + return; + } + + // We now are in the new worker + DBA::connect(); + Logger::info('Worker spawned', ['cron' => $do_cron, 'pid' => getmypid()]); + + DI::process()->start(); + + self::processQueue($do_cron); + + self::unclaimProcess(); + + DI::process()->end(); + Logger::info('Worker ended', ['cron' => $do_cron, 'pid' => getmypid()]); + exit(); + } + /** * Spawns a new worker * @@ -1189,13 +1228,15 @@ class Worker */ public static function spawnWorker($do_cron = false) { - $command = 'bin/worker.php'; - - $args = ['no_cron' => !$do_cron]; - - $a = DI::app(); - $process = new Core\Process(DI::logger(), DI::mode(), DI::config(), DI::modelProcess(), $a->getBasePath(), getmypid()); - $process->run($command, $args); + // Worker and daemon are started from the command line. + // This means that this is executed by a PHP interpreter without runtime limitations + if (in_array(DI::mode()->getExecutor(), [Mode::DAEMON, Mode::WORKER])) { + self::forkProcess($do_cron); + } else { + $process = new Core\Process(DI::logger(), DI::mode(), DI::config(), + DI::modelProcess(), DI::app()->getBasePath(), getmypid()); + $process->run('bin/worker.php', ['no_cron' => !$do_cron]); + } // after spawning we have to remove the flag. if (DI::config()->get('system', 'worker_daemon_mode', false)) {