Skip to content

Commit 9ca4d13

Browse files
authored
Merge pull request #34447 from nextcloud/backport/34302/stable24
[stable24] Fix: Prevent deadlocks during mtime/size/etag propagation
2 parents 637a58f + bdbacdf commit 9ca4d13

File tree

1 file changed

+29
-18
lines changed

1 file changed

+29
-18
lines changed

lib/private/Files/Cache/Propagator.php

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,19 @@
2424

2525
namespace OC\Files\Cache;
2626

27-
use OC\DB\QueryBuilder\QueryFunction;
27+
use Doctrine\DBAL\Exception\RetryableException;
2828
use OC\Files\Storage\Wrapper\Encryption;
2929
use OCP\DB\QueryBuilder\IQueryBuilder;
3030
use OCP\Files\Cache\IPropagator;
3131
use OCP\Files\Storage\IReliableEtagStorage;
3232
use OCP\IDBConnection;
33+
use Psr\Log\LoggerInterface;
3334

3435
/**
3536
* Propagate etags and mtimes within the storage
3637
*/
3738
class Propagator implements IPropagator {
39+
public const MAX_RETRIES = 3;
3840
private $inBatch = false;
3941

4042
private $batch = [];
@@ -101,35 +103,44 @@ public function propagateChange($internalPath, $time, $sizeDifference = 0) {
101103
$builder->set('etag', $builder->createNamedParameter($etag, IQueryBuilder::PARAM_STR));
102104
}
103105

104-
$builder->execute();
105-
106106
if ($sizeDifference !== 0) {
107-
// we need to do size separably so we can ignore entries with uncalculated size
108-
$builder = $this->connection->getQueryBuilder();
109-
$builder->update('filecache')
110-
->set('size', $builder->func()->greatest(
111-
$builder->func()->add('size', $builder->createNamedParameter($sizeDifference)),
112-
$builder->createNamedParameter(-1, IQueryBuilder::PARAM_INT)
113-
))
114-
->where($builder->expr()->eq('storage', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
115-
->andWhere($builder->expr()->in('path_hash', $hashParams))
116-
->andWhere($builder->expr()->gt('size', $builder->expr()->literal(-1, IQueryBuilder::PARAM_INT)));
107+
$hasCalculatedSize = $builder->expr()->gt('size', $builder->expr()->literal(-1, IQUeryBuilder::PARAM_INT));
108+
$sizeColumn = $builder->getColumnName('size');
109+
$newSize = $builder->func()->greatest(
110+
$builder->func()->add('size', $builder->createNamedParameter($sizeDifference)),
111+
$builder->createNamedParameter(-1, IQueryBuilder::PARAM_INT)
112+
);
113+
114+
// Only update if row had a previously calculated size
115+
$builder->set('size', $builder->createFunction("CASE WHEN $hasCalculatedSize THEN $newSize ELSE $sizeColumn END"));
117116

118117
if ($this->storage->instanceOfStorage(Encryption::class)) {
119118
// in case of encryption being enabled after some files are already uploaded, some entries will have an unencrypted_size of 0 and a non-zero size
120-
$eq = $builder->expr()->eq('unencrypted_size', $builder->expr()->literal(0, IQueryBuilder::PARAM_INT));
119+
$hasUnencryptedSize = $builder->expr()->neq('unencrypted_size', $builder->expr()->literal(0, IQueryBuilder::PARAM_INT));
121120
$sizeColumn = $builder->getColumnName('size');
122121
$unencryptedSizeColumn = $builder->getColumnName('unencrypted_size');
123-
$builder->set('unencrypted_size', $builder->func()->greatest(
122+
$newUnencryptedSize = $builder->func()->greatest(
124123
$builder->func()->add(
125-
new QueryFunction("CASE WHEN $eq THEN $unencryptedSizeColumn ELSE $sizeColumn END"),
124+
$builder->createFunction("CASE WHEN $hasUnencryptedSize THEN $unencryptedSizeColumn ELSE $sizeColumn END"),
126125
$builder->createNamedParameter($sizeDifference)
127126
),
128127
$builder->createNamedParameter(-1, IQueryBuilder::PARAM_INT)
129-
));
128+
);
129+
130+
// Only update if row had a previously calculated size
131+
$builder->set('unencrypted_size', $builder->createFunction("CASE WHEN $hasCalculatedSize THEN $newUnencryptedSize ELSE $unencryptedSizeColumn END"));
130132
}
133+
}
131134

132-
$builder->execute();
135+
for ($i = 0; $i < self::MAX_RETRIES; $i++) {
136+
try {
137+
$builder->executeStatement();
138+
break;
139+
} catch (RetryableException $e) {
140+
/** @var LoggerInterface $loggerInterface */
141+
$loggerInterface = \OC::$server->get(LoggerInterface::class);
142+
$loggerInterface->warning('Retrying propagation query after retryable exception.', [ 'exception' => $e ]);
143+
}
133144
}
134145
}
135146

0 commit comments

Comments
 (0)