summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorraul <raul@nextcloud.com>2022-09-29 19:01:42 +0200
committerRaul Ferreira Fuentes (Rebase PR Action) <Raudius@users.noreply.github.com>2022-10-25 15:08:22 +0000
commitba12309f3c6db5ced1941b3f3cc60a680df8390f (patch)
treeb767f267e8eb8fbcf5c879710de5a7378ce3728a
parent02334ce03a1c002e5c764d32513fba6780f3a7ba (diff)
downloadnextcloud-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.php46
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 ]);
+ }
}
}