Skip to content

Commit 8eb3a6e

Browse files
authored
Merge pull request #44009 from nextcloud/backport/43252/stable27
2 parents 5707acc + 8e5b14c commit 8eb3a6e

2 files changed

Lines changed: 69 additions & 13 deletions

File tree

apps/files_sharing/lib/DeleteOrphanedSharesJob.php

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
<?php
2+
3+
declare(strict_types=1);
4+
25
/**
36
* @copyright Copyright (c) 2016, ownCloud, Inc.
47
*
@@ -24,21 +27,45 @@
2427
*/
2528
namespace OCA\Files_Sharing;
2629

30+
use OCP\AppFramework\Db\TTransactional;
2731
use OCP\AppFramework\Utility\ITimeFactory;
2832
use OCP\BackgroundJob\TimedJob;
33+
use OCP\DB\QueryBuilder\IQueryBuilder;
34+
use OCP\IDBConnection;
35+
use PDO;
36+
use Psr\Log\LoggerInterface;
37+
use function array_map;
2938

3039
/**
3140
* Delete all share entries that have no matching entries in the file cache table.
3241
*/
3342
class DeleteOrphanedSharesJob extends TimedJob {
43+
44+
use TTransactional;
45+
46+
private const CHUNK_SIZE = 1000;
47+
48+
private const INTERVAL = 24 * 60 * 60; // 1 day
49+
50+
private IDBConnection $db;
51+
52+
private LoggerInterface $logger;
53+
3454
/**
3555
* sets the correct interval for this timed job
3656
*/
37-
public function __construct(ITimeFactory $time) {
57+
public function __construct(
58+
ITimeFactory $time,
59+
IDBConnection $db,
60+
LoggerInterface $logger
61+
) {
3862
parent::__construct($time);
3963

40-
$this->setInterval(24 * 60 * 60); // 1 day
64+
$this->db = $db;
65+
66+
$this->setInterval(self::INTERVAL); // 1 day
4167
$this->setTimeSensitivity(self::TIME_INSENSITIVE);
68+
$this->logger = $logger;
4269
}
4370

4471
/**
@@ -47,15 +74,45 @@ public function __construct(ITimeFactory $time) {
4774
* @param array $argument unused argument
4875
*/
4976
public function run($argument) {
50-
$connection = \OC::$server->getDatabaseConnection();
51-
$logger = \OC::$server->getLogger();
52-
53-
$sql =
54-
'DELETE FROM `*PREFIX*share` ' .
55-
'WHERE `item_type` in (\'file\', \'folder\') ' .
56-
'AND NOT EXISTS (SELECT `fileid` FROM `*PREFIX*filecache` WHERE `file_source` = `fileid`)';
77+
$qbSelect = $this->db->getQueryBuilder();
78+
$qbSelect->select('id')
79+
->from('share', 's')
80+
->leftJoin('s', 'filecache', 'fc', $qbSelect->expr()->eq('s.file_source', 'fc.fileid'))
81+
->where($qbSelect->expr()->isNull('fc.fileid'))
82+
->setMaxResults(self::CHUNK_SIZE);
83+
$deleteQb = $this->db->getQueryBuilder();
84+
$deleteQb->delete('share')
85+
->where(
86+
$deleteQb->expr()->in('id', $deleteQb->createParameter('ids'), IQueryBuilder::PARAM_INT_ARRAY)
87+
);
5788

58-
$deletedEntries = $connection->executeUpdate($sql);
59-
$logger->debug("$deletedEntries orphaned share(s) deleted", ['app' => 'DeleteOrphanedSharesJob']);
89+
/**
90+
* Read a chunk of orphan rows and delete them. Continue as long as the
91+
* chunk is filled and time before the next cron run does not run out.
92+
*
93+
* Note: With isolation level READ COMMITTED, the database will allow
94+
* other transactions to delete rows between our SELECT and DELETE. In
95+
* that (unlikely) case, our DELETE will have fewer affected rows than
96+
* IDs passed for the WHERE IN. If this happens while processing a full
97+
* chunk, the logic below will stop prematurely.
98+
* Note: The queries below are optimized for low database locking. They
99+
* could be combined into one single DELETE with join or sub query, but
100+
* that has shown to (dead)lock often.
101+
*/
102+
$cutOff = $this->time->getTime() + self::INTERVAL;
103+
do {
104+
$deleted = $this->atomic(function () use ($qbSelect, $deleteQb) {
105+
$result = $qbSelect->executeQuery();
106+
$ids = array_map('intval', $result->fetchAll(PDO::FETCH_COLUMN));
107+
$result->closeCursor();
108+
$deleteQb->setParameter('ids', $ids, IQueryBuilder::PARAM_INT_ARRAY);
109+
$deleted = $deleteQb->executeStatement();
110+
$this->logger->debug("{deleted} orphaned share(s) deleted", [
111+
'app' => 'DeleteOrphanedSharesJob',
112+
'deleted' => $deleted,
113+
]);
114+
return $deleted;
115+
}, $this->db);
116+
} while ($deleted >= self::CHUNK_SIZE && $this->time->getTime() <= $cutOff);
60117
}
61118
}

apps/files_sharing/tests/DeleteOrphanedSharesJobTest.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
namespace OCA\Files_Sharing\Tests;
2828

2929
use OCA\Files_Sharing\DeleteOrphanedSharesJob;
30-
use OCP\AppFramework\Utility\ITimeFactory;
3130
use OCP\Share\IShare;
3231

3332
/**
@@ -94,7 +93,7 @@ protected function setUp(): void {
9493

9594
\OC::registerShareHooks(\OC::$server->getSystemConfig());
9695

97-
$this->job = new DeleteOrphanedSharesJob(\OCP\Server::get(ITimeFactory::class));
96+
$this->job = \OCP\Server::get(DeleteOrphanedSharesJob::class);
9897
}
9998

10099
protected function tearDown(): void {

0 commit comments

Comments
 (0)