Skip to content

Commit 3db1f6d

Browse files
committed
Fix: Prevent deadlocks during mtime/size/etag propagation
Signed-off-by: raul <raul@nextcloud.com>
1 parent 3969497 commit 3db1f6d

File tree

1 file changed

+29
-17
lines changed

1 file changed

+29
-17
lines changed

lib/private/Files/Cache/Propagator.php

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

2525
namespace OC\Files\Cache;
2626

27+
use Doctrine\DBAL\Exception\RetryableException;
2728
use OC\Files\Storage\Wrapper\Encryption;
2829
use OCP\DB\QueryBuilder\IQueryBuilder;
2930
use OCP\Files\Cache\IPropagator;
3031
use OCP\Files\Storage\IReliableEtagStorage;
3132
use OCP\IDBConnection;
33+
use Psr\Log\LoggerInterface;
3234

3335
/**
3436
* Propagate etags and mtimes within the storage
3537
*/
3638
class Propagator implements IPropagator {
39+
public const MAX_RETRIES = 3;
3740
private $inBatch = false;
3841

3942
private $batch = [];
@@ -100,35 +103,44 @@ public function propagateChange($internalPath, $time, $sizeDifference = 0) {
100103
$builder->set('etag', $builder->createNamedParameter($etag, IQueryBuilder::PARAM_STR));
101104
}
102105

103-
$builder->execute();
104-
105106
if ($sizeDifference !== 0) {
106-
// we need to do size separably so we can ignore entries with uncalculated size
107-
$builder = $this->connection->getQueryBuilder();
108-
$builder->update('filecache')
109-
->set('size', $builder->func()->greatest(
110-
$builder->func()->add('size', $builder->createNamedParameter($sizeDifference)),
111-
$builder->createNamedParameter(-1, IQueryBuilder::PARAM_INT)
112-
))
113-
->where($builder->expr()->eq('storage', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
114-
->andWhere($builder->expr()->in('path_hash', $hashParams))
115-
->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"));
116116

117117
if ($this->storage->instanceOfStorage(Encryption::class)) {
118118
// 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
119-
$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));
120120
$sizeColumn = $builder->getColumnName('size');
121121
$unencryptedSizeColumn = $builder->getColumnName('unencrypted_size');
122-
$builder->set('unencrypted_size', $builder->func()->greatest(
122+
$newUnencryptedSize = $builder->func()->greatest(
123123
$builder->func()->add(
124-
$builder->createFunction("CASE WHEN $eq THEN $unencryptedSizeColumn ELSE $sizeColumn END"),
124+
$builder->createFunction("CASE WHEN $hasUnencryptedSize THEN $sizeColumn ELSE $unencryptedSizeColumn END"),
125125
$builder->createNamedParameter($sizeDifference)
126126
),
127127
$builder->createNamedParameter(-1, IQueryBuilder::PARAM_INT)
128-
));
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"));
129132
}
133+
}
130134

131-
$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 = \OCP\Server::get(LoggerInterface::class);
142+
$loggerInterface->warning('Retrying propagation query after retryable exception.', [ 'exception' => $e ]);
143+
}
132144
}
133145
}
134146

0 commit comments

Comments
 (0)