Skip to content

Commit 58481ce

Browse files
committed
Fix driver stuck RecursionError in COMMIT SUCCESS
The driver could get stuck when the result summary was so deeply nested that it caused an `RecursionError`. It would also get stuck on certain protocol violations in the same place (e.g., unknown PackStream tag). However, the latter is much more unlikely to occur. This patch will make the driver ditch the connection when it's not able to properly read a message for unexpected reasons.
1 parent 185f24f commit 58481ce

File tree

6 files changed

+52
-8
lines changed

6 files changed

+52
-8
lines changed

src/neo4j/_async/io/_bolt.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from ..._exceptions import (
3636
BoltError,
3737
BoltHandshakeError,
38+
SocketDeadlineExceededError,
3839
)
3940
from ..._io import BoltProtocolVersion
4041
from ..._meta import USER_AGENT
@@ -889,6 +890,15 @@ async def _set_defunct_write(self, error=None, silent=False):
889890
async def _set_defunct(self, message, error=None, silent=False):
890891
direct_driver = getattr(self.pool, "is_direct_pool", False)
891892
user_cancelled = isinstance(error, asyncio.CancelledError)
893+
connection_failed = isinstance(
894+
error,
895+
(
896+
ServiceUnavailable,
897+
SessionExpired,
898+
OSError,
899+
SocketDeadlineExceededError,
900+
),
901+
)
892902

893903
if not (user_cancelled or self._closing):
894904
log_call = log.error
@@ -915,6 +925,12 @@ async def _set_defunct(self, message, error=None, silent=False):
915925
if user_cancelled:
916926
self.kill()
917927
raise error # cancellation error should not be re-written
928+
if not connection_failed:
929+
# Something else but the connection failed
930+
# => we're not sure which state we're in
931+
# => ditch the connection and raise the error for user-awareness
932+
await self.close()
933+
raise error
918934
if not self._closing:
919935
# If we fail while closing the connection, there is no need to
920936
# remove the connection from the pool, nor to try to close the

src/neo4j/_async/io/_common.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,15 @@ async def pop(self, hydration_hooks):
8181
self._unpacker.unpack(hydration_hooks) for _ in range(size)
8282
]
8383
return tag, fields
84+
except Exception as error:
85+
log.debug(
86+
"[#%04X] _: Failed to unpack response: %r",
87+
self._local_port,
88+
error,
89+
)
90+
self._broken = True
91+
await AsyncUtil.callback(self.on_error, error)
92+
raise
8493
finally:
8594
# Reset for new message
8695
self._unpacker.reset()

src/neo4j/_async/work/result.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,6 @@
2626
warn_explicit,
2727
)
2828

29-
30-
if t.TYPE_CHECKING:
31-
import typing_extensions as te
32-
3329
from ..._api import (
3430
NotificationCategory,
3531
NotificationMinimumSeverity,
@@ -65,6 +61,7 @@
6561

6662
if t.TYPE_CHECKING:
6763
import pandas # type: ignore[import]
64+
import typing_extensions as te
6865

6966
from ..._addressing import Address
7067
from ...graph import Graph

src/neo4j/_sync/io/_bolt.py

Lines changed: 16 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/_common.py

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

src/neo4j/_sync/work/result.py

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

0 commit comments

Comments
 (0)