You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

265 lines
8.0 KiB

13 years ago
12 years ago
13 years ago
11 years ago
  1. <?php
  2. declare(strict_types=1);
  3. use OC\Files\SetupManager;
  4. use OC\Session\CryptoWrapper;
  5. use OC\Session\Memory;
  6. use OCP\ILogger;
  7. /**
  8. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  9. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  10. * SPDX-License-Identifier: AGPL-3.0-only
  11. */
  12. require_once __DIR__ . '/lib/versioncheck.php';
  13. use OCP\App\IAppManager;
  14. use OCP\BackgroundJob\IJobList;
  15. use OCP\IAppConfig;
  16. use OCP\IConfig;
  17. use OCP\ISession;
  18. use OCP\ITempManager;
  19. use OCP\Server;
  20. use OCP\Util;
  21. use Psr\Log\LoggerInterface;
  22. try {
  23. require_once __DIR__ . '/lib/base.php';
  24. if (isset($argv[1]) && ($argv[1] === '-h' || $argv[1] === '--help')) {
  25. echo 'Description:
  26. Run the background job routine
  27. Usage:
  28. php -f cron.php -- [-h] [--verbose] [<job-classes>...]
  29. Arguments:
  30. job-classes Optional job class list to only run those jobs
  31. Providing a class will ignore the time-sensitivity restriction
  32. Options:
  33. -h, --help Display this help message
  34. -v, --verbose Output more information' . PHP_EOL;
  35. exit(0);
  36. }
  37. if (Util::needUpgrade()) {
  38. Server::get(LoggerInterface::class)->debug('Update required, skipping cron', ['app' => 'cron']);
  39. exit;
  40. }
  41. $config = Server::get(IConfig::class);
  42. if ($config->getSystemValueBool('maintenance', false)) {
  43. Server::get(LoggerInterface::class)->debug('We are in maintenance mode, skipping cron', ['app' => 'cron']);
  44. exit;
  45. }
  46. // Don't do anything if Nextcloud has not been installed
  47. if (!$config->getSystemValueBool('installed', false)) {
  48. exit(0);
  49. }
  50. // load all apps to get all api routes properly setup
  51. Server::get(IAppManager::class)->loadApps();
  52. Server::get(ISession::class)->close();
  53. $verbose = isset($argv[1]) && ($argv[1] === '-v' || $argv[1] === '--verbose');
  54. // initialize a dummy memory session
  55. $session = new Memory();
  56. $cryptoWrapper = Server::get(CryptoWrapper::class);
  57. $session = $cryptoWrapper->wrapSession($session);
  58. \OC::$server->setSession($session);
  59. $logger = Server::get(LoggerInterface::class);
  60. $appConfig = Server::get(IAppConfig::class);
  61. $tempManager = Server::get(ITempManager::class);
  62. $tempManager->cleanOld();
  63. // Exit if background jobs are disabled!
  64. $appMode = $appConfig->getValueString('core', 'backgroundjobs_mode', 'ajax');
  65. if ($appMode === 'none') {
  66. if (OC::$CLI) {
  67. echo 'Background Jobs are disabled!' . PHP_EOL;
  68. } else {
  69. OC_JSON::error(['data' => ['message' => 'Background jobs disabled!']]);
  70. }
  71. exit(1);
  72. }
  73. if (OC::$CLI) {
  74. // set to run indefinitely if needed
  75. if (strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
  76. @set_time_limit(0);
  77. }
  78. // the cron job must be executed with the right user
  79. if (!function_exists('posix_getuid')) {
  80. echo 'The posix extensions are required - see https://www.php.net/manual/en/book.posix.php' . PHP_EOL;
  81. exit(1);
  82. }
  83. $user = posix_getuid();
  84. $configUser = fileowner(OC::$configDir . 'config.php');
  85. if ($user !== $configUser) {
  86. echo 'Console has to be executed with the user that owns the file config/config.php' . PHP_EOL;
  87. echo 'Current user id: ' . $user . PHP_EOL;
  88. echo 'Owner id of config.php: ' . $configUser . PHP_EOL;
  89. exit(1);
  90. }
  91. // We call Nextcloud from the CLI (aka cron)
  92. if ($appMode !== 'cron') {
  93. $appConfig->setValueString('core', 'backgroundjobs_mode', 'cron');
  94. }
  95. // a specific job class list can optionally be given as argument
  96. $jobClasses = array_slice($argv, $verbose ? 2 : 1);
  97. $jobClasses = empty($jobClasses) ? null : $jobClasses;
  98. // Low-load hours
  99. $onlyTimeSensitive = false;
  100. $startHour = $config->getSystemValueInt('maintenance_window_start', 100);
  101. if ($jobClasses === null && $startHour <= 23) {
  102. $date = new \DateTime('now', new \DateTimeZone('UTC'));
  103. $currentHour = (int)$date->format('G');
  104. $endHour = $startHour + 4;
  105. if ($startHour <= 20) {
  106. // Start time: 01:00
  107. // End time: 05:00
  108. // Only run sensitive tasks when it's before the start or after the end
  109. $onlyTimeSensitive = $currentHour < $startHour || $currentHour > $endHour;
  110. } else {
  111. // Start time: 23:00
  112. // End time: 03:00
  113. $endHour -= 24; // Correct the end time from 27:00 to 03:00
  114. // Only run sensitive tasks when it's after the end and before the start
  115. $onlyTimeSensitive = $currentHour > $endHour && $currentHour < $startHour;
  116. }
  117. }
  118. // Work
  119. $jobList = Server::get(IJobList::class);
  120. // We only ask for jobs for 14 minutes, because after 5 minutes the next
  121. // system cron task should spawn and we want to have at most three
  122. // cron jobs running in parallel.
  123. $endTime = time() + 14 * 60;
  124. $executedJobs = [];
  125. while ($job = $jobList->getNext($onlyTimeSensitive, $jobClasses)) {
  126. if (isset($executedJobs[$job->getId()])) {
  127. $jobList->unlockJob($job);
  128. break;
  129. }
  130. $jobDetails = get_class($job) . ' (id: ' . $job->getId() . ', arguments: ' . json_encode($job->getArgument()) . ')';
  131. $logger->debug('CLI cron call has selected job ' . $jobDetails, ['app' => 'cron']);
  132. $timeBefore = time();
  133. $memoryBefore = memory_get_usage();
  134. $memoryPeakBefore = memory_get_peak_usage();
  135. if ($verbose) {
  136. echo 'Starting job ' . $jobDetails . PHP_EOL;
  137. }
  138. /** @psalm-suppress DeprecatedMethod Calling execute until it is removed, then will switch to start */
  139. $job->execute($jobList);
  140. $timeAfter = time();
  141. $memoryAfter = memory_get_usage();
  142. $memoryPeakAfter = memory_get_peak_usage();
  143. $cronInterval = 5 * 60;
  144. $timeSpent = $timeAfter - $timeBefore;
  145. if ($timeSpent > $cronInterval) {
  146. $logLevel = match (true) {
  147. $timeSpent > $cronInterval * 128 => ILogger::FATAL,
  148. $timeSpent > $cronInterval * 64 => ILogger::ERROR,
  149. $timeSpent > $cronInterval * 16 => ILogger::WARN,
  150. $timeSpent > $cronInterval * 8 => ILogger::INFO,
  151. default => ILogger::DEBUG,
  152. };
  153. $logger->log(
  154. $logLevel,
  155. 'Background job ' . $jobDetails . ' ran for ' . $timeSpent . ' seconds',
  156. ['app' => 'cron']
  157. );
  158. }
  159. if ($memoryAfter - $memoryBefore > 50_000_000) {
  160. $message = 'Used memory grew by more than 50 MB when executing job ' . $jobDetails . ': ' . Util::humanFileSize($memoryAfter) . ' (before: ' . Util::humanFileSize($memoryBefore) . ')';
  161. $logger->warning($message, ['app' => 'cron']);
  162. if ($verbose) {
  163. echo $message . PHP_EOL;
  164. }
  165. }
  166. if ($memoryPeakAfter > 300_000_000 && $memoryPeakBefore <= 300_000_000) {
  167. $message = 'Cron job used more than 300 MB of ram after executing job ' . $jobDetails . ': ' . Util::humanFileSize($memoryPeakAfter) . ' (before: ' . Util::humanFileSize($memoryPeakBefore) . ')';
  168. $logger->warning($message, ['app' => 'cron']);
  169. if ($verbose) {
  170. echo $message . PHP_EOL;
  171. }
  172. }
  173. // clean up after unclean jobs
  174. Server::get(SetupManager::class)->tearDown();
  175. $tempManager->clean();
  176. if ($verbose) {
  177. echo 'Job ' . $jobDetails . ' done in ' . ($timeAfter - $timeBefore) . ' seconds' . PHP_EOL;
  178. }
  179. $jobList->setLastJob($job);
  180. $executedJobs[$job->getId()] = true;
  181. unset($job);
  182. if ($timeAfter > $endTime) {
  183. break;
  184. }
  185. }
  186. } else {
  187. // We call cron.php from some website
  188. if ($appMode === 'cron') {
  189. // Cron is cron :-P
  190. OC_JSON::error(['data' => ['message' => 'Backgroundjobs are using system cron!']]);
  191. } else {
  192. // Work and success :-)
  193. $jobList = Server::get(IJobList::class);
  194. $job = $jobList->getNext();
  195. if ($job != null) {
  196. $logger->debug('WebCron call has selected job with ID ' . strval($job->getId()), ['app' => 'cron']);
  197. /** @psalm-suppress DeprecatedMethod Calling execute until it is removed, then will switch to start */
  198. $job->execute($jobList);
  199. $jobList->setLastJob($job);
  200. }
  201. OC_JSON::success();
  202. }
  203. }
  204. // Log the successful cron execution
  205. $appConfig->setValueInt('core', 'lastcron', time());
  206. exit();
  207. } catch (Exception $ex) {
  208. Server::get(LoggerInterface::class)->error(
  209. $ex->getMessage(),
  210. ['app' => 'cron', 'exception' => $ex]
  211. );
  212. echo $ex . PHP_EOL;
  213. exit(1);
  214. } catch (Error $ex) {
  215. Server::get(LoggerInterface::class)->error(
  216. $ex->getMessage(),
  217. ['app' => 'cron', 'exception' => $ex]
  218. );
  219. echo $ex . PHP_EOL;
  220. exit(1);
  221. }