Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
feat(files_trashbin): Refactor expire background job to support paral…
…lel 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 <[email protected]>
  • Loading branch information
artonge committed May 21, 2025
commit 08928d572a628d00e9eb23dbd15ffbf0c6fa2c62
59 changes: 33 additions & 26 deletions apps/files_trashbin/lib/BackgroundJob/ExpireTrash.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,31 @@
*/
namespace OCA\Files_Trashbin\BackgroundJob;

use OC\Files\SetupManager;
use OC\Files\View;
use OCA\Files_Trashbin\Expiration;
use OCA\Files_Trashbin\Helper;
use OCA\Files_Trashbin\Trashbin;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\TimedJob;
use OCP\IAppConfig;
use OCP\IUser;
use OCP\IUserManager;
use Psr\Log\LoggerInterface;

class ExpireTrash extends TimedJob {
private const THIRTY_MINUTES = 30 * 60;

public function __construct(
private IAppConfig $appConfig,
private IUserManager $userManager,
private Expiration $expiration,
private LoggerInterface $logger,
private SetupManager $setupManager,
ITimeFactory $time
) {
parent::__construct($time);
// Run once per 30 minutes
$this->setInterval(60 * 30);
$this->setInterval(self::THIRTY_MINUTES);
}

protected function run($argument) {
Expand All @@ -60,44 +64,47 @@ protected function run($argument) {
return;
}

$stopTime = time() + 60 * 30; // Stops after 30 minutes.
$offset = $this->appConfig->getValueInt('files_trashbin', 'background_job_expire_trash_offset', 0);
$users = $this->userManager->getSeenUsers($offset);
$stopTime = time() + self::THIRTY_MINUTES;

foreach ($users as $user) {
try {
do {
$this->appConfig->clearCache();
$offset = $this->appConfig->getValueInt('files_trashbin', 'background_job_expire_trash_offset', 0);
$this->appConfig->setValueInt('files_trashbin', 'background_job_expire_trash_offset', $offset + 10);

$users = $this->userManager->getSeenUsers($offset, 10);
$count = 0;

foreach ($users as $user) {
$uid = $user->getUID();
if (!$this->setupFS($uid)) {
continue;
$count++;

try {
if ($this->setupFS($user)) {
$dirContent = Helper::getTrashFiles('/', $uid, 'mtime');
Trashbin::deleteExpiredFiles($dirContent, $uid);
}
} catch (\Throwable $e) {
$this->logger->error('Error while expiring trashbin for user ' . $uid, ['exception' => $e]);
} finally {
$this->setupManager->tearDown();
}
$dirContent = Helper::getTrashFiles('/', $uid, 'mtime');
Trashbin::deleteExpiredFiles($dirContent, $uid);
} catch (\Throwable $e) {
$this->logger->error('Error while expiring trashbin for user ' . $user->getUID(), ['exception' => $e]);
}

$offset++;
} while (time() < $stopTime && $count === 10);

if ($stopTime < time()) {
$this->appConfig->setValueInt('files_trashbin', 'background_job_expire_trash_offset', $offset);
\OC_Util::tearDownFS();
return;
}
if ($count < 10) {
$this->appConfig->setValueInt('files_trashbin', 'background_job_expire_trash_offset', 0);
}

$this->appConfig->setValueInt('files_trashbin', 'background_job_expire_trash_offset', 0);
\OC_Util::tearDownFS();
}

/**
* Act on behalf on trash item owner
*/
protected function setupFS(string $user): bool {
\OC_Util::tearDownFS();
\OC_Util::setupFS($user);
protected function setupFS(IUser $user): bool {
$this->setupManager->setupForUser($user);

// Check if this user has a trashbin directory
$view = new \OC\Files\View('/' . $user);
$view = new View('/' . $user->getUID());
if (!$view->is_dir('/files_trashbin/files')) {
return false;
}
Expand Down
9 changes: 7 additions & 2 deletions apps/files_trashbin/tests/BackgroundJob/ExpireTrashTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

namespace OCA\Files_Trashbin\Tests\BackgroundJob;

use OC\Files\SetupManager;
use OCA\Files_Trashbin\BackgroundJob\ExpireTrash;
use OCA\Files_Trashbin\Expiration;
use OCP\AppFramework\Utility\ITimeFactory;
Expand Down Expand Up @@ -54,6 +55,9 @@ class ExpireTrashTest extends TestCase {
/** @var ITimeFactory&MockObject */
private $time;

/** @var SetupManager&MockObject */
private $setupManager;

protected function setUp(): void {
parent::setUp();

Expand All @@ -62,6 +66,7 @@ protected function setUp(): void {
$this->expiration = $this->createMock(Expiration::class);
$this->jobList = $this->createMock(IJobList::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->setupManager = $this->createMock(SetupManager::class);

$this->time = $this->createMock(ITimeFactory::class);
$this->time->method('getTime')
Expand All @@ -81,7 +86,7 @@ public function testConstructAndRun(): void {
->with('files_trashbin', 'background_job_expire_trash_offset', 0)
->willReturn(0);

$job = new ExpireTrash($this->appConfig, $this->userManager, $this->expiration, $this->logger, $this->time);
$job = new ExpireTrash($this->appConfig, $this->userManager, $this->expiration, $this->logger, $this->setupManager, $this->time);
$job->start($this->jobList);
}

Expand All @@ -92,7 +97,7 @@ public function testBackgroundJobDeactivated(): void {
$this->expiration->expects($this->never())
->method('getMaxAgeAsTimestamp');

$job = new ExpireTrash($this->appConfig, $this->userManager, $this->expiration, $this->logger, $this->time);
$job = new ExpireTrash($this->appConfig, $this->userManager, $this->expiration, $this->logger, $this->setupManager, $this->time);
$job->start($this->jobList);
}
}
Loading