Skip to content

Commit 591cadb

Browse files
authored
Merge pull request #341 from utopia-php/feat-global-timeouts
Global timeouts using before query hooks
2 parents e0b832d + 0861e13 commit 591cadb

8 files changed

Lines changed: 136 additions & 145 deletions

File tree

composer.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Database/Adapter.php

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ abstract class Adapter
3434
*/
3535
protected array $metadata = [];
3636

37-
protected static ?int $timeout = null;
38-
3937
/**
4038
* @param string $key
4139
* @param mixed $value
@@ -205,7 +203,12 @@ public function before(string $event, string $name = '', ?callable $callback = n
205203
if (!isset($this->transformations[$event])) {
206204
$this->transformations[$event] = [];
207205
}
208-
$this->transformations[$event][$name] = $callback;
206+
207+
if (\is_null($callback)) {
208+
unset($this->transformations[$event][$name]);
209+
} else {
210+
$this->transformations[$event][$name] = $callback;
211+
}
209212

210213
return $this;
211214
}
@@ -458,11 +461,10 @@ abstract public function deleteDocument(string $collection, string $id): bool;
458461
* @param array<string> $orderTypes
459462
* @param array<string, mixed> $cursor
460463
* @param string $cursorDirection
461-
* @param int|null $timeout
462464
*
463465
* @return array<Document>
464466
*/
465-
abstract public function find(string $collection, array $queries = [], ?int $limit = 25, ?int $offset = null, array $orderAttributes = [], array $orderTypes = [], array $cursor = [], string $cursorDirection = Database::CURSOR_AFTER, ?int $timeout = null): array;
467+
abstract public function find(string $collection, array $queries = [], ?int $limit = 25, ?int $offset = null, array $orderAttributes = [], array $orderTypes = [], array $cursor = [], string $cursorDirection = Database::CURSOR_AFTER): array;
466468

467469
/**
468470
* Sum an attribute
@@ -474,7 +476,7 @@ abstract public function find(string $collection, array $queries = [], ?int $lim
474476
*
475477
* @return int|float
476478
*/
477-
abstract public function sum(string $collection, string $attribute, array $queries = [], ?int $max = null, ?int $timeout = null): float|int;
479+
abstract public function sum(string $collection, string $attribute, array $queries = [], ?int $max = null): float|int;
478480

479481
/**
480482
* Count Documents
@@ -485,7 +487,7 @@ abstract public function sum(string $collection, string $attribute, array $queri
485487
*
486488
* @return int
487489
*/
488-
abstract public function count(string $collection, array $queries = [], ?int $max = null, ?int $timeout = null): int;
490+
abstract public function count(string $collection, array $queries = [], ?int $max = null): int;
489491

490492
/**
491493
* Get Collection Size
@@ -745,32 +747,28 @@ abstract public function getMaxIndexLength(): int;
745747
* Set a global timeout for database queries in milliseconds.
746748
*
747749
* This function allows you to set a maximum execution time for all database
748-
* queries executed using the library. Once this timeout is set, any database
749-
* query that takes longer than the specified time will be automatically
750-
* terminated by the library, and an appropriate error or exception will be
751-
* raised to handle the timeout condition.
750+
* queries executed using the library, or a specific event specified by the
751+
* event parameter. Once this timeout is set, any database query that takes
752+
* longer than the specified time will be automatically terminated by the library,
753+
* and an appropriate error or exception will be raised to handle the timeout condition.
752754
*
753755
* @param int $milliseconds The timeout value in milliseconds for database queries.
756+
* @param string $event The event the timeout should fire fore
754757
* @return void
755758
*
756-
* @throws \Exception The provided timeout value must be greater than or equal to 0.
757-
*/
758-
public static function setTimeout(int $milliseconds): void
759-
{
760-
if ($milliseconds <= 0) {
761-
throw new DatabaseException('Timeout must be greater than 0');
762-
}
763-
self::$timeout = $milliseconds;
764-
}
759+
* @throws Exception The provided timeout value must be greater than or equal to 0.
760+
*/
761+
abstract public function setTimeout(int $milliseconds, string $event = Database::EVENT_ALL): void;
765762

766763
/**
767764
* Clears a global timeout for database queries.
768765
*
766+
* @param string $event
769767
* @return void
770-
*
771-
*/
772-
public static function clearTimeout(): void
768+
*/
769+
public function clearTimeout(string $event): void
773770
{
774-
self::$timeout = null;
771+
// Clear existing callback
772+
$this->before($event, 'timeout', null);
775773
}
776774
}

src/Database/Adapter/MariaDB.php

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
use Exception;
66
use PDO;
77
use PDOException;
8-
use Utopia\Database\Exception as DatabaseException;
98
use Utopia\Database\Database;
109
use Utopia\Database\Document;
11-
use Utopia\Database\Exception\Duplicate;
12-
use Utopia\Database\Exception\Timeout;
10+
use Utopia\Database\Exception as DatabaseException;
11+
use Utopia\Database\Exception\Duplicate as DuplicateException;
12+
use Utopia\Database\Exception\Timeout as TimeoutException;
1313
use Utopia\Database\Query;
1414
use Utopia\Database\Validator\Authorization;
1515

@@ -641,7 +641,7 @@ public function deleteIndex(string $collection, string $id): bool
641641
* @return Document
642642
* @throws Exception
643643
* @throws PDOException
644-
* @throws Duplicate
644+
* @throws DuplicateException
645645
*/
646646
public function createDocument(string $collection, Document $document): Document
647647
{
@@ -736,7 +736,7 @@ public function createDocument(string $collection, Document $document): Document
736736
switch ($e->getCode()) {
737737
case 1062:
738738
case 23000:
739-
throw new Duplicate('Duplicated document: ' . $e->getMessage());
739+
throw new DuplicateException('Duplicated document: ' . $e->getMessage());
740740

741741
default:
742742
throw $e;
@@ -758,7 +758,7 @@ public function createDocument(string $collection, Document $document): Document
758758
* @return Document
759759
* @throws Exception
760760
* @throws PDOException
761-
* @throws Duplicate
761+
* @throws DuplicateException
762762
*/
763763
public function updateDocument(string $collection, Document $document): Document
764764
{
@@ -939,7 +939,7 @@ public function updateDocument(string $collection, Document $document): Document
939939
switch ($e->getCode()) {
940940
case 1062:
941941
case 23000:
942-
throw new Duplicate('Duplicated document: ' . $e->getMessage());
942+
throw new DuplicateException('Duplicated document: ' . $e->getMessage());
943943

944944
default:
945945
throw $e;
@@ -1058,12 +1058,11 @@ public function deleteDocument(string $collection, string $id): bool
10581058
* @param array<string> $orderTypes
10591059
* @param array<string, mixed> $cursor
10601060
* @param string $cursorDirection
1061-
* @param int|null $timeout
10621061
* @return array<Document>
10631062
* @throws DatabaseException
1064-
* @throws Timeout
1063+
* @throws TimeoutException
10651064
*/
1066-
public function find(string $collection, array $queries = [], ?int $limit = 25, ?int $offset = null, array $orderAttributes = [], array $orderTypes = [], array $cursor = [], string $cursorDirection = Database::CURSOR_AFTER, ?int $timeout = null): array
1065+
public function find(string $collection, array $queries = [], ?int $limit = 25, ?int $offset = null, array $orderAttributes = [], array $orderTypes = [], array $cursor = [], string $cursorDirection = Database::CURSOR_AFTER): array
10671066
{
10681067
$name = $this->filter($collection);
10691068
$roles = Authorization::getRoles();
@@ -1173,11 +1172,8 @@ public function find(string $collection, array $queries = [], ?int $limit = 25,
11731172

11741173
$sql = $this->trigger(Database::EVENT_DOCUMENT_FIND, $sql);
11751174

1176-
if ($timeout || static::$timeout) {
1177-
$sql = $this->setTimeoutForQuery($sql, $timeout ? $timeout : static::$timeout);
1178-
}
1179-
11801175
$stmt = $this->getPDO()->prepare($sql);
1176+
11811177
foreach ($queries as $query) {
11821178
$this->bindConditionValue($stmt, $query);
11831179
}
@@ -1257,7 +1253,7 @@ public function find(string $collection, array $queries = [], ?int $limit = 25,
12571253
* @throws Exception
12581254
* @throws PDOException
12591255
*/
1260-
public function count(string $collection, array $queries = [], ?int $max = null, ?int $timeout = null): int
1256+
public function count(string $collection, array $queries = [], ?int $max = null): int
12611257
{
12621258
$name = $this->filter($collection);
12631259
$roles = Authorization::getRoles();
@@ -1287,11 +1283,8 @@ public function count(string $collection, array $queries = [], ?int $max = null,
12871283

12881284
$sql = $this->trigger(Database::EVENT_DOCUMENT_COUNT, $sql);
12891285

1290-
if ($timeout || self::$timeout) {
1291-
$sql = $this->setTimeoutForQuery($sql, $timeout ? $timeout : self::$timeout);
1292-
}
1293-
12941286
$stmt = $this->getPDO()->prepare($sql);
1287+
12951288
foreach ($queries as $query) {
12961289
$this->bindConditionValue($stmt, $query);
12971290
}
@@ -1322,7 +1315,7 @@ public function count(string $collection, array $queries = [], ?int $max = null,
13221315
* @throws Exception
13231316
* @throws PDOException
13241317
*/
1325-
public function sum(string $collection, string $attribute, array $queries = [], ?int $max = null, ?int $timeout = null): int|float
1318+
public function sum(string $collection, string $attribute, array $queries = [], ?int $max = null): int|float
13261319
{
13271320
$name = $this->filter($collection);
13281321
$roles = Authorization::getRoles();
@@ -1352,10 +1345,6 @@ public function sum(string $collection, string $attribute, array $queries = [],
13521345

13531346
$sql = $this->trigger(Database::EVENT_DOCUMENT_SUM, $sql);
13541347

1355-
if ($timeout || self::$timeout) {
1356-
$sql = $this->setTimeoutForQuery($sql, $timeout ? $timeout : self::$timeout);
1357-
}
1358-
13591348
$stmt = $this->getPDO()->prepare($sql);
13601349

13611350
foreach ($queries as $query) {
@@ -1583,35 +1572,42 @@ public function getSupportForTimeouts(): bool
15831572
}
15841573

15851574
/**
1586-
* Returns Max Execution Time
1587-
* @param string $sql
1575+
* Set max execution time
15881576
* @param int $milliseconds
1589-
* @return string
1577+
* @param string $event
1578+
* @return void
1579+
* @throws DatabaseException
15901580
*/
1591-
protected function setTimeoutForQuery(string $sql, int $milliseconds): string
1581+
public function setTimeout(int $milliseconds, string $event = Database::EVENT_ALL): void
15921582
{
15931583
if (!$this->getSupportForTimeouts()) {
1594-
return $sql;
1584+
return;
1585+
}
1586+
if ($milliseconds <= 0) {
1587+
throw new DatabaseException('Timeout must be greater than 0');
15951588
}
15961589

15971590
$seconds = $milliseconds / 1000;
1598-
return "SET STATEMENT max_statement_time = {$seconds} FOR " . $sql;
1591+
1592+
$this->before($event, 'timeout', function ($sql) use ($seconds) {
1593+
return "SET STATEMENT max_statement_time = {$seconds} FOR " . $sql;
1594+
});
15991595
}
16001596

16011597
/**
16021598
* @param PDOException $e
1603-
* @throws Timeout
1599+
* @throws TimeoutException
16041600
*/
16051601
protected function processException(PDOException $e): void
16061602
{
16071603
// Regular PDO
16081604
if ($e->getCode() === '70100' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 1969) {
1609-
throw new Timeout($e->getMessage());
1605+
throw new TimeoutException($e->getMessage());
16101606
}
16111607

16121608
// PDOProxy switches errorInfo PDOProxy.php line 64
16131609
if ($e->getCode() === 1969 && isset($e->errorInfo[0]) && $e->errorInfo[0] === '70100') {
1614-
throw new Timeout($e->getMessage());
1610+
throw new TimeoutException($e->getMessage());
16151611
}
16161612

16171613
throw $e;

src/Database/Adapter/Mongo.php

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ class Mongo extends Adapter
4242

4343
protected Client $client;
4444

45+
protected ?int $timeout = null;
46+
4547
/**
4648
* Constructor.
4749
*
@@ -812,13 +814,12 @@ public function updateAttribute(string $collection, string $id, string $type, in
812814
* @param array<string> $orderTypes
813815
* @param array<string, mixed> $cursor
814816
* @param string $cursorDirection
815-
* @param int|null $timeout
816817
*
817818
* @return array<Document>
818819
* @throws Exception
819820
* @throws Timeout
820821
*/
821-
public function find(string $collection, array $queries = [], ?int $limit = 25, ?int $offset = null, array $orderAttributes = [], array $orderTypes = [], array $cursor = [], string $cursorDirection = Database::CURSOR_AFTER, ?int $timeout = null): array
822+
public function find(string $collection, array $queries = [], ?int $limit = 25, ?int $offset = null, array $orderAttributes = [], array $orderTypes = [], array $cursor = [], string $cursorDirection = Database::CURSOR_AFTER): array
822823
{
823824
$name = $this->getNamespace() . '_' . $this->filter($collection);
824825

@@ -838,8 +839,8 @@ public function find(string $collection, array $queries = [], ?int $limit = 25,
838839
$options['skip'] = $offset;
839840
}
840841

841-
if ($timeout || self::$timeout) {
842-
$options['maxTimeMS'] = $timeout ? $timeout : self::$timeout;
842+
if ($this->timeout) {
843+
$options['maxTimeMS'] = $this->timeout;
843844
}
844845

845846
$selections = $this->getAttributeSelections($queries);
@@ -1076,7 +1077,7 @@ private function recursiveReplace(array $array, string $from, string $to, array
10761077
* @return int
10771078
* @throws Exception
10781079
*/
1079-
public function count(string $collection, array $queries = [], ?int $max = null, ?int $timeout = null): int
1080+
public function count(string $collection, array $queries = [], ?int $max = null): int
10801081
{
10811082
$name = $this->getNamespace() . '_' . $this->filter($collection);
10821083

@@ -1088,8 +1089,8 @@ public function count(string $collection, array $queries = [], ?int $max = null,
10881089
$options['limit'] = $max;
10891090
}
10901091

1091-
if ($timeout || self::$timeout) {
1092-
$options['maxTimeMS'] = $timeout ? $timeout : self::$timeout;
1092+
if ($this->timeout) {
1093+
$options['maxTimeMS'] = $this->timeout;
10931094
}
10941095

10951096
// queries
@@ -1115,15 +1116,9 @@ public function count(string $collection, array $queries = [], ?int $max = null,
11151116
* @return int|float
11161117
* @throws Exception
11171118
*/
1118-
public function sum(string $collection, string $attribute, array $queries = [], ?int $max = null, ?int $timeout = null): float|int
1119+
public function sum(string $collection, string $attribute, array $queries = [], ?int $max = null): float|int
11191120
{
11201121
$name = $this->getNamespace() . '_' . $this->filter($collection);
1121-
$collection = $this->getDatabase()->selectCollection($name);
1122-
// todo $collection is not used?
1123-
1124-
// todo add $timeout for aggregate in Mongo utopia client
1125-
1126-
$filters = [];
11271122

11281123
// queries
11291124
$filters = $this->buildFilters($queries);
@@ -1707,4 +1702,19 @@ public function getMaxIndexLength(): int
17071702
{
17081703
return 0;
17091704
}
1705+
1706+
public function setTimeout(int $milliseconds, string $event = Database::EVENT_ALL): void
1707+
{
1708+
if (!$this->getSupportForTimeouts()) {
1709+
return;
1710+
}
1711+
$this->timeout = $milliseconds;
1712+
}
1713+
1714+
public function clearTimeout(string $event): void
1715+
{
1716+
parent::clearTimeout($event);
1717+
1718+
$this->timeout = null;
1719+
}
17101720
}

0 commit comments

Comments
 (0)