diff --git a/lib/private/Files/Cache/Propagator.php b/lib/private/Files/Cache/Propagator.php index 2909e998bf97f..4bf88a6084353 100644 --- a/lib/private/Files/Cache/Propagator.php +++ b/lib/private/Files/Cache/Propagator.php @@ -24,16 +24,19 @@ 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; 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 = []; @@ -100,35 +103,44 @@ 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 $unencryptedSizeColumn ELSE $sizeColumn 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 ]); + } } }