Skip to content

Commit e15a0b8

Browse files
committed
Renaming to OverloadControlDatabase
1 parent 4006973 commit e15a0b8

File tree

5 files changed

+98
-88
lines changed

5 files changed

+98
-88
lines changed

arango/database.py

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"StandardDatabase",
33
"AsyncDatabase",
44
"BatchDatabase",
5-
"QueueBoundedDatabase",
5+
"OverloadControlDatabase",
66
"TransactionDatabase",
77
]
88

@@ -81,7 +81,7 @@
8181
AsyncApiExecutor,
8282
BatchApiExecutor,
8383
DefaultApiExecutor,
84-
QueueBoundedApiExecutor,
84+
OverloadControlApiExecutor,
8585
TransactionApiExecutor,
8686
)
8787
from arango.formatter import (
@@ -2521,18 +2521,18 @@ def begin_transaction(
25212521
max_size=max_size,
25222522
)
25232523

2524-
def begin_queue_bounded_execution(
2525-
self, max_queue_time_seconds: float = 0.0
2526-
) -> "QueueBoundedDatabase":
2527-
"""Begin queue bounded execution.
2524+
def begin_controlled_execution(
2525+
self, max_queue_time_seconds: Optional[float] = None
2526+
) -> "OverloadControlDatabase":
2527+
"""Begin a controlled connection, with options to handle server-side overload.
25282528
25292529
:param max_queue_time_seconds: Maximum time in seconds a request can be queued
2530-
on the server-side. If set to 0, the server ignores this setting.
2531-
:type max_queue_time_seconds: float
2530+
on the server-side. If set to 0 or None, the server ignores this setting.
2531+
:type max_queue_time_seconds: Optional[float]
25322532
:return: Database API wrapper object specifically for queue bounded execution.
2533-
:rtype: arango.database.QueueBoundedDatabase
2533+
:rtype: arango.database.OverloadControlDatabase
25342534
"""
2535-
return QueueBoundedDatabase(self._conn, max_queue_time_seconds)
2535+
return OverloadControlDatabase(self._conn, max_queue_time_seconds)
25362536

25372537

25382538
class AsyncDatabase(Database):
@@ -2706,27 +2706,29 @@ def abort_transaction(self) -> bool:
27062706
return self._executor.abort()
27072707

27082708

2709-
class QueueBoundedDatabase(Database):
2710-
"""Database API wrapper tailored specifically to hande time-bound requests.
2709+
class OverloadControlDatabase(Database):
2710+
"""Database API wrapper tailored to gracefully handle server overload scenarios.
27112711
2712-
See :func:`arango.database.StandardDatabase.begin_queue_bounded_execution`.
2712+
See :func:`arango.database.StandardDatabase.begin_controlled_execution`.
27132713
27142714
:param connection: HTTP connection.
27152715
:param max_queue_time_seconds: Maximum server-side queuing time in seconds.
27162716
If the server-side queuing time exceeds the client's specified limit,
27172717
the request will be rejected.
2718-
:type max_queue_time_seconds: float
2718+
:type max_queue_time_seconds: Optional[float]
27192719
"""
27202720

2721-
def __init__(self, connection: Connection, max_queue_time_seconds: float) -> None:
2722-
self._executor: QueueBoundedApiExecutor
2721+
def __init__(
2722+
self, connection: Connection, max_queue_time_seconds: Optional[float] = None
2723+
) -> None:
2724+
self._executor: OverloadControlApiExecutor
27232725
super().__init__(
27242726
connection=connection,
2725-
executor=QueueBoundedApiExecutor(connection, max_queue_time_seconds),
2727+
executor=OverloadControlApiExecutor(connection, max_queue_time_seconds),
27262728
)
27272729

27282730
def __repr__(self) -> str: # pragma: no cover
2729-
return f"<QueueBoundedDatabase {self.name}>"
2731+
return f"<OverloadControlDatabase {self.name}>"
27302732

27312733
@property
27322734
def last_queue_time(self) -> float:
@@ -2738,19 +2740,19 @@ def last_queue_time(self) -> float:
27382740
return self._executor.queue_time_seconds
27392741

27402742
@property
2741-
def max_queue_time(self) -> float:
2743+
def max_queue_time(self) -> Optional[float]:
27422744
"""Return the maximum server-side queuing time in seconds.
27432745
27442746
:return: Maximum server-side queuing time in seconds.
2745-
:rtype: float
2747+
:rtype: Optional[float]
27462748
"""
27472749
return self._executor.max_queue_time_seconds
27482750

2749-
def adjust_max_queue_time(self, max_queue_time_seconds: float) -> None:
2751+
def adjust_max_queue_time(self, max_queue_time_seconds: Optional[float]) -> None:
27502752
"""Adjust the maximum server-side queuing time in seconds.
27512753
27522754
:param max_queue_time_seconds: New maximum server-side queuing time
2753-
in seconds.
2754-
:type max_queue_time_seconds: float
2755+
in seconds. Setting it to None disables the limit.
2756+
:type max_queue_time_seconds: Optional[float]
27552757
"""
27562758
self._executor.max_queue_time_seconds = max_queue_time_seconds

arango/exceptions.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -232,13 +232,13 @@ class BatchExecuteError(ArangoServerError):
232232
"""Failed to execute batch API request."""
233233

234234

235-
######################################
236-
# Queue Bounded Execution Exceptions #
237-
######################################
235+
#########################################
236+
# Overload Control Execution Exceptions #
237+
#########################################
238238

239239

240-
class QueueBoundedExecutorError(ArangoServerError):
241-
"""Failed to execute queue-bounded API request."""
240+
class OverloadControlExecutorError(ArangoServerError):
241+
"""Failed to execute overload controlled API request."""
242242

243243

244244
#########################

arango/executor.py

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"AsyncApiExecutor",
55
"BatchApiExecutor",
66
"TransactionApiExecutor",
7-
"QueueBoundedApiExecutor",
7+
"OverloadControlApiExecutor",
88
]
99

1010
from collections import OrderedDict
@@ -17,7 +17,7 @@
1717
AsyncExecuteError,
1818
BatchExecuteError,
1919
BatchStateError,
20-
QueueBoundedExecutorError,
20+
OverloadControlExecutorError,
2121
TransactionAbortError,
2222
TransactionCommitError,
2323
TransactionInitError,
@@ -34,7 +34,7 @@
3434
"AsyncApiExecutor",
3535
"BatchApiExecutor",
3636
"TransactionApiExecutor",
37-
"QueueBoundedApiExecutor",
37+
"OverloadControlApiExecutor",
3838
]
3939

4040
T = TypeVar("T")
@@ -433,7 +433,7 @@ def abort(self) -> bool:
433433
raise TransactionAbortError(resp, request)
434434

435435

436-
class QueueBoundedApiExecutor:
436+
class OverloadControlApiExecutor:
437437
"""Allows setting the maximum acceptable server-side queuing time
438438
for client requests.
439439
@@ -444,39 +444,43 @@ class QueueBoundedApiExecutor:
444444
:type max_queue_time_seconds: float
445445
"""
446446

447-
def __init__(self, connection: Connection, max_queue_time_seconds: float) -> None:
447+
def __init__(
448+
self, connection: Connection, max_queue_time_seconds: Optional[float] = None
449+
) -> None:
448450
self._conn = connection
449451
self._max_queue_time_seconds = max_queue_time_seconds
450452
self._queue_time_seconds = 0.0
451453

452454
@property
453455
def context(self) -> str: # pragma: no cover
454-
return "queue-bounded"
456+
return "overload-control"
455457

456458
@property
457459
def queue_time_seconds(self) -> float:
458460
"""Return the most recent request queuing/de-queuing time.
461+
Defaults to 0 if no request has been sent.
459462
460463
:return: Server-side queuing time in seconds.
461464
:rtype: float
462465
"""
463466
return self._queue_time_seconds
464467

465468
@property
466-
def max_queue_time_seconds(self) -> float:
469+
def max_queue_time_seconds(self) -> Optional[float]:
467470
"""Return the maximum server-side queuing time.
468471
469472
:return: Maximum server-side queuing time in seconds.
470-
:rtype: float
473+
:rtype: Optional[float]
471474
"""
472475
return self._max_queue_time_seconds
473476

474477
@max_queue_time_seconds.setter
475-
def max_queue_time_seconds(self, value: float) -> None:
478+
def max_queue_time_seconds(self, value: Optional[float]) -> None:
476479
"""Set the maximum server-side queuing time.
480+
Setting it to None disables the feature.
477481
478482
:param value: Maximum server-side queuing time in seconds.
479-
:type value: float
483+
:type value: Optional[float]
480484
"""
481485
self._max_queue_time_seconds = value
482486

@@ -493,13 +497,14 @@ def execute(
493497
:type response_handler: callable
494498
:return: API execution result.
495499
"""
496-
request.headers["x-arango-queue-time-seconds"] = str(
497-
self._max_queue_time_seconds
498-
)
500+
if self._max_queue_time_seconds is not None:
501+
request.headers["x-arango-queue-time-seconds"] = str(
502+
self._max_queue_time_seconds
503+
)
499504
resp = self._conn.send_request(request)
500505

501506
if not resp.is_success:
502-
raise QueueBoundedExecutorError(resp, request)
507+
raise OverloadControlExecutorError(resp, request)
503508

504509
if "X-Arango-Queue-Time-Seconds" in resp.headers:
505510
self._queue_time_seconds = float(

tests/test_overload.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import warnings
2+
3+
from arango import errno
4+
from arango.exceptions import OverloadControlExecutorError
5+
6+
7+
def flood_with_requests(controlled_db, async_db):
8+
"""
9+
Flood the database with requests.
10+
It is impossible to predict what the last recorded queue time will be.
11+
We can only try and make it as large as possible. However, if the system
12+
is fast enough, it may still be 0.
13+
"""
14+
controlled_db.aql.execute("RETURN SLEEP(0.5)", count=True)
15+
for _ in range(3):
16+
for _ in range(500):
17+
async_db.aql.execute("RETURN SLEEP(0.5)", count=True)
18+
controlled_db.aql.execute("RETURN SLEEP(0.5)", count=True)
19+
if controlled_db.last_queue_time >= 0:
20+
break
21+
22+
23+
def test_overload_control(db):
24+
controlled_db = db.begin_controlled_execution(100)
25+
assert controlled_db.max_queue_time == 100
26+
27+
async_db = db.begin_async_execution(return_result=True)
28+
29+
flood_with_requests(controlled_db, async_db)
30+
assert controlled_db.last_queue_time >= 0
31+
32+
# We can only emit a warning here. The test will still pass.
33+
if controlled_db.last_queue_time == 0:
34+
warnings.warn(
35+
f"last_queue_time of {controlled_db} is 0, test may be unreliable"
36+
)
37+
38+
controlled_db.adjust_max_queue_time(0.0001)
39+
try:
40+
flood_with_requests(controlled_db, async_db)
41+
assert controlled_db.last_queue_time >= 0
42+
except OverloadControlExecutorError as e:
43+
assert e.http_code == errno.HTTP_PRECONDITION_FAILED
44+
assert e.error_code == errno.QUEUE_TIME_REQUIREMENT_VIOLATED
45+
else:
46+
warnings.warn(
47+
f"last_queue_time of {controlled_db} is {controlled_db.last_queue_time},"
48+
f"test may be unreliable"
49+
)

tests/test_queue_bounded.py

Lines changed: 0 additions & 46 deletions
This file was deleted.

0 commit comments

Comments
 (0)