-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Async redis subscription inside websocket doesn't shut down properly #2523
Comments
Still not working. Anyone have a solution? This seems to make PubSub completely unusable if asyncio.CancelledError isn't raised |
+1 |
This has been working for me. There are some random variables like "readers"; just copy pasted from a project so parse thru what you need: import asyncio
from collections.abc import Callable, Coroutine
from typing import Literal
from redis.asyncio.client import PubSub
from app.utils.redis.subscriber.typing import ChatReadersT, ReadersT, ReaderT
async def reader(channel: redis.client.PubSub, readers: ReadersT):
try:
while True:
# https://github.com/redis/redis-py/issues/2523
message: ChannelMessage = await channel.get_message(
ignore_subscribe_messages=True, timeout=0.5
)
if message is None:
continue
_channel = message["channel"].decode().split(":", 1)[1]
if ":" not in _channel:
_channel = int(_channel)
wsr = readers.get().get(_channel, {}).items()
data: dict = orjson.loads(message["data"])
reader_id = data.pop("id", None)
data = data["data"]
[w.messages.put_nowait(data) for k, w in wsr if k != reader_id]
except RedisConnectionError:
pass
finally:
pass
class BaseRedisConnection:
def __init__(
self,
*,
channel: str,
include_wildcard: bool = True,
reader: Callable,
subscription_type: Literal["psubscribe", "subscribe"] = "psubscribe",
):
"""
Handler for PubSub connection
:param channel: PubSub channel name
:param include_wildcard: Whether to make PubSub channel name include a wildcard.
Applicable only to when subscription_type is "psubscribe"
:param reader: An infinite loop callable that reads from the PubSub channel
:param subscription_type: the type of PubSub subscription to use
"""
self.reader_task: asyncio.Task | None = None
self.r = None
self.pubsub: PubSub | None = None
self.reader = reader
self.subscription_type = subscription_type
if subscription_type == "psubscribe" and include_wildcard:
self.channel = f"{channel}:*"
else:
self.channel = channel
async def create_reader(self, pubsub: PubSub) -> Coroutine:
raise NotImplementedError("create_reader() must be implemented")
async def start(self):
self.pubsub = self.r.pubsub()
await getattr(self.pubsub, self.subscription_type)(self.channel)
self.reader_task = asyncio.create_task(await self.create_reader(self.pubsub))
await self.reader_task
async def close(self):
await self.pubsub.close()
if self.reader_task is not None:
self.reader_task.cancel()
class RedisConnection(BaseRedisConnection):
def __init__(
self,
*,
channel: str,
include_wildcard: bool = True,
subscription_type: Literal["psubscribe", "subscribe"],
reader: ReaderT,
readers: ReadersT | ChatReadersT,
):
super().__init__(
channel=channel,
include_wildcard=include_wildcard,
subscription_type=subscription_type,
reader=reader,
)
self.readers = readers
async def create_reader(self, pubsub: PubSub) -> Coroutine:
return self.reader(pubsub, self.readers) |
What worked for me is scheduling the reader method with reader_task = asyncio.create_task(reader(# ..args)) Then I have a lifespan method to make sure it gets shut down: @asynccontextmanager
async def lifespan(app: FastAPI):
"""Ensure redis gets closed cleanly when shutting down"""
yield
reader_task.cancel() Register it like this: app = FastAPI(
lifespan=lifespan,
# ...
) |
Version: 4.4.0
Platform: Python 3.11.0, ubuntu
Description:
I am using async redis to subscribe to a topic within a fastapi websocket connection. It works fine, but I cannot make it shut down properly. i.e. when the server shuts down, the
await get_message
is not stopping.I have tried to use SIGTERM, but it seems the reader-function doesn't yield to SIGTERM, because it gives error
Before handling the SIGTERM command.
Does anyone have a suggestion for where to start looking?
The text was updated successfully, but these errors were encountered: