Skip to content

Commit 372e8a9

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

File tree

1 file changed

+25
-17
lines changed

1 file changed

+25
-17
lines changed

lib/private/Files/Cache/Propagator.php

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
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;
@@ -34,6 +35,7 @@
3435
* Propagate etags and mtimes within the storage
3536
*/
3637
class Propagator implements IPropagator {
38+
public const MAX_RETRIES = 3;
3739
private $inBatch = false;
3840

3941
private $batch = [];
@@ -100,35 +102,41 @@ public function propagateChange($internalPath, $time, $sizeDifference = 0) {
100102
$builder->set('etag', $builder->createNamedParameter($etag, IQueryBuilder::PARAM_STR));
101103
}
102104

103-
$builder->execute();
104-
105105
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)));
106+
$hasCalculatedSize = $builder->expr()->gt('size', $builder->expr()->literal(-1, IQUeryBuilder::PARAM_INT));
107+
$sizeColumn = $builder->getColumnName('size');
108+
$newSize = $builder->func()->greatest(
109+
$builder->func()->add('size', $builder->createNamedParameter($sizeDifference)),
110+
$builder->createNamedParameter(-1, IQueryBuilder::PARAM_INT)
111+
);
112+
113+
// Only update if row had a previously calculated size
114+
$builder->set('size', $builder->createFunction("CASE WHEN $hasCalculatedSize THEN $newSize ELSE $sizeColumn END"));
116115

117116
if ($this->storage->instanceOfStorage(Encryption::class)) {
118117
// 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));
118+
$hasUnencryptedSize = $builder->expr()->neq('unencrypted_size', $builder->expr()->literal(0, IQueryBuilder::PARAM_INT));
120119
$sizeColumn = $builder->getColumnName('size');
121120
$unencryptedSizeColumn = $builder->getColumnName('unencrypted_size');
122-
$builder->set('unencrypted_size', $builder->func()->greatest(
121+
$newUnencryptedSize = $builder->func()->greatest(
123122
$builder->func()->add(
124-
$builder->createFunction("CASE WHEN $eq THEN $unencryptedSizeColumn ELSE $sizeColumn END"),
123+
$builder->createFunction("CASE WHEN $hasUnencryptedSize THEN $sizeColumn ELSE $unencryptedSizeColumn END"),
125124
$builder->createNamedParameter($sizeDifference)
126125
),
127126
$builder->createNamedParameter(-1, IQueryBuilder::PARAM_INT)
128-
));
127+
);
128+
129+
// Only update if row had a previously calculated size
130+
$builder->set('unencrypted_size', $builder->createFunction("CASE WHEN $hasCalculatedSize THEN $newUnencryptedSize ELSE $unencryptedSizeColumn END"));
129131
}
132+
}
130133

131-
$builder->execute();
134+
for ($i = 0; $i < self::MAX_RETRIES; $i++) {
135+
try {
136+
$builder->executeStatement();
137+
break;
138+
} catch (RetryableException $e) {
139+
}
132140
}
133141
}
134142

0 commit comments

Comments
 (0)