Skip to content

Commit

Permalink
Move retry logic to the QueryBuilder
Browse files Browse the repository at this point in the history
Signed-off-by: Louis Chemineau <[email protected]>
  • Loading branch information
artonge committed Apr 8, 2024
1 parent ff2100a commit 560e934
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 24 deletions.
26 changes: 25 additions & 1 deletion lib/private/DB/QueryBuilder/QueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
*/
namespace OC\DB\QueryBuilder;

use Doctrine\DBAL\Exception\RetryableException;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Platforms\OraclePlatform;
use Doctrine\DBAL\Platforms\PostgreSQL94Platform;
Expand Down Expand Up @@ -76,6 +77,8 @@ class QueryBuilder implements IQueryBuilder {
/** @var string */
protected $lastInsertedTable;

private $retryLimit = 4;

/**
* Initializes a new QueryBuilder.
*
Expand Down Expand Up @@ -277,13 +280,34 @@ public function execute() {
]);
}

$result = $this->queryBuilder->execute();
for ($i = 1; $i <= $this->retryLimit; $i++) {
try {
$result = $this->queryBuilder->execute();
break;
} catch (RetryableException $e) {
// Throw if we reached retryLimit.
if ($i === $this->retryLimit) {
throw $e;
}

// Sleep a bit to give some time to the other transaction to finish.
usleep(100 * 1000 * $i);
}
}

if (is_int($result)) {
return $result;
}
return new ResultAdapter($result);
}

/**
* @param int $retryLimit - Retry the query up to $retryLimit times in case of a RetryableException. Initially equal to 4.
*/
public function setRetryLimit(int $retryLimit): void {
$this->retryLimit = $retryLimit;
}

public function executeQuery(): IResult {
if ($this->getType() !== \Doctrine\DBAL\Query\QueryBuilder::SELECT) {
throw new \RuntimeException('Invalid query type, expected SELECT query');
Expand Down
29 changes: 6 additions & 23 deletions lib/private/Files/Cache/Cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@

namespace OC\Files\Cache;

use Doctrine\DBAL\Exception\RetryableException;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use OC\Files\Search\SearchComparison;
use OC\Files\Search\SearchQuery;
Expand Down Expand Up @@ -715,28 +714,12 @@ public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) {
$query->set('encrypted', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT));
}

// Retry transaction in case of RetryableException like deadlocks.
// Retry up to 4 times because we should receive up to 4 concurrent requests from the frontend
$retryLimit = 4;
for ($i = 1; $i <= $retryLimit; $i++) {
try {
$this->connection->beginTransaction();
$query->executeStatement();
break;
} catch (\OC\DatabaseException $e) {
$this->connection->rollBack();
throw $e;
} catch (RetryableException $e) {
// Simply throw if we already retried 4 times.
if ($i === $retryLimit) {
throw $e;
}

$this->connection->rollBack();

// Sleep a bit to give some time to the other transaction to finish.
usleep(100 * 1000 * $i);
}
try {
$this->connection->beginTransaction();
$query->executeStatement(4);
} catch (\OC\DatabaseException $e) {
$this->connection->rollBack();
throw $e;
}
} else {
$this->connection->beginTransaction();
Expand Down
7 changes: 7 additions & 0 deletions lib/public/DB/QueryBuilder/IQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,13 @@ public function execute();
*/
public function executeQuery(): IResult;

/**
* @since 30.0.0
*
* @param int $retryLimit - Retry the query up to $retryLimit times in case of a RetryableException. Initially equal to 4.
*/
public function setRetryLimit(int $retryLimit): void;

/**
* Execute insert, update and delete statements
*
Expand Down

0 comments on commit 560e934

Please sign in to comment.