Skip to content

Commit fc43f30

Browse files
authored
Unify connection_acquisition_timeout behavior (#1215)
The config option `connection_acquisition_timeout` now spans anything that's required to acquire a working connection from the pool. This includes * Potentially fetching a routing table This entails acquiring a connection in itself. * Bolt, TLS, TCP handshaking * Authentication * Any other required IO (e.g., DNS lookups) * Waiting for room in the pool * possibly more Previously, the timeout wold be restarted for auxiliary connection acquisitions like those for fetching a routing table.
1 parent adcd4f7 commit fc43f30

File tree

16 files changed

+462
-32
lines changed

16 files changed

+462
-32
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,15 @@ See also https://github.com/neo4j/neo4j-python-driver/wiki for a full changelog.
3939
(instead of internal `neo4j._exceptions.BoltHandshakeError`).
4040
`UnsupportedServerProduct` is now a subclass of `ServiceUnavailable` (instead of `Exception` directly).
4141
- `connection_acquisition_timeout` configuration option
42-
- `ValueError` on invalid values (instead of `ClientError`)
42+
- Raise `ValueError` on invalid values (instead of `ClientError`).
4343
- Consistently restrict the value to be strictly positive
4444
- New `ConnectionAcquisitionTimeoutError` (subclass of `DriverError`) instead of `ClientError`
4545
(subclass of `Neo4jError`) the timeout is exceeded.
4646
- This improves the differentiation between `DriverError` for client-side errors and `Neo4jError` for server-side
4747
errors.
48+
- The option now spans *anything* required to acquire a connection.
49+
This includes potential fetching of routing tables which in itself requires acquiring a connection.
50+
Previously, the timeout would be restarted for such auxiliary connection acquisitions.
4851
- `TypeError` instead of `ValueError` when passing a `Query` object to `Transaction.run`.
4952
- `TransactionError` (subclass of `DriverError`) instead of `ClientError` (subclass of `Neo4jError`) when calling
5053
`session.run()` while an explicit transaction is active on that session.

docs/source/api.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,11 @@ it should be chosen larger than :ref:`connection-timeout-ref`.
430430
:Type: ``float``
431431
:Default: ``60.0``
432432

433+
.. versionadded:: 6.0
434+
The setting now entails *anything* required to acquire a connection.
435+
This includes potential fetching of routing tables which in itself requires acquiring a connection.
436+
Previously, the timeout would be restarted for such auxiliary connection acquisitions.
437+
433438

434439
.. _connection-timeout-ref:
435440

src/neo4j/_async/io/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"AsyncBoltPool",
2929
"AsyncNeo4jPool",
3030
"ConnectionErrorHandler",
31+
"acquisition_timeout_to_deadline",
3132
]
3233

3334

@@ -40,6 +41,7 @@
4041
from ._bolt import AsyncBolt
4142
from ._common import ConnectionErrorHandler
4243
from ._pool import (
44+
acquisition_timeout_to_deadline,
4345
AcquisitionAuth,
4446
AcquisitionDatabase,
4547
AsyncBoltPool,

src/neo4j/_async/io/_pool.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,8 @@ async def connection_creator():
260260
with self.lock:
261261
self.connections_reservations[address] -= 1
262262

263+
if deadline.expired():
264+
return None
263265
max_pool_size = self.pool_config.max_connection_pool_size
264266
infinite_pool_size = max_pool_size < 0 or max_pool_size == float("inf")
265267
with self.lock:
@@ -659,22 +661,21 @@ async def acquire(
659661
timeout,
660662
database,
661663
bookmarks,
662-
auth: AcquisitionAuth,
664+
auth: AcquisitionAuth | None,
663665
liveness_check_timeout,
664666
unprepared=False,
665667
database_callback=None,
666668
):
667669
# The access_mode and database is not needed for a direct connection,
668670
# it's just there for consistency.
669671
access_mode = check_access_mode(access_mode)
670-
_check_acquisition_timeout(timeout)
672+
deadline = acquisition_timeout_to_deadline(timeout)
671673
log.debug(
672674
"[#0000] _: <POOL> acquire direct connection, "
673675
"access_mode=%r, database=%r",
674676
access_mode,
675677
database,
676678
)
677-
deadline = Deadline.from_timeout_or_deadline(timeout)
678679
return await self._acquire(
679680
self.address, auth, deadline, liveness_check_timeout, unprepared
680681
)
@@ -969,7 +970,9 @@ async def update_routing_table(
969970
970971
:raise neo4j.exceptions.ServiceUnavailable:
971972
"""
972-
_check_acquisition_timeout(acquisition_timeout)
973+
acquisition_timeout = acquisition_timeout_to_deadline(
974+
acquisition_timeout
975+
)
973976
async with self.refresh_lock:
974977
routing_table = await self.get_routing_table(database)
975978
if routing_table is not None:
@@ -1147,7 +1150,7 @@ async def acquire(
11471150
database_callback=None,
11481151
):
11491152
access_mode = check_access_mode(access_mode)
1150-
_check_acquisition_timeout(timeout)
1153+
timeout = acquisition_timeout_to_deadline(timeout)
11511154

11521155
target_database = database.name
11531156

@@ -1242,6 +1245,13 @@ async def on_write_failure(self, address, database):
12421245
log.debug("[#0000] _: <POOL> table=%r", self.routing_tables)
12431246

12441247

1248+
def acquisition_timeout_to_deadline(timeout: object) -> Deadline:
1249+
if isinstance(timeout, Deadline):
1250+
return timeout
1251+
_check_acquisition_timeout(timeout)
1252+
return Deadline(timeout)
1253+
1254+
12451255
def _check_acquisition_timeout(timeout: object) -> None:
12461256
if not isinstance(timeout, (int, float)):
12471257
raise TypeError(

src/neo4j/_async/work/workspace.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,14 @@
3030
)
3131
from .._debug import AsyncNonConcurrentMethodChecker
3232
from ..io import (
33+
acquisition_timeout_to_deadline,
3334
AcquisitionAuth,
3435
AcquisitionDatabase,
3536
)
3637

3738

3839
if t.TYPE_CHECKING:
40+
from ..._deadline import Deadline
3941
from ...api import _TAuth
4042
from ...auth_management import (
4143
AsyncAuthManager,
@@ -159,13 +161,19 @@ async def _connect(self, access_mode, auth=None, **acquire_kwargs) -> None:
159161
await self._connection.fetch_all()
160162
await self._disconnect()
161163

164+
acquisition_deadline = acquisition_timeout_to_deadline(
165+
acquisition_timeout
166+
)
167+
162168
ssr_enabled = self._pool.ssr_enabled
163169
target_db = await self._get_routing_target_database(
164-
acquire_auth, ssr_enabled=ssr_enabled
170+
acquire_auth,
171+
ssr_enabled=ssr_enabled,
172+
acquisition_deadline=acquisition_deadline,
165173
)
166174
acquire_kwargs_ = {
167175
"access_mode": access_mode,
168-
"timeout": acquisition_timeout,
176+
"timeout": acquisition_deadline,
169177
"database": target_db,
170178
"bookmarks": await self._get_bookmarks(),
171179
"auth": acquire_auth,
@@ -188,7 +196,9 @@ async def _connect(self, access_mode, auth=None, **acquire_kwargs) -> None:
188196
)
189197
await self._disconnect()
190198
target_db = await self._get_routing_target_database(
191-
acquire_auth, ssr_enabled=False
199+
acquire_auth,
200+
ssr_enabled=False,
201+
acquisition_deadline=acquisition_deadline,
192202
)
193203
acquire_kwargs_["database"] = target_db
194204
self._connection = await self._pool.acquire(**acquire_kwargs_)
@@ -198,6 +208,7 @@ async def _get_routing_target_database(
198208
self,
199209
acquire_auth: AcquisitionAuth,
200210
ssr_enabled: bool,
211+
acquisition_deadline: Deadline,
201212
) -> AcquisitionDatabase:
202213
if (
203214
self._pinned_database
@@ -232,14 +243,13 @@ async def _get_routing_target_database(
232243
)
233244
return AcquisitionDatabase(cached_db, guessed=True)
234245

235-
acquisition_timeout = self._config.connection_acquisition_timeout
236246
log.debug("[#0000] _: <WORKSPACE> resolve home database")
237247
await self._pool.update_routing_table(
238248
database=self._config.database,
239249
imp_user=self._config.impersonated_user,
240250
bookmarks=await self._get_bookmarks(),
241251
auth=acquire_auth,
242-
acquisition_timeout=acquisition_timeout,
252+
acquisition_timeout=acquisition_deadline,
243253
database_callback=self._make_db_resolution_callback(),
244254
)
245255
return AcquisitionDatabase(self._config.database)

src/neo4j/_sync/io/__init__.py

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/neo4j/_sync/io/_pool.py

Lines changed: 15 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/neo4j/_sync/work/workspace.py

Lines changed: 15 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

testkitbackend/test_config.json

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,7 @@
1515
"'neo4j.datatypes.test_temporal_types.TestDataTypes.test_date_time_cypher_created_tz_id'":
1616
"test_subtest_skips.tz_id",
1717
"stub\\.routing\\.test_routing_v[0-9x]+\\.RoutingV[0-9x]+\\.test_should_drop_connections_failing_liveness_check":
18-
"Liveness check error handling is not (yet) unified: https://github.com/neo-technology/drivers-adr/pull/83",
19-
"'stub.homedb.test_homedb.TestHomeDbMixedCluster.test_connection_acquisition_timeout_during_fallback'":
20-
"TODO: 6.0 - pending unification: connection acquisition timeout should count towards the total time spent waiting for a connection (including routing, home db resolution, ...)",
21-
"'stub.driver_parameters.test_connection_acquisition_timeout_ms.TestConnectionAcquisitionTimeoutMs.test_does_encompass_router_route_response'":
22-
"TODO: 6.0 - pending unification: connection acquisition timeout should count towards the total time spent waiting for a connection (including routing, home db resolution, ...)",
23-
"'stub.driver_parameters.test_connection_acquisition_timeout_ms.TestConnectionAcquisitionTimeoutMs.test_router_handshake_shares_acquisition_timeout'":
24-
"TODO: 6.0 - pending unification: connection acquisition timeout should count towards the total time spent waiting for a connection (including routing, home db resolution, ...)"
18+
"Liveness check error handling is not (yet) unified: https://github.com/neo-technology/drivers-adr/pull/83"
2519
},
2620
"features": {
2721
"Feature:API:BookmarkManager": true,

0 commit comments

Comments
 (0)