aboutsummaryrefslogtreecommitdiffstats
path: root/cron.php
diff options
context:
space:
mode:
Diffstat (limited to 'cron.php')
-rw-r--r--cron.php297
1 files changed, 192 insertions, 105 deletions
diff --git a/cron.php b/cron.php
index 73f233e1350..0501c53ff40 100644
--- a/cron.php
+++ b/cron.php
@@ -1,166 +1,244 @@
<?php
+
+declare(strict_types=1);
+
+use OC\Files\SetupManager;
+use OC\Session\CryptoWrapper;
+use OC\Session\Memory;
+use OCP\ILogger;
+
/**
- * @author Bernhard Posselt <dev@bernhard-posselt.com>
- * @author Christopher Schäpers <kondou@ts.unde.re>
- * @author Jakob Sack <mail@jakobsack.de>
- * @author Joas Schilling <nickvergessen@owncloud.com>
- * @author Jörn Friedrich Dreyer <jfd@butonic.de>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Oliver Kohl D.Sc. <oliver@kohl.bz>
- * @author Phil Davis <phil.davis@inf.org>
- * @author Robin Appelman <icewind@owncloud.com>
- * @author Steffen Lindner <mail@steffen-lindner.de>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- * @author Vincent Petry <pvince81@owncloud.com>
- *
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
+require_once __DIR__ . '/lib/versioncheck.php';
+
+use OCP\App\IAppManager;
+use OCP\BackgroundJob\IJobList;
+use OCP\IAppConfig;
+use OCP\IConfig;
+use OCP\ISession;
+use OCP\ITempManager;
+use OCP\Server;
+use OCP\Util;
+use Psr\Log\LoggerInterface;
+
try {
+ require_once __DIR__ . '/lib/base.php';
- require_once 'lib/base.php';
+ if (isset($argv[1]) && ($argv[1] === '-h' || $argv[1] === '--help')) {
+ echo 'Description:
+ Run the background job routine
- if (\OCP\Util::needUpgrade()) {
- \OCP\Util::writeLog('cron', 'Update required, skipping cron', \OCP\Util::DEBUG);
- exit;
+Usage:
+ php -f cron.php -- [-h] [--verbose] [<job-classes>...]
+
+Arguments:
+ job-classes Optional job class list to only run those jobs
+ Providing a class will ignore the time-sensitivity restriction
+
+Options:
+ -h, --help Display this help message
+ -v, --verbose Output more information' . PHP_EOL;
+ exit(0);
}
- if (\OC::$server->getSystemConfig()->getValue('maintenance', false)) {
- \OCP\Util::writeLog('cron', 'We are in maintenance mode, skipping cron', \OCP\Util::DEBUG);
+
+ if (Util::needUpgrade()) {
+ Server::get(LoggerInterface::class)->debug('Update required, skipping cron', ['app' => 'cron']);
exit;
}
- if (\OC::$server->getSystemConfig()->getValue('singleuser', false)) {
- \OCP\Util::writeLog('cron', 'We are in admin only mode, skipping cron', \OCP\Util::DEBUG);
+ $config = Server::get(IConfig::class);
+
+ if ($config->getSystemValueBool('maintenance', false)) {
+ Server::get(LoggerInterface::class)->debug('We are in maintenance mode, skipping cron', ['app' => 'cron']);
exit;
}
+ // Don't do anything if Nextcloud has not been installed
+ if (!$config->getSystemValueBool('installed', false)) {
+ exit(0);
+ }
+
// load all apps to get all api routes properly setup
- OC_App::loadApps();
+ Server::get(IAppManager::class)->loadApps();
+ Server::get(ISession::class)->close();
- \OC::$server->getSession()->close();
+ $verbose = isset($argv[1]) && ($argv[1] === '-v' || $argv[1] === '--verbose');
// initialize a dummy memory session
- $session = new \OC\Session\Memory('');
- $cryptoWrapper = \OC::$server->getSessionCryptoWrapper();
+ $session = new Memory();
+ $cryptoWrapper = Server::get(CryptoWrapper::class);
$session = $cryptoWrapper->wrapSession($session);
\OC::$server->setSession($session);
- $logger = \OC::$server->getLogger();
- $config = \OC::$server->getConfig();
-
- // Don't do anything if ownCloud has not been installed
- if (!$config->getSystemValue('installed', false)) {
- exit(0);
- }
+ $logger = Server::get(LoggerInterface::class);
+ $appConfig = Server::get(IAppConfig::class);
+ $tempManager = Server::get(ITempManager::class);
- \OC::$server->getTempManager()->cleanOld();
+ $tempManager->cleanOld();
// Exit if background jobs are disabled!
- $appMode = \OCP\BackgroundJob::getExecutionType();
- if ($appMode == 'none') {
+ $appMode = $appConfig->getValueString('core', 'backgroundjobs_mode', 'ajax');
+ if ($appMode === 'none') {
if (OC::$CLI) {
echo 'Background Jobs are disabled!' . PHP_EOL;
} else {
- OC_JSON::error(array('data' => array('message' => 'Background jobs disabled!')));
+ OC_JSON::error(['data' => ['message' => 'Background jobs disabled!']]);
}
exit(1);
}
if (OC::$CLI) {
// set to run indefinitely if needed
- set_time_limit(0);
+ if (strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
+ @set_time_limit(0);
+ }
// the cron job must be executed with the right user
- if (!OC_Util::runningOnWindows()) {
- if (!function_exists('posix_getuid')) {
- echo "The posix extensions are required - see http://php.net/manual/en/book.posix.php" . PHP_EOL;
- exit(0);
- }
- $user = posix_getpwuid(posix_getuid());
- $configUser = posix_getpwuid(fileowner(OC::$SERVERROOT . '/config/config.php'));
- if ($user['name'] !== $configUser['name']) {
- echo "Console has to be executed with the same user as the web server is operated" . PHP_EOL;
- echo "Current user: " . $user['name'] . PHP_EOL;
- echo "Web server user: " . $configUser['name'] . PHP_EOL;
- exit(0);
- }
+ if (!function_exists('posix_getuid')) {
+ echo 'The posix extensions are required - see https://www.php.net/manual/en/book.posix.php' . PHP_EOL;
+ exit(1);
}
- $instanceId = $config->getSystemValue('instanceid');
- $lockFileName = 'owncloud-server-' . $instanceId . '-cron.lock';
- $lockDirectory = $config->getSystemValue('cron.lockfile.location', sys_get_temp_dir());
- $lockDirectory = rtrim($lockDirectory, '\\/');
- $lockFile = $lockDirectory . '/' . $lockFileName;
-
- if (!file_exists($lockFile)) {
- touch($lockFile);
+ $user = posix_getuid();
+ $configUser = fileowner(OC::$configDir . 'config.php');
+ if ($user !== $configUser) {
+ echo 'Console has to be executed with the user that owns the file config/config.php' . PHP_EOL;
+ echo 'Current user id: ' . $user . PHP_EOL;
+ echo 'Owner id of config.php: ' . $configUser . PHP_EOL;
+ exit(1);
}
- // We call ownCloud from the CLI (aka cron)
- if ($appMode != 'cron') {
- \OCP\BackgroundJob::setExecutionType('cron');
+
+ // We call Nextcloud from the CLI (aka cron)
+ if ($appMode !== 'cron') {
+ $appConfig->setValueString('core', 'backgroundjobs_mode', 'cron');
}
- // open the file and try to lock it. If it is not locked, the background
- // job can be executed, otherwise another instance is already running
- $fp = fopen($lockFile, 'w');
- $isLocked = flock($fp, LOCK_EX|LOCK_NB, $wouldBlock);
+ // a specific job class list can optionally be given as argument
+ $jobClasses = array_slice($argv, $verbose ? 2 : 1);
+ $jobClasses = empty($jobClasses) ? null : $jobClasses;
- // check if backgroundjobs is still running. The wouldBlock check is
- // needed on systems with advisory locking, see
- // http://php.net/manual/en/function.flock.php#45464
- if (!$isLocked || $wouldBlock) {
- echo "Another instance of cron.php is still running!" . PHP_EOL;
- exit(1);
+ // Low-load hours
+ $onlyTimeSensitive = false;
+ $startHour = $config->getSystemValueInt('maintenance_window_start', 100);
+ if ($jobClasses === null && $startHour <= 23) {
+ $date = new \DateTime('now', new \DateTimeZone('UTC'));
+ $currentHour = (int)$date->format('G');
+ $endHour = $startHour + 4;
+
+ if ($startHour <= 20) {
+ // Start time: 01:00
+ // End time: 05:00
+ // Only run sensitive tasks when it's before the start or after the end
+ $onlyTimeSensitive = $currentHour < $startHour || $currentHour > $endHour;
+ } else {
+ // Start time: 23:00
+ // End time: 03:00
+ $endHour -= 24; // Correct the end time from 27:00 to 03:00
+ // Only run sensitive tasks when it's after the end and before the start
+ $onlyTimeSensitive = $currentHour > $endHour && $currentHour < $startHour;
+ }
}
// Work
- $jobList = \OC::$server->getJobList();
+ $jobList = Server::get(IJobList::class);
+
+ // We only ask for jobs for 14 minutes, because after 5 minutes the next
+ // system cron task should spawn and we want to have at most three
+ // cron jobs running in parallel.
+ $endTime = time() + 14 * 60;
$executedJobs = [];
- while ($job = $jobList->getNext()) {
+
+ while ($job = $jobList->getNext($onlyTimeSensitive, $jobClasses)) {
if (isset($executedJobs[$job->getId()])) {
+ $jobList->unlockJob($job);
break;
}
- $logger->debug('Run job with ID ' . $job->getId(), ['app' => 'cron']);
- $job->execute($jobList, $logger);
- $logger->debug('Finished job with ID ' . $job->getId(), ['app' => 'cron']);
+ $jobDetails = get_class($job) . ' (id: ' . $job->getId() . ', arguments: ' . json_encode($job->getArgument()) . ')';
+ $logger->debug('CLI cron call has selected job ' . $jobDetails, ['app' => 'cron']);
+
+ $timeBefore = time();
+ $memoryBefore = memory_get_usage();
+ $memoryPeakBefore = memory_get_peak_usage();
+
+ if ($verbose) {
+ echo 'Starting job ' . $jobDetails . PHP_EOL;
+ }
+
+ /** @psalm-suppress DeprecatedMethod Calling execute until it is removed, then will switch to start */
+ $job->execute($jobList);
+
+ $timeAfter = time();
+ $memoryAfter = memory_get_usage();
+ $memoryPeakAfter = memory_get_peak_usage();
+
+ $cronInterval = 5 * 60;
+ $timeSpent = $timeAfter - $timeBefore;
+ if ($timeSpent > $cronInterval) {
+ $logLevel = match (true) {
+ $timeSpent > $cronInterval * 128 => ILogger::FATAL,
+ $timeSpent > $cronInterval * 64 => ILogger::ERROR,
+ $timeSpent > $cronInterval * 16 => ILogger::WARN,
+ $timeSpent > $cronInterval * 8 => ILogger::INFO,
+ default => ILogger::DEBUG,
+ };
+ $logger->log(
+ $logLevel,
+ 'Background job ' . $jobDetails . ' ran for ' . $timeSpent . ' seconds',
+ ['app' => 'cron']
+ );
+ }
+
+ if ($memoryAfter - $memoryBefore > 50_000_000) {
+ $message = 'Used memory grew by more than 50 MB when executing job ' . $jobDetails . ': ' . Util::humanFileSize($memoryAfter) . ' (before: ' . Util::humanFileSize($memoryBefore) . ')';
+ $logger->warning($message, ['app' => 'cron']);
+ if ($verbose) {
+ echo $message . PHP_EOL;
+ }
+ }
+ if ($memoryPeakAfter > 300_000_000 && $memoryPeakBefore <= 300_000_000) {
+ $message = 'Cron job used more than 300 MB of ram after executing job ' . $jobDetails . ': ' . Util::humanFileSize($memoryPeakAfter) . ' (before: ' . Util::humanFileSize($memoryPeakBefore) . ')';
+ $logger->warning($message, ['app' => 'cron']);
+ if ($verbose) {
+ echo $message . PHP_EOL;
+ }
+ }
+
+ // clean up after unclean jobs
+ Server::get(SetupManager::class)->tearDown();
+ $tempManager->clean();
+
+ if ($verbose) {
+ echo 'Job ' . $jobDetails . ' done in ' . ($timeAfter - $timeBefore) . ' seconds' . PHP_EOL;
+ }
$jobList->setLastJob($job);
$executedJobs[$job->getId()] = true;
unset($job);
- }
-
- // unlock the file
- flock($fp, LOCK_UN);
- fclose($fp);
+ if ($timeAfter > $endTime) {
+ break;
+ }
+ }
} else {
// We call cron.php from some website
- if ($appMode == 'cron') {
+ if ($appMode === 'cron') {
// Cron is cron :-P
- OC_JSON::error(array('data' => array('message' => 'Backgroundjobs are using system cron!')));
+ OC_JSON::error(['data' => ['message' => 'Backgroundjobs are using system cron!']]);
} else {
// Work and success :-)
- $jobList = \OC::$server->getJobList();
+ $jobList = Server::get(IJobList::class);
$job = $jobList->getNext();
if ($job != null) {
- $job->execute($jobList, $logger);
+ $logger->debug('WebCron call has selected job with ID ' . strval($job->getId()), ['app' => 'cron']);
+ /** @psalm-suppress DeprecatedMethod Calling execute until it is removed, then will switch to start */
+ $job->execute($jobList);
$jobList->setLastJob($job);
}
OC_JSON::success();
@@ -168,11 +246,20 @@ try {
}
// Log the successful cron execution
- if (\OC::$server->getConfig()->getSystemValue('cron_log', true)) {
- \OC::$server->getConfig()->setAppValue('core', 'lastcron', time());
- }
+ $appConfig->setValueInt('core', 'lastcron', time());
exit();
-
} catch (Exception $ex) {
- \OCP\Util::writeLog('cron', $ex->getMessage(), \OCP\Util::FATAL);
+ Server::get(LoggerInterface::class)->error(
+ $ex->getMessage(),
+ ['app' => 'cron', 'exception' => $ex]
+ );
+ echo $ex . PHP_EOL;
+ exit(1);
+} catch (Error $ex) {
+ Server::get(LoggerInterface::class)->error(
+ $ex->getMessage(),
+ ['app' => 'cron', 'exception' => $ex]
+ );
+ echo $ex . PHP_EOL;
+ exit(1);
}