diff options
author | Vincent Petry <vincent@nextcloud.com> | 2022-10-27 11:53:57 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-10-27 11:53:57 +0300 |
commit | 9ca4d1368d593dd1ecce53b6eee41517fb0ccc59 (patch) | |
tree | 3d07299df0f7727b5fd3d26e0f751dcc18768700 | |
parent | 637a58f18fd09579ed62818f6b1efd17b39fb244 (diff) | |
parent | bdbacdffc231c4dec65f7ea1809baeb697934c1a (diff) |
Merge pull request #34447 from nextcloud/backport/34302/stable24
[stable24] Fix: Prevent deadlocks during mtime/size/etag propagation
-rw-r--r-- | lib/private/Files/Cache/Propagator.php | 47 |
1 files changed, 29 insertions, 18 deletions
diff --git a/lib/private/Files/Cache/Propagator.php b/lib/private/Files/Cache/Propagator.php index 4086701f011..3fba8aaaed1 100644 --- a/lib/private/Files/Cache/Propagator.php +++ b/lib/private/Files/Cache/Propagator.php @@ -24,17 +24,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 +103,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 $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 = \OC::$server->get(LoggerInterface::class); + $loggerInterface->warning('Retrying propagation query after retryable exception.', [ 'exception' => $e ]); + } } } |