Skip to content

Commit 8f95a3e

Browse files
committed
Do not clear the redis-reader's state when we disconnect so that it can finish reading the final message
1 parent 4eaf960 commit 8f95a3e

File tree

2 files changed

+19
-12
lines changed

2 files changed

+19
-12
lines changed

redis/asyncio/connection.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -350,13 +350,14 @@ async def _readline(self) -> bytes:
350350
class HiredisParser(BaseParser):
351351
"""Parser class for connections using Hiredis"""
352352

353-
__slots__ = BaseParser.__slots__ + ("_reader",)
353+
__slots__ = BaseParser.__slots__ + ("_reader", "_connected")
354354

355355
def __init__(self, socket_read_size: int):
356356
if not HIREDIS_AVAILABLE:
357357
raise RedisError("Hiredis is not available.")
358358
super().__init__(socket_read_size=socket_read_size)
359359
self._reader: Optional[hiredis.Reader] = None
360+
self._connected: bool = False
360361

361362
def on_connect(self, connection: "Connection"):
362363
self._stream = connection._reader
@@ -369,13 +370,13 @@ def on_connect(self, connection: "Connection"):
369370
kwargs["errors"] = connection.encoder.encoding_errors
370371

371372
self._reader = hiredis.Reader(**kwargs)
373+
self._connected = False
372374

373375
def on_disconnect(self):
374-
self._stream = None
375-
self._reader = None
376+
self._connected = False
376377

377378
async def can_read_destructive(self):
378-
if not self._stream or not self._reader:
379+
if not self._connected:
379380
raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR)
380381
if self._reader.gets():
381382
return True
@@ -397,8 +398,10 @@ async def read_from_socket(self):
397398
async def read_response(
398399
self, disable_decoding: bool = False
399400
) -> Union[EncodableT, List[EncodableT]]:
400-
if not self._stream or not self._reader:
401-
self.on_disconnect()
401+
# If `on_disconnect()` has been called, prohibit any more reads
402+
# even if they could happen because data might be present.
403+
# We still allow reads in progress to finish
404+
if not self._connected:
402405
raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR) from None
403406

404407
response = self._reader.gets()

tests/test_asyncio/test_connection.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -150,15 +150,18 @@ async def test_connection_parse_response_resume(r: redis.Redis):
150150
assert i > 0
151151

152152

153-
@pytest.mark.xfail
154153
@pytest.mark.onlynoncluster
155154
async def test_connection_hiredis_disconect_race():
156155
"""
157156
This test reproduces the case in issue #2349
158-
where a connection is closed while the parser is reading to feed the internal buffer.
159-
The stremam read() will succeed, but when it returns, another task has already called
160-
`disconnect()` and is waiting for close to finish. When it attempts to feed the
161-
buffer, it will fail, since the buffer is no longer there.
157+
where a connection is closed while the parser is reading to feed the
158+
internal buffer.The stremam read() will succeed, but when it returns,
159+
another task hasalready called `disconnect()` and is waiting for
160+
close to finish. When it attempts to feed the buffer, it will fail
161+
since the buffer is no longer there.
162+
163+
This test verifies that a read in progress can finish even
164+
if the `disconnect()` method is called.
162165
"""
163166
if not HIREDIS_AVAILABLE:
164167
pytest.skip("Hiredis not available)")
@@ -194,7 +197,8 @@ async def do_close():
194197
await conn.disconnect()
195198

196199
async def do_read():
197-
await conn.read_response()
200+
with pytest.raises(InvalidResponse):
201+
await conn.read_response()
198202

199203
reader = AsyncMock()
200204
writer = AsyncMock()

0 commit comments

Comments
 (0)