Skip to content

Commit e655ffe

Browse files
Merge pull request #50768 from nextcloud/perf/cron/delay-timedjob-checking
perf(cron): Delay (re)checking timed jobs
2 parents 12e66ba + 2395526 commit e655ffe

File tree

5 files changed

+82
-17
lines changed

5 files changed

+82
-17
lines changed

lib/private/BackgroundJob/JobList.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Psr\Log\LoggerInterface;
2121
use function get_class;
2222
use function json_encode;
23+
use function min;
2324
use function strlen;
2425

2526
class JobList implements IJobList {
@@ -209,6 +210,26 @@ public function getNext(bool $onlyTimeSensitive = false, ?array $jobClasses = nu
209210
return $this->getNext($onlyTimeSensitive, $jobClasses);
210211
}
211212

213+
if ($job instanceof \OCP\BackgroundJob\TimedJob) {
214+
$now = $this->timeFactory->getTime();
215+
$nextPossibleRun = $job->getLastRun() + $job->getInterval();
216+
if ($now < $nextPossibleRun) {
217+
// This job is not ready for execution yet. Set timestamps to the future to avoid
218+
// re-checking with every cron run.
219+
// To avoid bugs that lead to jobs never executing again, the future timestamp is
220+
// capped at two days.
221+
$nextCheck = min($nextPossibleRun, $now + 48 * 3600);
222+
$updateTimedJob = $this->connection->getQueryBuilder();
223+
$updateTimedJob->update('jobs')
224+
->set('last_checked', $updateTimedJob->createNamedParameter($nextCheck, IQueryBuilder::PARAM_INT))
225+
->where($updateTimedJob->expr()->eq('id', $updateTimedJob->createParameter('jobid')));
226+
$updateTimedJob->setParameter('jobid', $row['id']);
227+
$updateTimedJob->executeStatement();
228+
229+
return $this->getNext($onlyTimeSensitive, $jobClasses);
230+
}
231+
}
232+
212233
$update = $this->connection->getQueryBuilder();
213234
$update->update('jobs')
214235
->set('reserved_at', $update->createNamedParameter($this->timeFactory->getTime()))

lib/public/BackgroundJob/TimedJob.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ public function setInterval(int $seconds) {
3232
$this->interval = $seconds;
3333
}
3434

35+
/**
36+
* Get the interval [seconds] for the job
37+
*
38+
* @since 32.0.0
39+
*/
40+
public function getInterval(): int {
41+
return $this->interval;
42+
}
43+
3544
/**
3645
* Whether the background job is time sensitive and needs to run soon after
3746
* the scheduled interval, of if it is okay to be delayed until a later time.

tests/lib/BackgroundJob/JobListTest.php

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
<?php
2+
3+
declare(strict_types=1);
4+
25
/**
36
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
47
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
@@ -153,7 +156,11 @@ public function testHasDifferentArgument($argument): void {
153156
$this->assertFalse($this->instance->has($job, 10));
154157
}
155158

156-
protected function createTempJob($class, $argument, $reservedTime = 0, $lastChecked = 0) {
159+
protected function createTempJob($class,
160+
$argument,
161+
int $reservedTime = 0,
162+
int $lastChecked = 0,
163+
int $lastRun = 0): int {
157164
if ($lastChecked === 0) {
158165
$lastChecked = time();
159166
}
@@ -163,11 +170,12 @@ protected function createTempJob($class, $argument, $reservedTime = 0, $lastChec
163170
->values([
164171
'class' => $query->createNamedParameter($class),
165172
'argument' => $query->createNamedParameter($argument),
166-
'last_run' => $query->createNamedParameter(0, IQueryBuilder::PARAM_INT),
173+
'last_run' => $query->createNamedParameter($lastRun, IQueryBuilder::PARAM_INT),
167174
'last_checked' => $query->createNamedParameter($lastChecked, IQueryBuilder::PARAM_INT),
168175
'reserved_at' => $query->createNamedParameter($reservedTime, IQueryBuilder::PARAM_INT),
169176
]);
170-
$query->execute();
177+
$query->executeStatement();
178+
return $query->getLastInsertId();
171179
}
172180

173181
public function testGetNext(): void {
@@ -200,6 +208,21 @@ public function testGetNextSkipReserved(): void {
200208
$this->assertEquals(2, $nextJob->getArgument());
201209
}
202210

211+
public function testGetNextSkipTimed(): void {
212+
$job = new TestTimedJobNew($this->timeFactory);
213+
$jobId = $this->createTempJob(get_class($job), 1, 123456789, 12345, 123456789 - 5);
214+
$this->timeFactory->expects(self::atLeastOnce())
215+
->method('getTime')
216+
->willReturn(123456789);
217+
218+
$nextJob = $this->instance->getNext();
219+
220+
self::assertNull($nextJob);
221+
$job = $this->instance->getById($jobId);
222+
self::assertInstanceOf(TestTimedJobNew::class, $job);
223+
self::assertEquals(123456789 - 5, $job->getLastRun());
224+
}
225+
203226
public function testGetNextSkipNonExisting(): void {
204227
$job = new TestJob();
205228
$this->createTempJob('\OC\Non\Existing\Class', 1, 0, 12345);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-only
8+
*/
9+
10+
namespace Test\BackgroundJob;
11+
12+
use OCP\AppFramework\Utility\ITimeFactory;
13+
use OCP\BackgroundJob\TimedJob;
14+
15+
class TestTimedJobNew extends TimedJob {
16+
public bool $ran = false;
17+
18+
public function __construct(ITimeFactory $timeFactory) {
19+
parent::__construct($timeFactory);
20+
$this->setInterval(10);
21+
}
22+
23+
public function run($argument) {
24+
$this->ran = true;
25+
}
26+
}

tests/lib/BackgroundJob/TimedJobTest.php

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,6 @@
88
namespace Test\BackgroundJob;
99

1010
use OCP\AppFramework\Utility\ITimeFactory;
11-
use OCP\BackgroundJob\TimedJob;
12-
13-
class TestTimedJobNew extends TimedJob {
14-
public bool $ran = false;
15-
16-
public function __construct(ITimeFactory $timeFactory) {
17-
parent::__construct($timeFactory);
18-
$this->setInterval(10);
19-
}
20-
21-
public function run($argument) {
22-
$this->ran = true;
23-
}
24-
}
2511

2612
class TimedJobTest extends \Test\TestCase {
2713
private DummyJobList $jobList;

0 commit comments

Comments
 (0)