Skip to content

Commit

Permalink
Fix: Prevent deadlocks during mtime/size/etag propagation
Browse files Browse the repository at this point in the history
Signed-off-by: raul <[email protected]>
  • Loading branch information
Raudius committed Sep 28, 2022
1 parent 5709084 commit ff32a09
Showing 1 changed file with 26 additions and 17 deletions.
43 changes: 26 additions & 17 deletions lib/private/Files/Cache/Propagator.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

namespace OC\Files\Cache;

use Doctrine\DBAL\Exception\RetryableException;
use OC\Files\Storage\Wrapper\Encryption;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Files\Cache\IPropagator;
Expand All @@ -34,6 +35,7 @@
* Propagate etags and mtimes within the storage
*/
class Propagator implements IPropagator {
public const MAX_RETRIES = 3;
private $inBatch = false;

private $batch = [];
Expand Down Expand Up @@ -100,35 +102,42 @@ public function propagateChange($internalPath, $time, $sizeDifference = 0) {
$builder->set('etag', $builder->createNamedParameter($etag, IQueryBuilder::PARAM_STR));
}

$builder->execute();

if ($sizeDifference !== 0) {
// we need to do size separably so we can ignore entries with uncalculated size
$builder = $this->connection->getQueryBuilder();
$builder->update('filecache')
->set('size', $builder->func()->greatest(
$builder->func()->add('size', $builder->createNamedParameter($sizeDifference)),
$builder->createNamedParameter(-1, IQueryBuilder::PARAM_INT)
))
->where($builder->expr()->eq('storage', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
->andWhere($builder->expr()->in('path_hash', $hashParams))
->andWhere($builder->expr()->gt('size', $builder->expr()->literal(-1, IQueryBuilder::PARAM_INT)));
$hasCalculatedSize = $builder->expr()->gt('size', $builder->expr()->literal(-1, IQUeryBuilder::PARAM_INT));
$sizeColumn = $builder->getColumnName('size');
$newSize = $builder->func()->greatest(
$builder->func()->add('size', $builder->createNamedParameter($sizeDifference)),
$builder->createNamedParameter(-1, IQueryBuilder::PARAM_INT)
);

// Only update if row had a previously calculated size
$builder->set('size', $builder->createFunction("CASE WHEN $hasCalculatedSize THEN $newSize ELSE $sizeColumn END"));

if ($this->storage->instanceOfStorage(Encryption::class)) {
// 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
$eq = $builder->expr()->eq('unencrypted_size', $builder->expr()->literal(0, IQueryBuilder::PARAM_INT));
$hasUnencryptedSize = $builder->expr()->neq('unencrypted_size', $builder->expr()->literal(0, IQueryBuilder::PARAM_INT));
$sizeColumn = $builder->getColumnName('size');
$unencryptedSizeColumn = $builder->getColumnName('unencrypted_size');
$builder->set('unencrypted_size', $builder->func()->greatest(
$newUnencryptedSize = $builder->func()->greatest(
$builder->func()->add(
$builder->createFunction("CASE WHEN $eq THEN $unencryptedSizeColumn ELSE $sizeColumn END"),
$builder->createFunction("CASE WHEN $hasUnencryptedSize THEN $sizeColumn ELSE $unencryptedSizeColumn END"),
$builder->createNamedParameter($sizeDifference)
),
$builder->createNamedParameter(-1, IQueryBuilder::PARAM_INT)
));
);

// Only update if row had a previously calculated size
$builder->set('unencrypted_size', $builder->createFunction("CASE WHEN $hasCalculatedSize THEN $newUnencryptedSize ELSE $unencryptedSizeColumn END"));
}
}

$builder->execute();
for ($i = 0; $i < self::MAX_RETRIES; $i++) {
try {
$builder->executeStatement();
} catch (RetryableException $e) {
continue;
}
break;
}
}

Expand Down

0 comments on commit ff32a09

Please sign in to comment.