diff options
author | raul <raul@nextcloud.com> | 2022-09-29 19:01:42 +0200 |
---|---|---|
committer | Raul Ferreira Fuentes (Rebase PR Action) <Raudius@users.noreply.github.com> | 2022-10-25 15:08:22 +0000 |
commit | ba12309f3c6db5ced1941b3f3cc60a680df8390f (patch) | |
tree | b767f267e8eb8fbcf5c879710de5a7378ce3728a | |
parent | 02334ce03a1c002e5c764d32513fba6780f3a7ba (diff) | |
download | nextcloud-server-ba12309f3c6db5ced1941b3f3cc60a680df8390f.tar.gz nextcloud-server-ba12309f3c6db5ced1941b3f3cc60a680df8390f.zip |
Fix: Prevent deadlocks during mtime/size/etag propagation
Signed-off-by: raul <raul@nextcloud.com>
-rw-r--r-- | lib/private/Files/Cache/Propagator.php | 46 |
1 files changed, 29 insertions, 17 deletions
diff --git a/lib/private/Files/Cache/Propagator.php b/lib/private/Files/Cache/Propagator.php index 4086701f011..0a4b23d5bd1 100644 --- a/lib/private/Files/Cache/Propagator.php +++ b/lib/private/Files/Cache/Propagator.php @@ -25,16 +25,19 @@ namespace OC\Files\Cache; use OC\DB\QueryBuilder\QueryFunction; +use Doctrine\DBAL\Exception\RetryableException; use OC\Files\Storage\Wrapper\Encryption; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Files\Cache\IPropagator; use OCP\Files\Storage\IReliableEtagStorage; use OCP\IDBConnection; +use Psr\Log\LoggerInterface; /** * Propagate etags and mtimes within the storage */ class Propagator implements IPropagator { + public const MAX_RETRIES = 3; private $inBatch = false; private $batch = []; @@ -101,35 +104,44 @@ class Propagator implements IPropagator { $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( - new QueryFunction("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(); + break; + } catch (RetryableException $e) { + /** @var LoggerInterface $loggerInterface */ + $loggerInterface = \OCP\Server::get(LoggerInterface::class); + $loggerInterface->warning('Retrying propagation query after retryable exception.', [ 'exception' => $e ]); + } } } |