aboutsummaryrefslogtreecommitdiffstats
path: root/lib/public/BackgroundJob
diff options
context:
space:
mode:
Diffstat (limited to 'lib/public/BackgroundJob')
-rw-r--r--lib/public/BackgroundJob/IJob.php96
-rw-r--r--lib/public/BackgroundJob/IJobList.php171
-rw-r--r--lib/public/BackgroundJob/IParallelAwareJob.php28
-rw-r--r--lib/public/BackgroundJob/Job.php146
-rw-r--r--lib/public/BackgroundJob/QueuedJob.php45
-rw-r--r--lib/public/BackgroundJob/TimedJob.php100
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);
+ }
+ }
+}