Skip to content

Commit 161e8d2

Browse files
authored
[4.4] Backport tests for new timeout config options [ADR 006] (#477)
Backport of #474 and #404 [ADR 006](https://github.com/neo-technology/drivers-adr/blob/main/adrs/006-acquisition-timeouts.md) * Update and extend tests for `connection_acquisition_timeout` setting. Tests did not encompass routing drivers * Add new timeout driver config options * `sessionConnectionTimeoutMs` * `updateRoutingTableTimeoutMs` * Add new feature flag * `API_CONNECTION_ACQUISITION_TIMEOUT = Feature:API:ConnectionAcquisitionTimeout"`
1 parent 4f8b091 commit 161e8d2

16 files changed

+801
-9
lines changed

nutkit/backend/backend.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from contextlib import contextmanager
12
import inspect
23
import json
34
import os
@@ -59,6 +60,7 @@ def __init__(self, address, port):
5960
self._encoder = Encoder()
6061
self._reader = self._socket.makefile(mode="r", encoding="utf-8")
6162
self._writer = self._socket.makefile(mode="w", encoding="utf-8")
63+
self.default_timeout = DEFAULT_TIMEOUT
6264

6365
def close(self):
6466
self._reader.close()
@@ -79,7 +81,9 @@ def send(self, req, hooks=None):
7981
self._writer.write("#request end\n")
8082
self._writer.flush()
8183

82-
def receive(self, timeout=DEFAULT_TIMEOUT, hooks=None):
84+
def receive(self, timeout=None, hooks=None):
85+
if timeout is None:
86+
timeout = self.default_timeout
8387
self._socket.settimeout(timeout)
8488
response = ""
8589
in_response = False
@@ -128,6 +132,14 @@ def receive(self, timeout=DEFAULT_TIMEOUT, hooks=None):
128132
elif DEBUG_MESSAGES:
129133
print("[BACKEND]: %s" % line)
130134

131-
def send_and_receive(self, req, timeout=DEFAULT_TIMEOUT, hooks=None):
135+
def send_and_receive(self, req, timeout=None, hooks=None):
132136
self.send(req, hooks=hooks)
133137
return self.receive(timeout, hooks=hooks)
138+
139+
140+
@contextmanager
141+
def backend_timeout_adjustment(backend, timeout):
142+
old_timeout = backend.default_timeout
143+
backend.default_timeout = timeout
144+
yield
145+
backend.default_timeout = old_timeout

nutkit/frontend/driver.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ class Driver:
66
def __init__(self, backend, uri, auth_token, user_agent=None,
77
resolver_fn=None, domain_name_resolver_fn=None,
88
connection_timeout_ms=None, fetch_size=None,
9-
max_tx_retry_time_ms=None, encrypted=None,
9+
max_tx_retry_time_ms=None, session_connection_timeout_ms=None,
10+
update_routing_table_timeout_ms=None, encrypted=None,
1011
trusted_certificates=None, liveness_check_timeout_ms=None,
1112
max_connection_pool_size=None,
1213
connection_acquisition_timeout_ms=None):
@@ -18,6 +19,8 @@ def __init__(self, backend, uri, auth_token, user_agent=None,
1819
resolverRegistered=resolver_fn is not None,
1920
domainNameResolverRegistered=domain_name_resolver_fn is not None,
2021
connectionTimeoutMs=connection_timeout_ms,
22+
sessionConnectionTimeoutMs=session_connection_timeout_ms,
23+
updateRoutingTableTimeoutMs=update_routing_table_timeout_ms,
2124
fetchSize=fetch_size, maxTxRetryTimeMs=max_tx_retry_time_ms,
2225
encrypted=encrypted, trustedCertificates=trusted_certificates,
2326
liveness_check_timeout_ms=liveness_check_timeout_ms,

nutkit/protocol/feature.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,18 @@
44

55
class Feature(Enum):
66
# === FUNCTIONAL FEATURES ===
7+
# The driver offers a configuration option to limit time it spends at most,
8+
# trying to acquire a connection from the pool.
9+
# The connection acquisition timeout must account for the whole acquisition
10+
# execution time, whether a new connection is created, an idle connection
11+
# is picked up instead or we need to wait until the full pool depletes.
12+
API_CONNECTION_ACQUISITION_TIMEOUT = \
13+
"Feature:API:ConnectionAcquisitionTimeout"
714
# The driver offers a method for driver objects to report if they were
815
# configured with a or without encryption.
916
API_DRIVER_IS_ENCRYPTED = "Feature:API:Driver.IsEncrypted"
17+
# The driver supports connection liveness check.
18+
API_LIVENESS_CHECK = "Feature:API:Liveness.Check"
1019
# The driver offers a method for the result to return all records as a list
1120
# or array. This method should exhaust the result.
1221
API_RESULT_LIST = "Feature:API:Result.List"
@@ -18,8 +27,13 @@ class Feature(Enum):
1827
# This methods asserts that exactly one record in left in the result
1928
# stream, else it will raise an exception.
2029
API_RESULT_SINGLE = "Feature:API:Result.Single"
21-
# The driver supports connection liveness check.
22-
API_LIVENESS_CHECK = "Feature:API:Liveness.Check"
30+
# The driver offers a configuration option to limit time it spends at most,
31+
# trying to acquire a usable read/write connection for any session.
32+
# The connection acquisition timeout must account for the whole acquisition
33+
# execution time, whether a new connection is created, an idle connection
34+
# is picked up instead, we need to wait until the full pool depletes, or
35+
# a routing table must be fetched.
36+
API_SESSION_CONNECTION_TIMEOUT = "Feature:API:SessionConnectionTimeout"
2337
# The driver implements explicit configuration options for SSL.
2438
# - enable / disable SSL
2539
# - verify signature against system store / custom cert / not at all
@@ -31,6 +45,9 @@ class Feature(Enum):
3145
API_SSL_SCHEMES = "Feature:API:SSLSchemes"
3246
# The driver supports sending and receiving temporal data types.
3347
API_TYPE_TEMPORAL = "Feature:API:Type.Temporal"
48+
# The driver offers a configuration option to limit time it spends at most,
49+
# trying to update the routing table whenever needed.
50+
API_UPDATE_ROUTING_TABLE_TIMEOUT = "Feature:API:UpdateRoutingTableTimeout"
3451
# The driver supports single-sign-on (SSO) by providing a bearer auth token
3552
# API.
3653
AUTH_BEARER = "Feature:Auth:Bearer"

nutkit/protocol/requests.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class NewDriver:
4848
def __init__(
4949
self, uri, authToken, userAgent=None, resolverRegistered=False,
5050
domainNameResolverRegistered=False, connectionTimeoutMs=None,
51+
sessionConnectionTimeoutMs=None, updateRoutingTableTimeoutMs=None,
5152
fetchSize=None, maxTxRetryTimeMs=None, encrypted=None,
5253
trustedCertificates=None, liveness_check_timeout_ms=None,
5354
max_connection_pool_size=None,
@@ -62,6 +63,8 @@ def __init__(
6263
self.resolverRegistered = resolverRegistered
6364
self.domainNameResolverRegistered = domainNameResolverRegistered
6465
self.connectionTimeoutMs = connectionTimeoutMs
66+
self.sessionConnectionTimeoutMs = sessionConnectionTimeoutMs
67+
self.updateRoutingTableTimeoutMs = updateRoutingTableTimeoutMs
6568
self.fetchSize = fetchSize
6669
self.maxTxRetryTimeMs = maxTxRetryTimeMs
6770
self.livenessCheckTimeoutMs = liveness_check_timeout_ms

tests/stub/driver_parameters/__init__.py

Whitespace-only changes.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
!: BOLT 4.4
2+
!: AUTO RESET
3+
!: ALLOW RESTART
4+
5+
A: HELLO {"{}": "*"}
6+
*: RESET
7+
{+
8+
C: ROUTE "*" "*" "*"
9+
S: SUCCESS { "rt": { "ttl": 1000, "servers": [{"addresses": ["#HOST#:9000"], "role":"ROUTE"}, {"addresses": ["#HOST#:9010"], "role":"READ"}, {"addresses": ["#HOST#:9010"], "role":"WRITE"}]}}
10+
*: RESET
11+
+}
12+
?: GOODBYE
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
!: BOLT 4.4
2+
!: AUTO RESET
3+
!: ALLOW RESTART
4+
5+
C: HELLO {"{}": "*"}
6+
S: <SLEEP> 2
7+
<NOOP>
8+
<SLEEP> 2
9+
SUCCESS {"server": "Neo4j/5.0.0", "connection_id": "bolt-123456789"}
10+
*: RESET
11+
{+
12+
C: ROUTE "*" "*" "*"
13+
S: SUCCESS { "rt": { "ttl": 1000, "servers": [{"addresses": ["#HOST#:9000"], "role":"ROUTE"}, {"addresses": ["#HOST#:9010"], "role":"READ"}, {"addresses": ["#HOST#:9010"], "role":"WRITE"}]}}
14+
*: RESET
15+
+}
16+
?: GOODBYE
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
!: BOLT 4.4
2+
!: AUTO RESET
3+
!: ALLOW RESTART
4+
5+
A: HELLO {"{}": "*"}
6+
*: RESET
7+
{+
8+
C: ROUTE "*" "*" "*"
9+
S: <SLEEP> 2
10+
<NOOP>
11+
<SLEEP> 2
12+
SUCCESS { "rt": { "ttl": 1000, "servers": [{"addresses": ["#HOST#:9000"], "role":"ROUTE"}, {"addresses": ["#HOST#:9010"], "role":"READ"}, {"addresses": ["#HOST#:9010"], "role":"WRITE"}]}}
13+
*: RESET
14+
+}
15+
?: GOODBYE
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
!: BOLT 4.4
2+
!: ALLOW CONCURRENT
3+
4+
A: HELLO {"{}": "*"}
5+
*: RESET
6+
C: RUN "*" "*" "*"
7+
S: SUCCESS {"fields": ["n"]}
8+
C: PULL {"n": "*"}
9+
S: RECORD [1]
10+
SUCCESS {"type": "r"}
11+
*: RESET
12+
?: GOODBYE
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
!: BOLT 4.4
2+
!: ALLOW CONCURRENT
3+
4+
C: HELLO {"{}": "*"}
5+
S: <SLEEP> 2
6+
<NOOP>
7+
<SLEEP> 2
8+
SUCCESS {"server": "Neo4j/5.0.0", "connection_id": "bolt-123456789"}
9+
*: RESET
10+
C: RUN "*" "*" "*"
11+
S: SUCCESS {"fields": ["n"]}
12+
C: PULL {"n": "*"}
13+
S: RECORD [1]
14+
SUCCESS {"type": "r"}
15+
*: RESET
16+
?: GOODBYE

0 commit comments

Comments
 (0)