feat: Port jobs table to snowflakes ids

Signed-off-by: Carl Schwan <carl.schwan@nextcloud.com>
pull/56628/head
Carl Schwan 2025-11-24 18:11:53 +07:00
parent 31af870ef0
commit 0e686fc6a9
No known key found for this signature in database
GPG Key ID: 02325448204E452A
13 changed files with 147 additions and 178 deletions

@ -45,7 +45,7 @@ class RefreshWebcalJobTest extends TestCase {
#[\PHPUnit\Framework\Attributes\DataProvider('runDataProvider')] #[\PHPUnit\Framework\Attributes\DataProvider('runDataProvider')]
public function testRun(int $lastRun, int $time, bool $process): void { public function testRun(int $lastRun, int $time, bool $process): void {
$backgroundJob = new RefreshWebcalJob($this->refreshWebcalService, $this->config, $this->logger, $this->timeFactory); $backgroundJob = new RefreshWebcalJob($this->refreshWebcalService, $this->config, $this->logger, $this->timeFactory);
$backgroundJob->setId(42); $backgroundJob->setId('42');
$backgroundJob->setArgument([ $backgroundJob->setArgument([
'principaluri' => 'principals/users/testuser', 'principaluri' => 'principals/users/testuser',

@ -35,7 +35,7 @@ class Delete extends Base {
} }
protected function execute(InputInterface $input, OutputInterface $output): int { protected function execute(InputInterface $input, OutputInterface $output): int {
$jobId = (int)$input->getArgument('job-id'); $jobId = (string)$input->getArgument('job-id');
$job = $this->jobList->getById($jobId); $job = $this->jobList->getById($jobId);
if ($job === null) { if ($job === null) {

@ -44,7 +44,7 @@ class Job extends Command {
} }
protected function execute(InputInterface $input, OutputInterface $output): int { protected function execute(InputInterface $input, OutputInterface $output): int {
$jobId = (int)$input->getArgument('job-id'); $jobId = (string)$input->getArgument('job-id');
$job = $this->jobList->getById($jobId); $job = $this->jobList->getById($jobId);
if ($job === null) { if ($job === null) {
@ -87,7 +87,7 @@ class Job extends Command {
return 0; return 0;
} }
protected function printJobInfo(int $jobId, IJob $job, OutputInterface $output): void { protected function printJobInfo(string $jobId, IJob $job, OutputInterface $output): void {
$row = $this->jobList->getDetailsById($jobId); $row = $this->jobList->getDetailsById($jobId);
$lastRun = new \DateTime(); $lastRun = new \DateTime();

@ -27,7 +27,7 @@ abstract class JobBase extends Base {
parent::__construct(); parent::__construct();
} }
protected function printJobInfo(int $jobId, IJob $job, OutputInterface $output): void { protected function printJobInfo(string $jobId, IJob $job, OutputInterface $output): void {
$row = $this->jobList->getDetailsById($jobId); $row = $this->jobList->getDetailsById($jobId);
if ($row === null) { if ($row === null) {

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Migrations;
use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\Attributes\ModifyColumn;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
use Override;
#[ModifyColumn(table: 'jobs', name: 'id', description: 'Remove auto-increment')]
class Version33000Date20251124110529 extends SimpleMigrationStep {
/**
* @param Closure(): ISchemaWrapper $schemaClosure The `\Closure` returns a `ISchemaWrapper`
*/
#[Override]
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
$schema = $schemaClosure();
if ($schema->hasTable('jobs')) {
$schema->dropAutoincrementColumn('jobs', 'id');
}
return $schema;
}
}

@ -1537,6 +1537,7 @@ return array(
'OC\\Core\\Migrations\\Version33000Date20251023110529' => $baseDir . '/core/Migrations/Version33000Date20251023110529.php', 'OC\\Core\\Migrations\\Version33000Date20251023110529' => $baseDir . '/core/Migrations/Version33000Date20251023110529.php',
'OC\\Core\\Migrations\\Version33000Date20251023120529' => $baseDir . '/core/Migrations/Version33000Date20251023120529.php', 'OC\\Core\\Migrations\\Version33000Date20251023120529' => $baseDir . '/core/Migrations/Version33000Date20251023120529.php',
'OC\\Core\\Migrations\\Version33000Date20251106131209' => $baseDir . '/core/Migrations/Version33000Date20251106131209.php', 'OC\\Core\\Migrations\\Version33000Date20251106131209' => $baseDir . '/core/Migrations/Version33000Date20251106131209.php',
'OC\\Core\\Migrations\\Version33000Date20251124110529' => $baseDir . '/core/Migrations/Version33000Date20251124110529.php',
'OC\\Core\\Migrations\\Version33000Date20251126152410' => $baseDir . '/core/Migrations/Version33000Date20251126152410.php', 'OC\\Core\\Migrations\\Version33000Date20251126152410' => $baseDir . '/core/Migrations/Version33000Date20251126152410.php',
'OC\\Core\\Notification\\CoreNotifier' => $baseDir . '/core/Notification/CoreNotifier.php', 'OC\\Core\\Notification\\CoreNotifier' => $baseDir . '/core/Notification/CoreNotifier.php',
'OC\\Core\\ResponseDefinitions' => $baseDir . '/core/ResponseDefinitions.php', 'OC\\Core\\ResponseDefinitions' => $baseDir . '/core/ResponseDefinitions.php',

@ -1578,6 +1578,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Core\\Migrations\\Version33000Date20251023110529' => __DIR__ . '/../../..' . '/core/Migrations/Version33000Date20251023110529.php', 'OC\\Core\\Migrations\\Version33000Date20251023110529' => __DIR__ . '/../../..' . '/core/Migrations/Version33000Date20251023110529.php',
'OC\\Core\\Migrations\\Version33000Date20251023120529' => __DIR__ . '/../../..' . '/core/Migrations/Version33000Date20251023120529.php', 'OC\\Core\\Migrations\\Version33000Date20251023120529' => __DIR__ . '/../../..' . '/core/Migrations/Version33000Date20251023120529.php',
'OC\\Core\\Migrations\\Version33000Date20251106131209' => __DIR__ . '/../../..' . '/core/Migrations/Version33000Date20251106131209.php', 'OC\\Core\\Migrations\\Version33000Date20251106131209' => __DIR__ . '/../../..' . '/core/Migrations/Version33000Date20251106131209.php',
'OC\\Core\\Migrations\\Version33000Date20251124110529' => __DIR__ . '/../../..' . '/core/Migrations/Version33000Date20251124110529.php',
'OC\\Core\\Migrations\\Version33000Date20251126152410' => __DIR__ . '/../../..' . '/core/Migrations/Version33000Date20251126152410.php', 'OC\\Core\\Migrations\\Version33000Date20251126152410' => __DIR__ . '/../../..' . '/core/Migrations/Version33000Date20251126152410.php',
'OC\\Core\\Notification\\CoreNotifier' => __DIR__ . '/../../..' . '/core/Notification/CoreNotifier.php', 'OC\\Core\\Notification\\CoreNotifier' => __DIR__ . '/../../..' . '/core/Notification/CoreNotifier.php',
'OC\\Core\\ResponseDefinitions' => __DIR__ . '/../../..' . '/core/ResponseDefinitions.php', 'OC\\Core\\ResponseDefinitions' => __DIR__ . '/../../..' . '/core/ResponseDefinitions.php',

@ -7,7 +7,6 @@
*/ */
namespace OC\BackgroundJob; namespace OC\BackgroundJob;
use OCP\AppFramework\QueryException;
use OCP\AppFramework\Utility\ITimeFactory; use OCP\AppFramework\Utility\ITimeFactory;
use OCP\AutoloadNotAllowedException; use OCP\AutoloadNotAllowedException;
use OCP\BackgroundJob\IJob; use OCP\BackgroundJob\IJob;
@ -17,6 +16,9 @@ use OCP\DB\Exception;
use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IConfig; use OCP\IConfig;
use OCP\IDBConnection; use OCP\IDBConnection;
use OCP\Snowflake\IGenerator;
use Override;
use Psr\Container\ContainerExceptionInterface;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use function get_class; use function get_class;
use function json_encode; use function json_encode;
@ -24,18 +26,20 @@ use function min;
use function strlen; use function strlen;
class JobList implements IJobList { class JobList implements IJobList {
/** @var array<string, int> */ /** @var array<string, string> */
protected array $alreadyVisitedParallelBlocked = []; protected array $alreadyVisitedParallelBlocked = [];
public function __construct( public function __construct(
protected IDBConnection $connection, protected readonly IDBConnection $connection,
protected IConfig $config, protected readonly IConfig $config,
protected ITimeFactory $timeFactory, protected readonly ITimeFactory $timeFactory,
protected LoggerInterface $logger, protected readonly LoggerInterface $logger,
protected readonly IGenerator $generator,
) { ) {
} }
public function add($job, $argument = null, ?int $firstCheck = null): void { #[Override]
public function add(IJob|string $job, mixed $argument = null, ?int $firstCheck = null): void {
if ($firstCheck === null) { if ($firstCheck === null) {
$firstCheck = $this->timeFactory->getTime(); $firstCheck = $this->timeFactory->getTime();
} }
@ -51,6 +55,7 @@ class JobList implements IJobList {
if (!$this->has($job, $argument)) { if (!$this->has($job, $argument)) {
$query->insert('jobs') $query->insert('jobs')
->values([ ->values([
'id' => $query->createNamedParameter($this->generator->nextId(), IQueryBuilder::PARAM_INT),
'class' => $query->createNamedParameter($class), 'class' => $query->createNamedParameter($class),
'argument' => $query->createNamedParameter($argumentJson), 'argument' => $query->createNamedParameter($argumentJson),
'argument_hash' => $query->createNamedParameter(hash('sha256', $argumentJson)), 'argument_hash' => $query->createNamedParameter(hash('sha256', $argumentJson)),
@ -68,15 +73,12 @@ class JobList implements IJobList {
$query->executeStatement(); $query->executeStatement();
} }
public function scheduleAfter(string $job, int $runAfter, $argument = null): void { public function scheduleAfter(string $job, int $runAfter, mixed $argument = null): void {
$this->add($job, $argument, $runAfter); $this->add($job, $argument, $runAfter);
} }
/** #[Override]
* @param IJob|string $job public function remove(IJob|string $job, mixed $argument = null): void {
* @param mixed $argument
*/
public function remove($job, $argument = null): void {
$class = ($job instanceof IJob) ? get_class($job) : $job; $class = ($job instanceof IJob) ? get_class($job) : $job;
$query = $this->connection->getQueryBuilder(); $query = $this->connection->getQueryBuilder();
@ -104,20 +106,16 @@ class JobList implements IJobList {
} }
} }
public function removeById(int $id): void { #[Override]
public function removeById(string $id): void {
$query = $this->connection->getQueryBuilder(); $query = $this->connection->getQueryBuilder();
$query->delete('jobs') $query->delete('jobs')
->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT))); ->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
$query->executeStatement(); $query->executeStatement();
} }
/** #[Override]
* check if a job is in the list public function has(IJob|string $job, mixed $argument): bool {
*
* @param IJob|class-string<IJob> $job
* @param mixed $argument
*/
public function has($job, $argument): bool {
$class = ($job instanceof IJob) ? get_class($job) : $job; $class = ($job instanceof IJob) ? get_class($job) : $job;
$argument = json_encode($argument); $argument = json_encode($argument);
@ -135,18 +133,16 @@ class JobList implements IJobList {
return (bool)$row; return (bool)$row;
} }
public function getJobs($job, ?int $limit, int $offset): array { #[Override]
public function getJobs(IJob|string|null $job, ?int $limit, int $offset): array {
$iterable = $this->getJobsIterator($job, $limit, $offset); $iterable = $this->getJobsIterator($job, $limit, $offset);
return (is_array($iterable)) return (is_array($iterable))
? $iterable ? $iterable
: iterator_to_array($iterable); : iterator_to_array($iterable);
} }
/** #[Override]
* @param IJob|class-string<IJob>|null $job public function getJobsIterator(IJob|string|null $job, ?int $limit, int $offset): iterable {
* @return iterable<IJob> Avoid to store these objects as they may share a Singleton instance. You should instead use these IJobs instances while looping on the iterable.
*/
public function getJobsIterator($job, ?int $limit, int $offset): iterable {
$query = $this->connection->getQueryBuilder(); $query = $this->connection->getQueryBuilder();
$query->select('*') $query->select('*')
->from('jobs') ->from('jobs')
@ -169,9 +165,7 @@ class JobList implements IJobList {
$result->closeCursor(); $result->closeCursor();
} }
/** #[Override]
* @inheritDoc
*/
public function getNext(bool $onlyTimeSensitive = false, ?array $jobClasses = null): ?IJob { public function getNext(bool $onlyTimeSensitive = false, ?array $jobClasses = null): ?IJob {
$query = $this->connection->getQueryBuilder(); $query = $this->connection->getQueryBuilder();
$query->select('*') $query->select('*')
@ -279,10 +273,8 @@ class JobList implements IJobList {
} }
} }
/** #[Override]
* @return ?IJob The job matching the id. Beware that this object may be a singleton and may be modified by the next call to buildJob. public function getById(string $id): ?IJob {
*/
public function getById(int $id): ?IJob {
$row = $this->getDetailsById($id); $row = $this->getDetailsById($id);
if ($row) { if ($row) {
@ -292,7 +284,8 @@ class JobList implements IJobList {
return null; return null;
} }
public function getDetailsById(int $id): ?array { #[Override]
public function getDetailsById(string $id): ?array {
$query = $this->connection->getQueryBuilder(); $query = $this->connection->getQueryBuilder();
$query->select('*') $query->select('*')
->from('jobs') ->from('jobs')
@ -320,7 +313,7 @@ class JobList implements IJobList {
// Try to load the job as a service // Try to load the job as a service
/** @var IJob $job */ /** @var IJob $job */
$job = \OCP\Server::get($row['class']); $job = \OCP\Server::get($row['class']);
} catch (QueryException $e) { } catch (ContainerExceptionInterface $e) {
if (class_exists($row['class'])) { if (class_exists($row['class'])) {
$class = $row['class']; $class = $row['class'];
$job = new $class(); $job = new $class();
@ -336,7 +329,7 @@ class JobList implements IJobList {
// This most likely means an invalid job was enqueued. We can ignore it. // This most likely means an invalid job was enqueued. We can ignore it.
return null; return null;
} }
$job->setId((int)$row['id']); $job->setId($row['id']);
$job->setLastRun((int)$row['last_run']); $job->setLastRun((int)$row['last_run']);
$job->setArgument(json_decode($row['argument'], true)); $job->setArgument(json_decode($row['argument'], true));
return $job; return $job;
@ -351,12 +344,10 @@ class JobList implements IJobList {
*/ */
public function setLastJob(IJob $job): void { public function setLastJob(IJob $job): void {
$this->unlockJob($job); $this->unlockJob($job);
$this->config->setAppValue('backgroundjob', 'lastjob', (string)$job->getId()); $this->config->setAppValue('backgroundjob', 'lastjob', $job->getId());
} }
/** #[Override]
* Remove the reservation for a job
*/
public function unlockJob(IJob $job): void { public function unlockJob(IJob $job): void {
$query = $this->connection->getQueryBuilder(); $query = $this->connection->getQueryBuilder();
$query->update('jobs') $query->update('jobs')
@ -365,9 +356,7 @@ class JobList implements IJobList {
$query->executeStatement(); $query->executeStatement();
} }
/** #[Override]
* set the lastRun of $job to now
*/
public function setLastRun(IJob $job): void { public function setLastRun(IJob $job): void {
$query = $this->connection->getQueryBuilder(); $query = $this->connection->getQueryBuilder();
$query->update('jobs') $query->update('jobs')
@ -382,9 +371,7 @@ class JobList implements IJobList {
$query->executeStatement(); $query->executeStatement();
} }
/** #[Override]
* @param int $timeTaken
*/
public function setExecutionTime(IJob $job, $timeTaken): void { public function setExecutionTime(IJob $job, $timeTaken): void {
$query = $this->connection->getQueryBuilder(); $query = $this->connection->getQueryBuilder();
$query->update('jobs') $query->update('jobs')
@ -394,11 +381,7 @@ class JobList implements IJobList {
$query->executeStatement(); $query->executeStatement();
} }
/** #[Override]
* Reset the $job so it executes on the next trigger
*
* @since 23.0.0
*/
public function resetBackgroundJob(IJob $job): void { public function resetBackgroundJob(IJob $job): void {
$query = $this->connection->getQueryBuilder(); $query = $this->connection->getQueryBuilder();
$query->update('jobs') $query->update('jobs')
@ -408,6 +391,7 @@ class JobList implements IJobList {
$query->executeStatement(); $query->executeStatement();
} }
#[Override]
public function hasReservedJob(?string $className = null): bool { public function hasReservedJob(?string $className = null): bool {
$query = $this->connection->getQueryBuilder(); $query = $this->connection->getQueryBuilder();
$query->select('*') $query->select('*')
@ -430,6 +414,7 @@ class JobList implements IJobList {
} }
} }
#[Override]
public function countByClass(): array { public function countByClass(): array {
$query = $this->connection->getQueryBuilder(); $query = $this->connection->getQueryBuilder();
$query->select('class') $query->select('class')
@ -444,7 +429,7 @@ class JobList implements IJobList {
while (($row = $result->fetch()) !== false) { while (($row = $result->fetch()) !== false) {
/** /**
* @var array{count:int, class:class-string} $row * @var array{count:int, class:class-string<IJob>} $row
*/ */
$jobs[] = $row; $jobs[] = $row;
} }

@ -43,43 +43,42 @@ interface IJob {
/** /**
* @since 7.0.0 * @since 7.0.0
* @since 33.0.0 Parameter $id changed from int to string
*/ */
public function setId(int $id); public function setId(string $id): void;
/** /**
* @since 7.0.0 * @since 7.0.0
*/ */
public function setLastRun(int $lastRun); public function setLastRun(int $lastRun): void;
/** /**
* @param mixed $argument * @param mixed $argument
* @since 7.0.0 * @since 7.0.0
*/ */
public function setArgument($argument); public function setArgument(mixed $argument): void;
/** /**
* Get the id of the background job * Get the id of the background job
* This id is determined by the job list when a job is added to the list * This id is determined by the job list when a job is added to the list
* *
* @return int
* @since 7.0.0 * @since 7.0.0
* @since 33.0.0 The return type changed from int to string
*/ */
public function getId(); public function getId(): string;
/** /**
* Get the last time this job was run as unix timestamp * Get the last time this job was run as unix timestamp
* *
* @return int
* @since 7.0.0 * @since 7.0.0
*/ */
public function getLastRun(); public function getLastRun(): int;
/** /**
* Get the argument associated with the background job * Get the argument associated with the background job
* This is the argument that will be passed to the background job * This is the argument that will be passed to the background job
* *
* @return mixed
* @since 7.0.0 * @since 7.0.0
*/ */
public function getArgument(); public function getArgument(): mixed;
} }

@ -7,6 +7,8 @@
*/ */
namespace OCP\BackgroundJob; namespace OCP\BackgroundJob;
use OCP\AppFramework\Attribute\Consumable;
/** /**
* Interface IJobList * Interface IJobList
* *
@ -28,6 +30,7 @@ namespace OCP\BackgroundJob;
* *
* @since 7.0.0 * @since 7.0.0
*/ */
#[Consumable(since: '7.0.0')]
interface IJobList { interface IJobList {
/** /**
* Add a job to the list * Add a job to the list
@ -36,7 +39,7 @@ interface IJobList {
* @param mixed $argument The argument to be passed to $job->run() when the job is executed * @param mixed $argument The argument to be passed to $job->run() when the job is executed
* @since 7.0.0 * @since 7.0.0
*/ */
public function add($job, $argument = null): void; public function add(IJob|string $job, mixed $argument = null): void;
/** /**
* Add a job to the list but only run it after the given timestamp * Add a job to the list but only run it after the given timestamp
@ -49,7 +52,7 @@ interface IJobList {
* @param mixed $argument The serializable argument to be passed to $job->run() when the job is executed * @param mixed $argument The serializable argument to be passed to $job->run() when the job is executed
* @since 28.0.0 * @since 28.0.0
*/ */
public function scheduleAfter(string $job, int $runAfter, $argument = null): void; public function scheduleAfter(string $job, int $runAfter, mixed $argument = null): void;
/** /**
* Remove a job from the list * Remove a job from the list
@ -58,15 +61,15 @@ interface IJobList {
* @param mixed $argument * @param mixed $argument
* @since 7.0.0 * @since 7.0.0
*/ */
public function remove($job, $argument = null): void; public function remove(IJob|string $job, mixed $argument = null): void;
/** /**
* Remove a job from the list by id * Remove a job from the list by id
* *
* @param int $id
* @since 30.0.0 * @since 30.0.0
* @since 33.0.0 Parameter $id changed from int to string
*/ */
public function removeById(int $id): void; public function removeById(string $id): void;
/** /**
* check if a job is in the list * check if a job is in the list
@ -75,7 +78,7 @@ interface IJobList {
* @param mixed $argument * @param mixed $argument
* @since 7.0.0 * @since 7.0.0
*/ */
public function has($job, $argument): bool; public function has(IJob|string $job, mixed $argument): bool;
/** /**
* Get jobs matching the search * Get jobs matching the search
@ -85,7 +88,7 @@ interface IJobList {
* @since 25.0.0 * @since 25.0.0
* @deprecated 26.0.0 Use getJobsIterator instead to avoid duplicated job objects * @deprecated 26.0.0 Use getJobsIterator instead to avoid duplicated job objects
*/ */
public function getJobs($job, ?int $limit, int $offset): array; public function getJobs(IJob|string|null $job, ?int $limit, int $offset): array;
/** /**
* Get jobs matching the search * Get jobs matching the search
@ -94,7 +97,7 @@ interface IJobList {
* @return iterable<IJob> * @return iterable<IJob>
* @since 26.0.0 * @since 26.0.0
*/ */
public function getJobsIterator($job, ?int $limit, int $offset): iterable; public function getJobsIterator(IJob|string|null $job, ?int $limit, int $offset): iterable;
/** /**
* Get the next job in the list * Get the next job in the list
@ -108,13 +111,15 @@ interface IJobList {
/** /**
* @since 7.0.0 * @since 7.0.0
* @since 33.0.0 Parameter $id changed from int to string
*/ */
public function getById(int $id): ?IJob; public function getById(string $id): ?IJob;
/** /**
* @since 23.0.0 * @since 23.0.0
* @since 33.0.0 Parameter $id changed from int to string
*/ */
public function getDetailsById(int $id): ?array; public function getDetailsById(string $id): ?array;
/** /**
* set the job that was last ran to the current time * set the job that was last ran to the current time
@ -155,7 +160,7 @@ interface IJobList {
* Checks whether a job of the passed class was reserved to run * Checks whether a job of the passed class was reserved to run
* in the last 6h * in the last 6h
* *
* @param string|null $className * @param class-string<IJob>|null $className
* @return bool * @return bool
* @since 27.0.0 * @since 27.0.0
*/ */
@ -164,7 +169,7 @@ interface IJobList {
/** /**
* Returns a count of jobs per Job class * Returns a count of jobs per Job class
* *
* @return list<array{class:class-string, count:int}> * @return list<array{class:class-string<IJob>, count:int}>
* @since 30.0.0 * @since 30.0.0
*/ */
public function countByClass(): array; public function countByClass(): array;

@ -8,6 +8,7 @@ declare(strict_types=1);
namespace OCP\BackgroundJob; namespace OCP\BackgroundJob;
use OCP\AppFramework\Utility\ITimeFactory; use OCP\AppFramework\Utility\ITimeFactory;
use Override;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
/** /**
@ -21,7 +22,7 @@ use Psr\Log\LoggerInterface;
* @since 33.0.0 removed deprecated `execute()` method * @since 33.0.0 removed deprecated `execute()` method
*/ */
abstract class Job implements IJob, IParallelAwareJob { abstract class Job implements IJob, IParallelAwareJob {
protected int $id = 0; protected string $id = '0';
protected int $lastRun = 0; protected int $lastRun = 0;
protected mixed $argument = null; protected mixed $argument = null;
protected bool $allowParallelRuns = true; protected bool $allowParallelRuns = true;
@ -34,10 +35,7 @@ abstract class Job implements IJob, IParallelAwareJob {
) { ) {
} }
/** #[Override]
* @inheritdoc
* @since 25.0.0
*/
public function start(IJobList $jobList): void { public function start(IJobList $jobList): void {
$jobList->setLastRun($this); $jobList->setLastRun($this);
$logger = \OCP\Server::get(LoggerInterface::class); $logger = \OCP\Server::get(LoggerInterface::class);
@ -61,74 +59,45 @@ abstract class Job implements IJob, IParallelAwareJob {
} }
} }
/** #[Override]
* @since 15.0.0 final public function setId(string $id): void {
*/
final public function setId(int $id) {
$this->id = $id; $this->id = $id;
} }
/** #[Override]
* @since 15.0.0 final public function setLastRun(int $lastRun): void {
*/
final public function setLastRun(int $lastRun) {
$this->lastRun = $lastRun; $this->lastRun = $lastRun;
} }
/** #[Override]
* @since 15.0.0 public function setArgument(mixed $argument): void {
*/
public function setArgument($argument) {
$this->argument = $argument; $this->argument = $argument;
} }
/** #[Override]
* @since 15.0.0 final public function getId(): string {
*/
final public function getId(): int {
return $this->id; return $this->id;
} }
/** #[Override]
* @since 15.0.0
*/
final public function getLastRun(): int { final public function getLastRun(): int {
return $this->lastRun; return $this->lastRun;
} }
/** #[Override]
* @since 15.0.0 public function getArgument(): mixed {
*/
public function getArgument() {
return $this->argument; return $this->argument;
} }
/** #[Override]
* 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 { public function setAllowParallelRuns(bool $allow): void {
$this->allowParallelRuns = $allow; $this->allowParallelRuns = $allow;
} }
/** #[Override]
* @return bool
* @since 27.0.0
*/
public function getAllowParallelRuns(): bool { public function getAllowParallelRuns(): bool {
return $this->allowParallelRuns; 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); abstract protected function run($argument);
} }

@ -13,6 +13,7 @@ use OC\BackgroundJob\JobList;
use OCP\BackgroundJob\IJob; use OCP\BackgroundJob\IJob;
use OCP\BackgroundJob\Job; use OCP\BackgroundJob\Job;
use OCP\Server; use OCP\Server;
use OCP\Snowflake\IGenerator;
/** /**
* Class DummyJobList * Class DummyJobList
@ -31,7 +32,6 @@ class DummyJobList extends JobList {
private array $reserved = []; private array $reserved = [];
private int $last = 0; private int $last = 0;
private int $lastId = 0;
public function __construct() { public function __construct() {
} }
@ -46,8 +46,7 @@ class DummyJobList extends JobList {
$job = Server::get($job); $job = Server::get($job);
} }
$job->setArgument($argument); $job->setArgument($argument);
$job->setId($this->lastId); $job->setId(Server::get(IGenerator::class)->nextId());
$this->lastId++;
if (!$this->has($job, null)) { if (!$this->has($job, null)) {
$this->jobs[] = $job; $this->jobs[] = $job;
} }
@ -70,7 +69,7 @@ class DummyJobList extends JobList {
} }
} }
public function removeById(int $id): void { public function removeById(string $id): void {
foreach ($this->jobs as $index => $listJob) { foreach ($this->jobs as $index => $listJob) {
if ($listJob->getId() === $id) { if ($listJob->getId() === $id) {
unset($this->jobs[$index]); unset($this->jobs[$index]);
@ -148,7 +147,7 @@ class DummyJobList extends JobList {
} }
} }
public function getById(int $id): ?IJob { public function getById(string $id): ?IJob {
foreach ($this->jobs as $job) { foreach ($this->jobs as $job) {
if ($job->getId() === $id) { if ($job->getId() === $id) {
return $job; return $job;
@ -157,7 +156,7 @@ class DummyJobList extends JobList {
return null; return null;
} }
public function getDetailsById(int $id): ?array { public function getDetailsById(string $id): ?array {
return null; return null;
} }

@ -12,11 +12,13 @@ namespace Test\BackgroundJob;
use OC\BackgroundJob\JobList; use OC\BackgroundJob\JobList;
use OCP\AppFramework\Utility\ITimeFactory; use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\IJob;
use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IConfig; use OCP\IConfig;
use OCP\IDBConnection; use OCP\IDBConnection;
use OCP\Server; use OCP\Server;
use OCP\Snowflake\IGenerator;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Test\TestCase; use Test\TestCase;
@ -27,18 +29,10 @@ use Test\TestCase;
*/ */
#[\PHPUnit\Framework\Attributes\Group('DB')] #[\PHPUnit\Framework\Attributes\Group('DB')]
class JobListTest extends TestCase { class JobListTest extends TestCase {
/** @var \OC\BackgroundJob\JobList */ protected JobList $instance;
protected $instance; protected IDBConnection $connection;
protected IConfig&MockObject $config;
/** @var IDBConnection */ protected ITimeFactory&MockObject $timeFactory;
protected $connection;
/** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */
protected $config;
/** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */
protected $timeFactory;
private bool $ran = false;
protected function setUp(): void { protected function setUp(): void {
parent::setUp(); parent::setUp();
@ -52,16 +46,17 @@ class JobListTest extends TestCase {
$this->config, $this->config,
$this->timeFactory, $this->timeFactory,
Server::get(LoggerInterface::class), Server::get(LoggerInterface::class),
Server::get(IGenerator::class),
); );
} }
protected function clearJobsList() { protected function clearJobsList(): void {
$query = $this->connection->getQueryBuilder(); $query = $this->connection->getQueryBuilder();
$query->delete('jobs'); $query->delete('jobs');
$query->executeStatement(); $query->executeStatement();
} }
protected function getAllSorted() { protected function getAllSorted(): array {
$iterator = $this->instance->getJobsIterator(null, null, 0); $iterator = $this->instance->getJobsIterator(null, null, 0);
$jobs = []; $jobs = [];
@ -69,10 +64,6 @@ class JobListTest extends TestCase {
$jobs[] = clone $job; $jobs[] = clone $job;
} }
usort($jobs, function (IJob $job1, IJob $job2) {
return $job1->getId() - $job2->getId();
});
return $jobs; return $jobs;
} }
@ -89,11 +80,8 @@ class JobListTest extends TestCase {
]; ];
} }
/** #[DataProvider('argumentProvider')]
* @param $argument public function testAddRemove(mixed $argument): void {
*/
#[\PHPUnit\Framework\Attributes\DataProvider('argumentProvider')]
public function testAddRemove($argument): void {
$existingJobs = $this->getAllSorted(); $existingJobs = $this->getAllSorted();
$job = new TestJob(); $job = new TestJob();
$this->instance->add($job, $argument); $this->instance->add($job, $argument);
@ -111,11 +99,8 @@ class JobListTest extends TestCase {
$this->assertEquals($existingJobs, $jobs); $this->assertEquals($existingJobs, $jobs);
} }
/** #[DataProvider('argumentProvider')]
* @param $argument public function testRemoveDifferentArgument(mixed $argument): void {
*/
#[\PHPUnit\Framework\Attributes\DataProvider('argumentProvider')]
public function testRemoveDifferentArgument($argument): void {
$existingJobs = $this->getAllSorted(); $existingJobs = $this->getAllSorted();
$job = new TestJob(); $job = new TestJob();
$this->instance->add($job, $argument); $this->instance->add($job, $argument);
@ -132,11 +117,8 @@ class JobListTest extends TestCase {
$this->assertEquals($existingJobs, $jobs); $this->assertEquals($existingJobs, $jobs);
} }
/** #[DataProvider('argumentProvider')]
* @param $argument public function testHas(mixed $argument): void {
*/
#[\PHPUnit\Framework\Attributes\DataProvider('argumentProvider')]
public function testHas($argument): void {
$job = new TestJob(); $job = new TestJob();
$this->assertFalse($this->instance->has($job, $argument)); $this->assertFalse($this->instance->has($job, $argument));
$this->instance->add($job, $argument); $this->instance->add($job, $argument);
@ -148,11 +130,8 @@ class JobListTest extends TestCase {
$this->assertFalse($this->instance->has($job, $argument)); $this->assertFalse($this->instance->has($job, $argument));
} }
/** #[DataProvider('argumentProvider')]
* @param $argument public function testHasDifferentArgument(mixed $argument): void {
*/
#[\PHPUnit\Framework\Attributes\DataProvider('argumentProvider')]
public function testHasDifferentArgument($argument): void {
$job = new TestJob(); $job = new TestJob();
$this->instance->add($job, $argument); $this->instance->add($job, $argument);
@ -163,14 +142,16 @@ class JobListTest extends TestCase {
$argument, $argument,
int $reservedTime = 0, int $reservedTime = 0,
int $lastChecked = 0, int $lastChecked = 0,
int $lastRun = 0): int { int $lastRun = 0): string {
if ($lastChecked === 0) { if ($lastChecked === 0) {
$lastChecked = time(); $lastChecked = time();
} }
$id = Server::get(IGenerator::class)->nextId();
$query = $this->connection->getQueryBuilder(); $query = $this->connection->getQueryBuilder();
$query->insert('jobs') $query->insert('jobs')
->values([ ->values([
'id' => $query->createNamedParameter($id, IQueryBuilder::PARAM_INT),
'class' => $query->createNamedParameter($class), 'class' => $query->createNamedParameter($class),
'argument' => $query->createNamedParameter($argument), 'argument' => $query->createNamedParameter($argument),
'last_run' => $query->createNamedParameter($lastRun, IQueryBuilder::PARAM_INT), 'last_run' => $query->createNamedParameter($lastRun, IQueryBuilder::PARAM_INT),
@ -178,7 +159,7 @@ class JobListTest extends TestCase {
'reserved_at' => $query->createNamedParameter($reservedTime, IQueryBuilder::PARAM_INT), 'reserved_at' => $query->createNamedParameter($reservedTime, IQueryBuilder::PARAM_INT),
]); ]);
$query->executeStatement(); $query->executeStatement();
return $query->getLastInsertId(); return $id;
} }
public function testGetNext(): void { public function testGetNext(): void {
@ -243,7 +224,7 @@ class JobListTest extends TestCase {
/** /**
* @param $argument * @param $argument
*/ */
#[\PHPUnit\Framework\Attributes\DataProvider('argumentProvider')] #[DataProvider('argumentProvider')]
public function testGetById($argument): void { public function testGetById($argument): void {
$job = new TestJob(); $job = new TestJob();
$this->instance->add($job, $argument); $this->instance->add($job, $argument);
@ -334,8 +315,4 @@ class JobListTest extends TestCase {
$job = $this->instance->getNext(); $job = $this->instance->getNext();
$this->assertNull($job); // Job doesn't allow parallel runs $this->assertNull($job); // Job doesn't allow parallel runs
} }
public function markRun() {
$this->ran = true;
}
} }