-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Description
Version: redis-py 4.0.2, Redis 6.2.6. Also reproduces with #1733 or #1737 applied.
Platform: Python 3.8, Ubuntu 20.04
Description: This is somewhat similar to #1720, but different. If get_message
is called between issuing an UNSUBSCRIBE command that removes that last subscription, and getting the unsubscribe message back, and parse_response
decides it needs to do a health check, then the reply to the health check is mis-interpreted. This is because redis responds differently to PING depending on whether there are active subscriptions or not, and parse_response
only recognises the one form.
aioredis has the same bug, and the fix (recognise both possible responses) would probably work for redis-py too.
Code to reproduce the issue (it's a race condition so might not work every time, but it's been reliable for me). Depends on a redis server on localhost:
#!/usr/bin/env python3
import threading
import time
import redis
def poll(ps, event):
print(ps.get_message(timeout=5))
event.wait()
while True:
message = ps.get_message(timeout=5)
if message is not None:
print(message)
else:
break
def main():
r = redis.Redis.from_url("redis://localhost", health_check_interval=1)
ps = r.pubsub()
ps.subscribe("foo")
event = threading.Event()
poller = threading.Thread(target=poll, args=(ps, event))
poller.start()
time.sleep(2)
event.set()
ps.unsubscribe("foo")
poller.join()
main()
It subscribes, then waits 2 seconds (to make the health check interval kick in), then races to call get_message
and unsubscribe
.
Output:
{'type': 'subscribe', 'pattern': None, 'channel': b'foo', 'data': 1}
{'type': 'unsubscribe', 'pattern': None, 'channel': b'foo', 'data': 0}
{'type': 114, 'pattern': None, 'channel': 101, 'data': 100}
The last line is the problem. It's caused by trying to treat a byte string (b"redis-py-health-check") as a list and selecting elements from it (114, 101, 100 are the ASCII codes for 'r', 'e', 'd').