Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit adcd4f7

Browse files
robsdedudeMaxAake
andauthored
Make DNS error retryable (#1211)
Configuring the driver with a URL that cannot be DNS resolved will raise a (retryable) `ServiceUnavailable` error instead of a `ValueError`. Co-authored-by: MaxAake <61233757+MaxAake@users.noreply.github.com>
1 parent d6668df commit adcd4f7

File tree

10 files changed

+227
-2
lines changed

10 files changed

+227
-2
lines changed

‎CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ See also https://github.com/neo4j/neo4j-python-driver/wiki for a full changelog.
165165
- `neo4j.graph.Node`, `neo4j.graph.Relationship`, `neo4j.graph.Path`
166166
- `neo4j.time.Date`, `neo4j.time.Time`, `neo4j.time.DateTime`
167167
- `neo4j.spatial.Point` (and subclasses)
168+
- Configuring the driver with a URL that cannot be DNS resolved will raise a (retryable) `ServiceUnavailable` error
169+
instead of a `ValueError`.
168170
- Separate out log entries that are session-related (including transaction retries)
169171
form sub-logger `neo4j.pool` to a new sub-logger `neo4j.session`.
170172
- Notifications:

‎src/neo4j/_async_compat/network/_util.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
Address,
2323
ResolvedAddress,
2424
)
25+
from ...exceptions import ServiceUnavailable
2526
from ..util import AsyncUtil
2627

2728

@@ -40,6 +41,14 @@ def _resolved_addresses_from_info(info, host_name):
4041
yield ResolvedAddress(addr, host_name=host_name)
4142

4243

44+
_RETRYABLE_DNS_ERRNOS = {
45+
socket.EAI_ADDRFAMILY,
46+
socket.EAI_AGAIN,
47+
socket.EAI_MEMORY,
48+
socket.EAI_NODATA,
49+
}
50+
51+
4352
class AsyncNetworkUtil:
4453
@staticmethod
4554
async def get_address_info(
@@ -69,7 +78,16 @@ async def _dns_resolver(address, family=0):
6978
type=socket.SOCK_STREAM,
7079
)
7180
except OSError as e:
72-
raise ValueError(f"Cannot resolve address {address}") from e
81+
if e.errno in _RETRYABLE_DNS_ERRNOS or (
82+
e.errno == socket.EAI_NONAME
83+
and (address.host is not None or address.port is not None)
84+
):
85+
raise ServiceUnavailable(
86+
f"Failed to DNS resolve address {address}: {e}"
87+
) from e
88+
raise ValueError(
89+
f"Failed to DNS resolve address {address}: {e}"
90+
) from e
7391
return list(_resolved_addresses_from_info(info, address._host_name))
7492

7593
@staticmethod
@@ -151,7 +169,16 @@ def _dns_resolver(address, family=0):
151169
type=socket.SOCK_STREAM,
152170
)
153171
except OSError as e:
154-
raise ValueError(f"Cannot resolve address {address}") from e
172+
if e.errno in _RETRYABLE_DNS_ERRNOS or (
173+
e.errno == socket.EAI_NONAME
174+
and (address.host is not None or address.port is not None)
175+
):
176+
raise ServiceUnavailable(
177+
f"Failed to DNS resolve address {address}: {e}"
178+
) from e
179+
raise ValueError(
180+
f"Failed to DNS resolve address {address}: {e}"
181+
) from e
155182
return _resolved_addresses_from_info(info, address._host_name)
156183

157184
@staticmethod
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Copyright (c) "Neo4j"
2+
# Neo4j Sweden AB [https://neo4j.com]
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# https://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Copyright (c) "Neo4j"
2+
# Neo4j Sweden AB [https://neo4j.com]
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# https://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Copyright (c) "Neo4j"
2+
# Neo4j Sweden AB [https://neo4j.com]
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# https://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
17+
from __future__ import annotations
18+
19+
import socket
20+
21+
import pytest
22+
23+
from neo4j._addressing import (
24+
ResolvedAddress,
25+
ResolvedIPv4Address,
26+
ResolvedIPv6Address,
27+
)
28+
from neo4j._async_compat.network import AsyncNetworkUtil
29+
from neo4j.addressing import Address
30+
from neo4j.exceptions import ServiceUnavailable
31+
32+
from ....._async_compat import mark_async_test
33+
34+
35+
@mark_async_test
36+
async def test_resolve_address():
37+
resolved = [
38+
addr
39+
async for addr in AsyncNetworkUtil.resolve_address(
40+
Address(("localhost", 1234)),
41+
)
42+
]
43+
assert all(isinstance(addr, ResolvedAddress) for addr in resolved)
44+
for addr in resolved:
45+
if isinstance(addr, ResolvedIPv4Address):
46+
assert len(addr) == 2
47+
assert addr[0].startswith("127.0.0.")
48+
assert addr[1] == 1234
49+
elif isinstance(addr, ResolvedIPv6Address):
50+
assert len(addr) == 4
51+
assert addr[:2] == ("::1", 1234)
52+
53+
54+
@mark_async_test
55+
async def test_resolve_invalid_address():
56+
with pytest.raises(ServiceUnavailable) as exc:
57+
await anext(
58+
AsyncNetworkUtil.resolve_address(
59+
Address(("example.invalid", 1234)),
60+
)
61+
)
62+
cause = exc.value.__cause__
63+
assert isinstance(cause, socket.gaierror)
64+
assert cause.errno, socket.EAI_NONAME

‎tests/integration/sync/async_compat/__init__.py

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

‎tests/integration/sync/async_compat/network/__init__.py

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

‎tests/integration/sync/async_compat/network/test_util.py

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

‎tests/unit/async_/test_addressing.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
)
2525
from neo4j._async_compat.network import AsyncNetworkUtil
2626
from neo4j._async_compat.util import AsyncUtil
27+
from neo4j.exceptions import ServiceUnavailable
2728

2829
from ..._async_compat import mark_async_test
2930

@@ -53,14 +54,19 @@ async def test_address_resolve_with_custom_resolver_none() -> None:
5354
@pytest.mark.parametrize(
5455
("test_input", "expected"),
5556
[
57+
(Address(("example.invalid", "7687")), ServiceUnavailable),
58+
(Address(("example.invalid", 7687)), ServiceUnavailable),
5659
(Address(("127.0.0.1", "abcd")), ValueError),
5760
(Address((None, None)), ValueError),
61+
(Address((1234, "7687")), TypeError),
5862
],
5963
)
6064
@mark_async_test
6165
async def test_address_resolve_with_unresolvable_address(
6266
test_input, expected
6367
) -> None:
68+
# import contextlib
69+
# with contextlib.suppress(Exception):
6470
with pytest.raises(expected):
6571
await AsyncUtil.list(
6672
AsyncNetworkUtil.resolve_address(test_input, resolver=None)

‎tests/unit/sync/test_addressing.py

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

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /