Skip to content

Another race condition in health checks and pubsub #1740

@bmerry

Description

@bmerry

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').

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions