Skip to content

Commit 00e07a7

Browse files
committed
Fix handling of CancelledError by async client
1 parent 6c708c2 commit 00e07a7

File tree

2 files changed

+51
-0
lines changed

2 files changed

+51
-0
lines changed

redis/asyncio/client.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,12 @@ async def execute_command(self, *args, **options):
517517
),
518518
lambda error: self._disconnect_raise(conn, error),
519519
)
520+
except asyncio.CancelledError:
521+
# If we're cancelled and cancel happned after we sent the command
522+
# but before we read the response, we need to read the response
523+
# to avoid reading responses out of order.
524+
await conn.read_response(timeout=0.001)
525+
raise
520526
finally:
521527
if self.single_connection_client:
522528
self._single_conn_lock.release()

tests/test_asyncio/test_connection.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,3 +273,48 @@ async def open_connection(*args, **kwargs):
273273

274274
vals = await asyncio.gather(do_read(), do_close())
275275
assert vals == [b"Hello, World!", None]
276+
277+
278+
@pytest.mark.onlynoncluster
279+
async def test_client_handle_canceled_error(create_redis):
280+
"""
281+
This test reproduces the case in issue #2539
282+
where asyncio.CancelledError is raised when the parser is reading to feed the
283+
internal buffer. The stream `readline()` will be interrupted by the CancelledError,
284+
which will result in not reading the response after executing the command. This will
285+
cause responses to be mixed up between commands. In this test, we execute a command
286+
after the CancelledError is raised, and verify that the response is correct.
287+
"""
288+
r = await create_redis(single_connection_client=True)
289+
290+
async def do_pings():
291+
while True:
292+
await r.ping()
293+
294+
future = asyncio.ensure_future(do_pings())
295+
await asyncio.sleep(0.01)
296+
future.cancel()
297+
with pytest.raises(asyncio.CancelledError):
298+
await future
299+
# To reproduce the issue, we need to execute a command which returns a different
300+
# response type than PING. In this case, we use EXISTS because it should return an
301+
# integer.
302+
assert await r.exists("foo") == 0
303+
304+
await r.sadd("set", "one")
305+
await r.sadd("set", "two")
306+
await r.sadd("set", "three")
307+
308+
async def do_smembers():
309+
while True:
310+
await r.smembers("set")
311+
312+
future = asyncio.ensure_future(do_smembers())
313+
await asyncio.sleep(0.01)
314+
future.cancel()
315+
with pytest.raises(asyncio.CancelledError):
316+
await future
317+
318+
assert await r.exists("foo") == 0
319+
320+
await r.connection.disconnect()

0 commit comments

Comments
 (0)