Poll deadlines with ZRANGE BYSCORE instead of the deprecated ZRANGEBYSCORE
This commit is contained in:
@@ -244,10 +244,17 @@ class RedisGameStore(GameStore):
|
||||
await self._redis.zadd(DEADLINES_KEY, {member: due_at})
|
||||
|
||||
async def due_deadlines(self, now: float, limit: int = 32) -> List[str]:
|
||||
# ``ZRANGE ... BYSCORE`` is the non-deprecated spelling of the
|
||||
# old ``ZRANGEBYSCORE``.
|
||||
members = cast(
|
||||
list,
|
||||
await self._redis.zrangebyscore(
|
||||
DEADLINES_KEY, "-inf", now, start=0, num=limit
|
||||
await self._redis.zrange(
|
||||
DEADLINES_KEY,
|
||||
"-inf",
|
||||
now,
|
||||
byscore=True,
|
||||
offset=0,
|
||||
num=limit,
|
||||
),
|
||||
)
|
||||
return [m.decode("utf-8") if isinstance(m, bytes) else m for m in members]
|
||||
|
||||
@@ -4,10 +4,12 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
import time
|
||||
import unittest
|
||||
from typing import Any, Optional, cast
|
||||
|
||||
from tavolo.platform import GameSession, Seat
|
||||
from tavolo.platform.registry import GameRegistry
|
||||
from tavolo.platform.store import (
|
||||
DEADLINES_KEY,
|
||||
ONGOING_GAMES_KEY,
|
||||
InMemoryGameStore,
|
||||
RedisGameStore,
|
||||
@@ -75,10 +77,39 @@ class _FakeRedis:
|
||||
def __init__(self) -> None:
|
||||
self.keys: dict = {}
|
||||
self.zsets: dict = {}
|
||||
self.zrange_calls: list = []
|
||||
|
||||
def pipeline(self, transaction: bool = True) -> _FakePipeline:
|
||||
return _FakePipeline(self)
|
||||
|
||||
async def zadd(self, key: str, mapping: dict) -> int:
|
||||
return cast(int, self.apply(("zadd", key, mapping)))
|
||||
|
||||
async def zrem(self, key: str, member: str) -> int:
|
||||
return cast(int, self.apply(("zrem", key, member)))
|
||||
|
||||
async def zrange(
|
||||
self,
|
||||
name: str,
|
||||
start: Any,
|
||||
end: Any,
|
||||
byscore: bool = False,
|
||||
offset: Optional[int] = None,
|
||||
num: Optional[int] = None,
|
||||
**kwargs: object,
|
||||
) -> list:
|
||||
self.zrange_calls.append((name, start, end, byscore, offset, num))
|
||||
members = sorted(
|
||||
self.zsets.get(name, {}).items(), key=lambda item: item[1]
|
||||
)
|
||||
if byscore:
|
||||
low = float("-inf") if start == "-inf" else float(start)
|
||||
high = float("inf") if end == "+inf" else float(end)
|
||||
members = [item for item in members if low <= item[1] <= high]
|
||||
if offset is not None and num is not None:
|
||||
members = members[offset : offset + num]
|
||||
return [member for member, _ in members]
|
||||
|
||||
def apply(self, op: tuple) -> object:
|
||||
name, *args = op
|
||||
if name == "set":
|
||||
@@ -269,6 +300,24 @@ class RedisGameStoreTest(unittest.TestCase):
|
||||
self.assertEqual(1, await store.count_ongoing())
|
||||
self.assertNotIn("stale", redis.zsets[ONGOING_GAMES_KEY])
|
||||
|
||||
@async_test
|
||||
async def test_due_deadlines_uses_zrange_byscore(self) -> None:
|
||||
redis = _FakeRedis()
|
||||
store = RedisGameStore(redis, _registry()) # type: ignore[arg-type]
|
||||
await store.add_deadline("b", due_at=50.0)
|
||||
await store.add_deadline("a", due_at=10.0)
|
||||
await store.add_deadline("c", due_at=200.0)
|
||||
# Re-adding an existing member only updates its due time.
|
||||
await store.add_deadline("b", due_at=60.0)
|
||||
|
||||
self.assertEqual(["a"], await store.due_deadlines(now=20.0))
|
||||
self.assertEqual(
|
||||
[(DEADLINES_KEY, "-inf", 20.0, True, 0, 32)], redis.zrange_calls
|
||||
)
|
||||
# Matches come back in due-time order and honour the limit.
|
||||
self.assertEqual(["a"], await store.due_deadlines(now=100.0, limit=1))
|
||||
self.assertEqual(["a", "b"], await store.due_deadlines(now=100.0))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user