-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Revert #2104, provide way to disable disconnects in read_response()
#2506
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3351e42
023c645
f873523
b0ff44b
6c268a1
03171f5
4b6caaa
76db492
04c5a97
6838db8
b26fda3
6345a69
524c454
1c32296
c97d3c4
b9c8c69
6733632
22f29cd
8df2c00
1bc5074
22a67d3
f42bbd7
794b285
3ec9029
0b599bd
284af1b
e34f456
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,11 @@ | ||
""" | ||
Tests async overrides of commands from their mixins | ||
""" | ||
import asyncio | ||
import binascii | ||
import datetime | ||
import re | ||
import sys | ||
from string import ascii_letters | ||
|
||
import pytest | ||
|
@@ -18,6 +20,11 @@ | |
skip_unless_arch_bits, | ||
) | ||
|
||
if sys.version_info.major >= 3 and sys.version_info.minor >= 11: | ||
from asyncio import timeout as async_timeout | ||
else: | ||
from async_timeout import timeout as async_timeout | ||
|
||
REDIS_6_VERSION = "5.9.0" | ||
|
||
|
||
|
@@ -2999,6 +3006,37 @@ async def test_module_list(self, r: redis.Redis): | |
for x in await r.module_list(): | ||
assert isinstance(x, dict) | ||
|
||
@pytest.mark.onlynoncluster | ||
async def test_interrupted_command(self, r: redis.Redis): | ||
""" | ||
Regression test for issue #1128: An Un-handled BaseException | ||
will leave the socket with un-read response to a previous | ||
command. | ||
""" | ||
ready = asyncio.Event() | ||
|
||
async def helper(): | ||
with pytest.raises(asyncio.CancelledError): | ||
# blocking pop | ||
ready.set() | ||
await r.brpop(["nonexist"]) | ||
# If the following is not done, further Timout operations will fail, | ||
# because the timeout won't catch its Cancelled Error if the task | ||
# has a pending cancel. Python documentation probably should reflect this. | ||
if sys.version_info.major >= 3 and sys.version_info.minor >= 11: | ||
asyncio.current_task().uncancel() | ||
# if all is well, we can continue. The following should not hang. | ||
await r.set("status", "down") | ||
Comment on lines
+3028
to
+3029
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still tbd to validate that not only you don't "hang", but also that the next read response matches the command. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, a separate test which continues an interrupted read seems proper. |
||
|
||
task = asyncio.create_task(helper()) | ||
await ready.wait() | ||
await asyncio.sleep(0.01) | ||
# the task is now sleeping, lets send it an exception | ||
task.cancel() | ||
# If all is well, the task should finish right away, otherwise fail with Timeout | ||
async with async_timeout(0.1): | ||
await task | ||
|
||
|
||
@pytest.mark.onlynoncluster | ||
class TestBinarySave: | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,9 +1,12 @@ | ||||||
import binascii | ||||||
import datetime | ||||||
import re | ||||||
import threading | ||||||
import time | ||||||
from asyncio import CancelledError | ||||||
from string import ascii_letters | ||||||
from unittest import mock | ||||||
from unittest.mock import patch | ||||||
|
||||||
import pytest | ||||||
|
||||||
|
@@ -4726,6 +4729,38 @@ def test_psync(self, r): | |||||
res = r2.psync(r2.client_id(), 1) | ||||||
assert b"FULLRESYNC" in res | ||||||
|
||||||
@pytest.mark.onlynoncluster | ||||||
def test_interrupted_command(self, r: redis.Redis): | ||||||
""" | ||||||
Regression test for issue #1128: An Un-handled BaseException | ||||||
will leave the socket with un-read response to a previous | ||||||
command. | ||||||
""" | ||||||
|
||||||
ok = False | ||||||
|
||||||
def helper(): | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
IIUC there's no way to get a retval from a thread, though you can pass a mutable object and use that to communicate. In particular it might be that we need to marshal errors to avoid them being silently ignored? e.g. https://github.com/bjoluc/pytest-reraise There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The test is relying on the thread actually finishing. There is no way to interrupt a hanging thread in python. If these tests fail, the test suite hangs. I'm open to suggestions on how to avoid that. I suppose that an old-school timeout can be added. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, there is no type hinting in redis-py, not going to start adding that to unit tests :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I mostly just meant for you to remove the retval. No need to type-annotate, though I do notice that some stuff is type-annotated. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right, there is the odd type hinting, but It is not consistently applied. I guess people add it to help their dev tools with code completion. |
||||||
with pytest.raises(CancelledError): | ||||||
# blocking pop | ||||||
with patch.object( | ||||||
r.connection._parser, "read_response", side_effect=CancelledError | ||||||
): | ||||||
r.brpop(["nonexist"]) | ||||||
# if all is well, we can continue. | ||||||
r.set("status", "down") # should not hang | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this also exercise the "command expects int but got back str" scenario? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No. It excercises the originally reported scenario: A blocking wait is initiated, and even though a new command is sent on the channel, the server is still in blocking wait mode. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That scenario doesn't require half-read. It's an interrupt between send_command and parse_response:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll add a half-read test case. |
||||||
nonlocal ok | ||||||
ok = True | ||||||
|
||||||
thread = threading.Thread(target=helper) | ||||||
thread.start() | ||||||
thread.join(0.1) | ||||||
try: | ||||||
assert not thread.is_alive() | ||||||
assert ok | ||||||
finally: | ||||||
# disconnect here so that fixture cleanup can proceed | ||||||
r.connection.disconnect() | ||||||
|
||||||
|
||||||
@pytest.mark.onlynoncluster | ||||||
class TestBinarySave: | ||||||
|
Uh oh!
There was an error while loading. Please reload this page.