diff options
Diffstat (limited to 'lib/public/BackgroundJob')
-rw-r--r-- | lib/public/BackgroundJob/IJob.php | 96 | ||||
-rw-r--r-- | lib/public/BackgroundJob/IJobList.php | 171 | ||||
-rw-r--r-- | lib/public/BackgroundJob/IParallelAwareJob.php | 28 | ||||
-rw-r--r-- | lib/public/BackgroundJob/Job.php | 146 | ||||
-rw-r--r-- | lib/public/BackgroundJob/QueuedJob.php | 45 | ||||
-rw-r--r-- | lib/public/BackgroundJob/TimedJob.php | 100 |
6 files changed, 586 insertions, 0 deletions
diff --git a/lib/public/BackgroundJob/IJob.php b/lib/public/BackgroundJob/IJob.php new file mode 100644 index 00000000000..28a7df1c377 --- /dev/null +++ b/lib/public/BackgroundJob/IJob.php @@ -0,0 +1,96 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OCP\BackgroundJob; + +use OCP\ILogger; + +/** + * This interface represents a background job run with cron + * + * To implement a background job, you must extend either \OCP\BackgroundJob\Job, + * \OCP\BackgroundJob\TimedJob or \OCP\BackgroundJob\QueuedJob + * + * @since 7.0.0 + */ +interface IJob { + /** + * @since 24.0.0 + */ + public const TIME_INSENSITIVE = 0; + /** + * @since 24.0.0 + */ + public const TIME_SENSITIVE = 1; + + /** + * Run the background job with the registered argument + * + * @param IJobList $jobList The job list that manages the state of this job + * @param ILogger|null $logger + * @since 7.0.0 + * @deprecated 25.0.0 Use start() instead. This method will be removed + * with the ILogger interface + */ + public function execute(IJobList $jobList, ?ILogger $logger = null); + + /** + * Start the background job with the registered argument + * + * This methods will take care of running the background job, of initializing + * the state and cleaning up the job list after running the job. + * + * For common background job scenario, you will want to use TimedJob or QueuedJob + * instead of overwritting this method. + * + * @param IJobList $jobList The job list that manages the state of this job + * @since 25.0.0 + */ + public function start(IJobList $jobList): void; + + /** + * @since 7.0.0 + */ + public function setId(int $id); + + /** + * @since 7.0.0 + */ + public function setLastRun(int $lastRun); + + /** + * @param mixed $argument + * @since 7.0.0 + */ + public function setArgument($argument); + + /** + * Get the id of the background job + * This id is determined by the job list when a job is added to the list + * + * @return int + * @since 7.0.0 + */ + public function getId(); + + /** + * Get the last time this job was run as unix timestamp + * + * @return int + * @since 7.0.0 + */ + public function getLastRun(); + + /** + * Get the argument associated with the background job + * This is the argument that will be passed to the background job + * + * @return mixed + * @since 7.0.0 + */ + public function getArgument(); +} diff --git a/lib/public/BackgroundJob/IJobList.php b/lib/public/BackgroundJob/IJobList.php new file mode 100644 index 00000000000..c082ef22f2f --- /dev/null +++ b/lib/public/BackgroundJob/IJobList.php @@ -0,0 +1,171 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OCP\BackgroundJob; + +/** + * Interface IJobList + * + * This interface provides functions to register background jobs + * + * To create a new background job create a new class that inherits from either + * \OCP\BackgroundJob\Job, \OCP\BackgroundJob\QueuedJob or + * \OCP\BackgroundJob\TimedJob and register it using ->add($job, $argument), + * $argument will be passed to the run() function of the job when the job is + * executed. + * + * A regular job will be executed every time cron.php is run, a QueuedJob will + * only run once and a TimedJob will only run at a specific interval which is to + * be specified in the constructor of the job by calling + * $this->setInterval($interval) with $interval in seconds. + * + * This interface should be used directly and not implemented by an application. + * The implementation is provided by the server. + * + * @since 7.0.0 + */ +interface IJobList { + /** + * Add a job to the list + * + * @param IJob|class-string<IJob> $job + * @param mixed $argument The argument to be passed to $job->run() when the job is executed + * @since 7.0.0 + */ + public function add($job, $argument = null): void; + + /** + * Add a job to the list but only run it after the given timestamp + * + * For cron background jobs this means the job will likely run shortly after the timestamp + * has been reached. For ajax background jobs the job might only run when users are active + * on the instance again. + * + * @param class-string<IJob> $job + * @param mixed $argument The serializable argument to be passed to $job->run() when the job is executed + * @since 28.0.0 + */ + public function scheduleAfter(string $job, int $runAfter, $argument = null): void; + + /** + * Remove a job from the list + * + * @param IJob|class-string<IJob> $job + * @param mixed $argument + * @since 7.0.0 + */ + public function remove($job, $argument = null): void; + + /** + * Remove a job from the list by id + * + * @param int $id + * @since 30.0.0 + */ + public function removeById(int $id): void; + + /** + * check if a job is in the list + * + * @param IJob|class-string<IJob> $job + * @param mixed $argument + * @since 7.0.0 + */ + public function has($job, $argument): bool; + + /** + * Get jobs matching the search + * + * @param IJob|class-string<IJob>|null $job + * @return array<IJob> + * @since 25.0.0 + * @deprecated 26.0.0 Use getJobsIterator instead to avoid duplicated job objects + */ + public function getJobs($job, ?int $limit, int $offset): array; + + /** + * Get jobs matching the search + * + * @param IJob|class-string<IJob>|null $job + * @return iterable<IJob> + * @since 26.0.0 + */ + public function getJobsIterator($job, ?int $limit, int $offset): iterable; + + /** + * Get the next job in the list + * + * @param bool $onlyTimeSensitive Whether we get only time sensitive jobs or not + * @param class-string<IJob>[]|null $jobClasses List of job classes to restrict which next job we get + * @return ?IJob the next job to run. Beware that this object may be a singleton and may be modified by the next call to buildJob. + * @since 7.0.0 - In 24.0.0 parameter $onlyTimeSensitive got added; In 30.0.0 parameter $jobClasses got added + */ + public function getNext(bool $onlyTimeSensitive = false, ?array $jobClasses = null): ?IJob; + + /** + * @since 7.0.0 + */ + public function getById(int $id): ?IJob; + + /** + * @since 23.0.0 + */ + public function getDetailsById(int $id): ?array; + + /** + * set the job that was last ran to the current time + * + * @since 7.0.0 + */ + public function setLastJob(IJob $job): void; + + /** + * Remove the reservation for a job + * + * @since 9.1.0 + */ + public function unlockJob(IJob $job): void; + + /** + * set the lastRun of $job to now + * + * @since 7.0.0 + */ + public function setLastRun(IJob $job): void; + + /** + * set the run duration of $job + * + * @since 12.0.0 + */ + public function setExecutionTime(IJob $job, int $timeTaken): void; + + /** + * Reset the $job so it executes on the next trigger + * + * @since 23.0.0 + */ + public function resetBackgroundJob(IJob $job): void; + + /** + * Checks whether a job of the passed class was reserved to run + * in the last 6h + * + * @param string|null $className + * @return bool + * @since 27.0.0 + */ + public function hasReservedJob(?string $className): bool; + + /** + * Returns a count of jobs per Job class + * + * @return list<array{class:class-string, count:int}> + * @since 30.0.0 + */ + public function countByClass(): array; +} diff --git a/lib/public/BackgroundJob/IParallelAwareJob.php b/lib/public/BackgroundJob/IParallelAwareJob.php new file mode 100644 index 00000000000..d69d897cb0c --- /dev/null +++ b/lib/public/BackgroundJob/IParallelAwareJob.php @@ -0,0 +1,28 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\BackgroundJob; + +/** + * @since 27.0.0 + */ +interface IParallelAwareJob { + /** + * Set this to false to prevent two Jobs from the same class from running in parallel + * + * @param bool $allow + * @return void + * @since 27.0.0 + */ + public function setAllowParallelRuns(bool $allow): void; + + /** + * @return bool + * @since 27.0.0 + */ + public function getAllowParallelRuns(): bool; +} diff --git a/lib/public/BackgroundJob/Job.php b/lib/public/BackgroundJob/Job.php new file mode 100644 index 00000000000..2483387a9c9 --- /dev/null +++ b/lib/public/BackgroundJob/Job.php @@ -0,0 +1,146 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\BackgroundJob; + +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\ILogger; +use Psr\Log\LoggerInterface; + +/** + * Base class for background jobs + * + * This is here if you want to do advanced stuff in your background jobs. + * For the most common use cases have a look at QueuedJob and TimedJob + * + * @since 15.0.0 + */ +abstract class Job implements IJob, IParallelAwareJob { + protected int $id = 0; + protected int $lastRun = 0; + protected $argument; + protected ITimeFactory $time; + protected bool $allowParallelRuns = true; + + /** + * @since 15.0.0 + */ + public function __construct(ITimeFactory $time) { + $this->time = $time; + } + + /** + * The function to prepare the execution of the job. + * + * @return void + * + * @since 15.0.0 + * @deprecated 25.0.0 Use start() instead. This method will be removed + * with the ILogger interface + */ + public function execute(IJobList $jobList, ?ILogger $logger = null) { + $this->start($jobList); + } + + /** + * @inheritdoc + * @since 25.0.0 + */ + public function start(IJobList $jobList): void { + $jobList->setLastRun($this); + $logger = \OCP\Server::get(LoggerInterface::class); + + try { + $jobDetails = get_class($this) . ' (id: ' . $this->getId() . ', arguments: ' . json_encode($this->getArgument()) . ')'; + $jobStartTime = $this->time->getTime(); + $logger->debug('Starting job ' . $jobDetails, ['app' => 'cron']); + $this->run($this->argument); + $timeTaken = $this->time->getTime() - $jobStartTime; + + $logger->debug('Finished job ' . $jobDetails . ' in ' . $timeTaken . ' seconds', ['app' => 'cron']); + $jobList->setExecutionTime($this, $timeTaken); + } catch (\Throwable $e) { + if ($logger) { + $logger->error('Error while running background job ' . $jobDetails, [ + 'app' => 'core', + 'exception' => $e, + ]); + } + } + } + + /** + * @since 15.0.0 + */ + final public function setId(int $id) { + $this->id = $id; + } + + /** + * @since 15.0.0 + */ + final public function setLastRun(int $lastRun) { + $this->lastRun = $lastRun; + } + + /** + * @since 15.0.0 + */ + public function setArgument($argument) { + $this->argument = $argument; + } + + /** + * @since 15.0.0 + */ + final public function getId(): int { + return $this->id; + } + + /** + * @since 15.0.0 + */ + final public function getLastRun(): int { + return $this->lastRun; + } + + /** + * @since 15.0.0 + */ + public function getArgument() { + return $this->argument; + } + + /** + * Set this to false to prevent two Jobs from this class from running in parallel + * + * @param bool $allow + * @return void + * @since 27.0.0 + */ + public function setAllowParallelRuns(bool $allow): void { + $this->allowParallelRuns = $allow; + } + + /** + * @return bool + * @since 27.0.0 + */ + public function getAllowParallelRuns(): bool { + return $this->allowParallelRuns; + } + + /** + * The actual function that is called to run the job + * + * @param $argument + * @return void + * + * @since 15.0.0 + */ + abstract protected function run($argument); +} diff --git a/lib/public/BackgroundJob/QueuedJob.php b/lib/public/BackgroundJob/QueuedJob.php new file mode 100644 index 00000000000..75e27d1d60f --- /dev/null +++ b/lib/public/BackgroundJob/QueuedJob.php @@ -0,0 +1,45 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\BackgroundJob; + +use OCP\ILogger; + +/** + * Simple base class for a one time background job + * + * @since 15.0.0 + */ +abstract class QueuedJob extends Job { + /** + * Run the job, then remove it from the joblist + * + * @param IJobList $jobList + * @param ILogger|null $logger + * + * @since 15.0.0 + * @deprecated 25.0.0 Use start() instead. This method will be removed + * with the ILogger interface + */ + final public function execute($jobList, ?ILogger $logger = null) { + $this->start($jobList); + } + + /** + * Run the job, then remove it from the joblist + * + * @since 25.0.0 + */ + final public function start(IJobList $jobList): void { + if ($this->id) { + $jobList->removeById($this->id); + } else { + $jobList->remove($this, $this->argument); + } + parent::start($jobList); + } +} diff --git a/lib/public/BackgroundJob/TimedJob.php b/lib/public/BackgroundJob/TimedJob.php new file mode 100644 index 00000000000..486c03c5fda --- /dev/null +++ b/lib/public/BackgroundJob/TimedJob.php @@ -0,0 +1,100 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\BackgroundJob; + +use OCP\ILogger; +use OCP\Server; +use Psr\Log\LoggerInterface; + +/** + * Simple base class to extend to run periodic background jobs. + * Call setInterval with your desired interval in seconds from the constructor. + * + * @since 15.0.0 + */ +abstract class TimedJob extends Job { + protected int $interval = 0; + protected int $timeSensitivity = IJob::TIME_SENSITIVE; + + /** + * Set the interval for the job + * + * @param int $seconds the time to pass between two runs of the same job in seconds + * + * @since 15.0.0 + */ + public function setInterval(int $seconds) { + $this->interval = $seconds; + } + + /** + * Get the interval [seconds] for the job + * + * @since 32.0.0 + */ + public function getInterval(): int { + return $this->interval; + } + + /** + * Whether the background job is time sensitive and needs to run soon after + * the scheduled interval, of if it is okay to be delayed until a later time. + * + * @return bool + * @since 24.0.0 + */ + public function isTimeSensitive(): bool { + return $this->timeSensitivity === IJob::TIME_SENSITIVE; + } + + /** + * If your background job is not time sensitive (sending instant email + * notifications, etc.) it would be nice to set it to IJob::TIME_INSENSITIVE + * This way the execution can be delayed during high usage times. + * + * @param int $sensitivity + * @psalm-param IJob::TIME_* $sensitivity + * @return void + * @since 24.0.0 + */ + public function setTimeSensitivity(int $sensitivity): void { + if ($sensitivity !== self::TIME_SENSITIVE + && $sensitivity !== self::TIME_INSENSITIVE) { + throw new \InvalidArgumentException('Invalid sensitivity'); + } + + $this->timeSensitivity = $sensitivity; + } + + /** + * Run the job if the last run is more than the interval ago + * + * @param IJobList $jobList + * @param ILogger|null $logger + * + * @since 15.0.0 + * @deprecated 25.0.0 Use start() instead + */ + final public function execute(IJobList $jobList, ?ILogger $logger = null) { + $this->start($jobList); + } + + /** + * Run the job if the last run is more than the interval ago + * + * @since 25.0.0 + */ + final public function start(IJobList $jobList): void { + if (($this->time->getTime() - $this->lastRun) > $this->interval) { + if ($this->interval >= 12 * 60 * 60 && $this->isTimeSensitive()) { + Server::get(LoggerInterface::class)->debug('TimedJob ' . get_class($this) . ' has a configured interval of ' . $this->interval . ' seconds, but is also marked as time sensitive. Please consider marking it as time insensitive to allow more sensitive jobs to run when needed.'); + } + parent::start($jobList); + } + } +} |