Skip to content

Commit 48e6568

Browse files
committed
feat(files_trashbin): Refactor expire background job to support parallel run
- Follow-up of #51600 The original PR introduced the possibility to continue an `ExpireTrash` job by saving the offset. This was to prevent having to start over the whole user list when the job crashed or was killed. But on big instances, one process is not enough to go through all the users in a timely manner. Supporting parallel run allows covering more ground faster. This PR introduced this possibility. We are now storing the offset right away to allow another parallel job to pick up the task at that point. We are arbitrarily cutting the user list in chunk of 10 to not drastically overflow the 30 minutes time limit. Signed-off-by: Louis Chemineau <louis@chmn.me>
1 parent 9fa0477 commit 48e6568

File tree

5 files changed

+58
-73
lines changed

5 files changed

+58
-73
lines changed

apps/files_trashbin/lib/BackgroundJob/ExpireTrash.php

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77
namespace OCA\Files_Trashbin\BackgroundJob;
88

9+
use OC\Files\SetupManager;
910
use OC\Files\View;
1011
use OCA\Files_Trashbin\Expiration;
1112
use OCA\Files_Trashbin\Helper;
@@ -17,16 +18,18 @@
1718
use Psr\Log\LoggerInterface;
1819

1920
class ExpireTrash extends TimedJob {
21+
private const THIRTY_MINUTES = 30 * 60;
22+
2023
public function __construct(
2124
private IAppConfig $appConfig,
2225
private IUserManager $userManager,
2326
private Expiration $expiration,
2427
private LoggerInterface $logger,
28+
private SetupManager $setupManager,
2529
ITimeFactory $time,
2630
) {
2731
parent::__construct($time);
28-
// Run once per 30 minutes
29-
$this->setInterval(60 * 30);
32+
$this->setInterval(self::THIRTY_MINUTES);
3033
}
3134

3235
protected function run($argument) {
@@ -40,44 +43,47 @@ protected function run($argument) {
4043
return;
4144
}
4245

43-
$stopTime = time() + 60 * 30; // Stops after 30 minutes.
44-
$offset = $this->appConfig->getValueInt('files_trashbin', 'background_job_expire_trash_offset', 0);
45-
$users = $this->userManager->getSeenUsers($offset);
46+
$stopTime = time() + self::THIRTY_MINUTES;
47+
48+
do {
49+
$this->appConfig->clearCache();
50+
$offset = $this->appConfig->getValueInt('files_trashbin', 'background_job_expire_trash_offset', 0);
51+
$this->appConfig->setValueInt('files_trashbin', 'background_job_expire_trash_offset', $offset + 10);
52+
53+
$users = $this->userManager->getSeenUsers($offset, 10);
54+
$count = 0;
55+
56+
foreach ($users as $uid) {
57+
$uid = $uid->getUID();
58+
$count++;
4659

47-
foreach ($users as $user) {
48-
try {
49-
$uid = $user->getUID();
50-
if (!$this->setupFS($uid)) {
51-
continue;
60+
try {
61+
if ($this->setupFS($uid)) {
62+
$dirContent = Helper::getTrashFiles('/', $uid, 'mtime');
63+
Trashbin::deleteExpiredFiles($dirContent, $uid);
64+
}
65+
} catch (\Throwable $e) {
66+
$this->logger->error('Error while expiring trashbin for user ' . $uid, ['exception' => $e]);
5267
}
53-
$dirContent = Helper::getTrashFiles('/', $uid, 'mtime');
54-
Trashbin::deleteExpiredFiles($dirContent, $uid);
55-
} catch (\Throwable $e) {
56-
$this->logger->error('Error while expiring trashbin for user ' . $user->getUID(), ['exception' => $e]);
68+
69+
$this->setupManager->tearDown();
5770
}
5871

59-
$offset++;
72+
} while (time() < $stopTime && $count === 10);
6073

61-
if ($stopTime < time()) {
62-
$this->appConfig->setValueInt('files_trashbin', 'background_job_expire_trash_offset', $offset);
63-
\OC_Util::tearDownFS();
64-
return;
65-
}
74+
if ($count < 10) {
75+
$this->appConfig->setValueInt('files_trashbin', 'background_job_expire_trash_offset', 0);
6676
}
67-
68-
$this->appConfig->setValueInt('files_trashbin', 'background_job_expire_trash_offset', 0);
69-
\OC_Util::tearDownFS();
7077
}
7178

7279
/**
7380
* Act on behalf on trash item owner
7481
*/
75-
protected function setupFS(string $user): bool {
76-
\OC_Util::tearDownFS();
77-
\OC_Util::setupFS($user);
82+
protected function setupFS(string $uid): bool {
83+
$this->setupManager->setupForUser($this->userManager->get($uid));
7884

7985
// Check if this user has a trashbin directory
80-
$view = new View('/' . $user);
86+
$view = new View('/' . $uid);
8187
if (!$view->is_dir('/files_trashbin/files')) {
8288
return false;
8389
}

apps/files_trashbin/tests/BackgroundJob/ExpireTrashTest.php

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
namespace OCA\Files_Trashbin\Tests\BackgroundJob;
99

10+
use OC\Files\SetupManager;
1011
use OCA\Files_Trashbin\BackgroundJob\ExpireTrash;
1112
use OCA\Files_Trashbin\Expiration;
1213
use OCP\AppFramework\Utility\ITimeFactory;
@@ -18,23 +19,14 @@
1819
use Test\TestCase;
1920

2021
class ExpireTrashTest extends TestCase {
21-
/** @var IAppConfig&MockObject */
22-
private $appConfig;
2322

24-
/** @var IUserManager&MockObject */
25-
private $userManager;
26-
27-
/** @var Expiration&MockObject */
28-
private $expiration;
29-
30-
/** @var IJobList&MockObject */
31-
private $jobList;
32-
33-
/** @var LoggerInterface&MockObject */
34-
private $logger;
35-
36-
/** @var ITimeFactory&MockObject */
37-
private $time;
23+
private IAppConfig&MockObject $appConfig;
24+
private IUserManager&MockObject $userManager;
25+
private Expiration&MockObject $expiration;
26+
private IJobList&MockObject $jobList;
27+
private LoggerInterface&MockObject $logger;
28+
private ITimeFactory&MockObject $time;
29+
private SetupManager&MockObject $setupManager;
3830

3931
protected function setUp(): void {
4032
parent::setUp();
@@ -44,6 +36,7 @@ protected function setUp(): void {
4436
$this->expiration = $this->createMock(Expiration::class);
4537
$this->jobList = $this->createMock(IJobList::class);
4638
$this->logger = $this->createMock(LoggerInterface::class);
39+
$this->setupManager = $this->createMock(SetupManager::class);
4740

4841
$this->time = $this->createMock(ITimeFactory::class);
4942
$this->time->method('getTime')
@@ -63,7 +56,7 @@ public function testConstructAndRun(): void {
6356
->with('files_trashbin', 'background_job_expire_trash_offset', 0)
6457
->willReturn(0);
6558

66-
$job = new ExpireTrash($this->appConfig, $this->userManager, $this->expiration, $this->logger, $this->time);
59+
$job = new ExpireTrash($this->appConfig, $this->userManager, $this->expiration, $this->logger, $this->setupManager, $this->time);
6760
$job->start($this->jobList);
6861
}
6962

@@ -74,7 +67,7 @@ public function testBackgroundJobDeactivated(): void {
7467
$this->expiration->expects($this->never())
7568
->method('getMaxAgeAsTimestamp');
7669

77-
$job = new ExpireTrash($this->appConfig, $this->userManager, $this->expiration, $this->logger, $this->time);
70+
$job = new ExpireTrash($this->appConfig, $this->userManager, $this->expiration, $this->logger, $this->setupManager, $this->time);
7871
$job->start($this->jobList);
7972
}
8073
}

build/psalm-baseline.xml

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1792,19 +1792,6 @@
17921792
<code><![CDATA[isset($_['hideFileList']) && $_['hideFileList'] !== true]]></code>
17931793
</RedundantCondition>
17941794
</file>
1795-
<file src="apps/files_trashbin/lib/BackgroundJob/ExpireTrash.php">
1796-
<DeprecatedClass>
1797-
<code><![CDATA[\OC_Util::setupFS($user)]]></code>
1798-
<code><![CDATA[\OC_Util::tearDownFS()]]></code>
1799-
<code><![CDATA[\OC_Util::tearDownFS()]]></code>
1800-
<code><![CDATA[\OC_Util::tearDownFS()]]></code>
1801-
</DeprecatedClass>
1802-
<DeprecatedMethod>
1803-
<code><![CDATA[\OC_Util::tearDownFS()]]></code>
1804-
<code><![CDATA[\OC_Util::tearDownFS()]]></code>
1805-
<code><![CDATA[\OC_Util::tearDownFS()]]></code>
1806-
</DeprecatedMethod>
1807-
</file>
18081795
<file src="apps/files_trashbin/lib/Command/CleanUp.php">
18091796
<DeprecatedClass>
18101797
<code><![CDATA[\OC_Util::setupFS($uid)]]></code>

lib/private/User/Manager.php

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -634,7 +634,7 @@ public function callForSeenUsers(\Closure $callback) {
634634
}
635635

636636
/**
637-
* Getting all userIds that have a listLogin value requires checking the
637+
* Getting all userIds that have a lastLogin value requires checking the
638638
* value in php because on oracle you cannot use a clob in a where clause,
639639
* preventing us from doing a not null or length(value) > 0 check.
640640
*
@@ -812,19 +812,17 @@ public function getDisplayNameCache(): DisplayNameCache {
812812
return $this->displayNameCache;
813813
}
814814

815-
/**
816-
* Gets the list of users sorted by lastLogin, from most recent to least recent
817-
*
818-
* @param int $offset from which offset to fetch
819-
* @return \Iterator<IUser> list of user IDs
820-
* @since 30.0.0
821-
*/
822-
public function getSeenUsers(int $offset = 0): \Iterator {
823-
$limit = 1000;
824-
815+
public function getSeenUsers(int $offset = 0, ?int $limit = null): \Iterator {
825816
do {
826-
$userIds = $this->getSeenUserIds($limit, $offset);
827-
$offset += $limit;
817+
if ($limit !== null) {
818+
$batchSize = min($limit, 1000);
819+
$limit -= $batchSize;
820+
} else {
821+
$batchSize = 1000;
822+
}
823+
824+
$userIds = $this->getSeenUserIds($batchSize, $offset);
825+
$offset += $batchSize;
828826

829827
foreach ($userIds as $userId) {
830828
foreach ($this->backends as $backend) {
@@ -835,6 +833,6 @@ public function getSeenUsers(int $offset = 0): \Iterator {
835833
}
836834
}
837835
}
838-
} while (count($userIds) === $limit);
836+
} while (count($userIds) === $batchSize && $limit !== 0);
839837
}
840838
}

lib/public/IUserManager.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,8 +239,9 @@ public function getLastLoggedInUsers(?int $limit = null, int $offset = 0, string
239239
* The offset argument allows the caller to continue the iteration at a specific offset.
240240
*
241241
* @param int $offset from which offset to fetch
242+
* @param int|null $limit maximum number of records to fetch
242243
* @return \Iterator<IUser> list of IUser object
243244
* @since 32.0.0
244245
*/
245-
public function getSeenUsers(int $offset = 0): \Iterator;
246+
public function getSeenUsers(int $offset = 0, ?int $limit = null): \Iterator;
246247
}

0 commit comments

Comments
 (0)