Repository navigation
Keep a partly received pubsub message instead of reconnecting #4613
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
base: master
Are you sure you want to change the base?
Changes from 1 commit
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 |
|---|---|---|
|
|
@@ -2851,6 +2851,28 @@ def test_multiple_messages_with_timeout(self, r): | |
| assert messages[1]["data"] == b"msg2" | ||
| assert messages[2]["data"] == b"msg3" | ||
|
|
||
| def test_get_message_resumes_a_partly_received_message(self, r): | ||
| """ | ||
| A message that is only partly readable when get_message() polls is | ||
| returned by a later call, not dropped by a reconnect. | ||
| """ | ||
| p = r.pubsub() | ||
| p.subscribe("foo") | ||
|
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.
In the cluster test profile, this unmarked test shares Useful? React with 👍 / 👎.
Contributor
Author
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. Fixed: the test now uses its own channel, |
||
| assert wait_for_message(p, timeout=1.0) is not None | ||
| r.publish("foo", "hello") | ||
| assert p.connection.can_read(timeout=1.0) | ||
|
|
||
| # What the parser raises when the rest of the message does not arrive | ||
| # within the read timeout; the unread data stays on the connection. | ||
| with ( | ||
| mock.patch.object(p.connection, "read_response", side_effect=TimeoutError), | ||
| mock.patch.object(p, "_reconnect") as reconnect, | ||
| ): | ||
| assert p.get_message(timeout=0) is None | ||
| reconnect.assert_not_called() | ||
|
|
||
| assert wait_for_message(p, timeout=1.0)["data"] == b"hello" | ||
|
|
||
| def test_timeout_with_pattern_subscribe(self, r): | ||
| """ | ||
| Test that timeout works correctly with pattern subscriptions. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When RESP3 is used and
push_handler_funcraisesredis.exceptions.TimeoutError(for example, its own Redis I/O times out),Connection.read_response()routes that callback exception through this catch too, so nonblockingget_message()returnsNonerather than surfacing the handler failure. The Python RESP3 parser rewinds the frame on that exception, causing each poll to invoke the same failing handler again, while hiredis has already consumed the frame and silently drops the failure/message; the async PubSub path still propagates RedisTimeoutError. Narrow the suppression to a timeout produced by the bounded socket read.AGENTS.md reference: AGENTS.md:L147-L148
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, thanks. A
TimeoutErrorfrom apush_handler_funcreachestry_read()the same way as a read timeout, so the two cannot be told apart there without relying on the implicit exception chain. With a push handler installed, the timeout now goes to the retry layer as on master; only reads without one returnNone.test_get_message_timeout_with_push_handler_reaches_retry_layercovers this.One detail: on master, with the default retry, the handler failure is not surfaced either. The retry layer reconnects and the next read returns the resubscribe reply, so the failing message and the one after it are lost without an error. The regression was elsewhere: the pure-Python parser ran a handler that keeps failing again on every poll, so the subscriber spun without delivering anything. With this change the push handler case behaves as on master.