simplify redis code, less redis calls (#4456)

* simplify redis code, less redis calls

* cleanup

* properly ignore non-lock events from redis pubsub, keep timeout
This commit is contained in:
benedikt-bartscher 2024-12-12 14:56:42 +01:00 committed by GitHub
parent a2f14e7713
commit 1b6f539657
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -11,6 +11,7 @@ import inspect
import json import json
import pickle import pickle
import sys import sys
import time
import typing import typing
import uuid import uuid
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
@ -39,6 +40,7 @@ from typing import (
get_type_hints, get_type_hints,
) )
from redis.asyncio.client import PubSub
from sqlalchemy.orm import DeclarativeBase from sqlalchemy.orm import DeclarativeBase
from typing_extensions import Self from typing_extensions import Self
@ -3479,6 +3481,35 @@ class StateManagerRedis(StateManager):
nx=True, # only set if it doesn't exist nx=True, # only set if it doesn't exist
) )
async def _get_pubsub_message(
self, pubsub: PubSub, timeout: float | None = None
) -> None:
"""Get lock release events from the pubsub.
Args:
pubsub: The pubsub to get a message from.
timeout: Remaining time to wait for a message.
Returns:
The message.
"""
if timeout is None:
timeout = self.lock_expiration / 1000.0
started = time.time()
message = await pubsub.get_message(
ignore_subscribe_messages=True,
timeout=timeout,
)
if (
message is None
or message["data"] not in self._redis_keyspace_lock_release_events
):
remaining = timeout - (time.time() - started)
if remaining <= 0:
return
await self._get_pubsub_message(pubsub, timeout=remaining)
async def _wait_lock(self, lock_key: bytes, lock_id: bytes) -> None: async def _wait_lock(self, lock_key: bytes, lock_id: bytes) -> None:
"""Wait for a redis lock to be released via pubsub. """Wait for a redis lock to be released via pubsub.
@ -3491,7 +3522,6 @@ class StateManagerRedis(StateManager):
Raises: Raises:
ResponseError: when the keyspace config cannot be set. ResponseError: when the keyspace config cannot be set.
""" """
state_is_locked = False
lock_key_channel = f"__keyspace@0__:{lock_key.decode()}" lock_key_channel = f"__keyspace@0__:{lock_key.decode()}"
# Enable keyspace notifications for the lock key, so we know when it is available. # Enable keyspace notifications for the lock key, so we know when it is available.
try: try:
@ -3505,20 +3535,13 @@ class StateManagerRedis(StateManager):
raise raise
async with self.redis.pubsub() as pubsub: async with self.redis.pubsub() as pubsub:
await pubsub.psubscribe(lock_key_channel) await pubsub.psubscribe(lock_key_channel)
while not state_is_locked:
# wait for the lock to be released # wait for the lock to be released
while True: while True:
if not await self.redis.exists(lock_key): # fast path
break # key was removed, try to get the lock again if await self._try_get_lock(lock_key, lock_id):
message = await pubsub.get_message( return
ignore_subscribe_messages=True, # wait for lock events
timeout=self.lock_expiration / 1000.0, await self._get_pubsub_message(pubsub)
)
if message is None:
continue
if message["data"] in self._redis_keyspace_lock_release_events:
break
state_is_locked = await self._try_get_lock(lock_key, lock_id)
@contextlib.asynccontextmanager @contextlib.asynccontextmanager
async def _lock(self, token: str): async def _lock(self, token: str):