Wake deadline consumers on enqueue instead of polling every second
This commit is contained in:
@@ -220,6 +220,7 @@ class DummyEngine(GameEngine):
|
||||
|
||||
def make_platform(
|
||||
engines: Sequence[GameEngine] = (DummyEngine(),),
|
||||
heartbeat_ms: int = 50,
|
||||
) -> Tuple[KayaApp, Platform, TortoiseMixin]:
|
||||
"""Build a throwaway app + platform wired with in-memory stores."""
|
||||
registry = GameRegistry(engines)
|
||||
@@ -251,7 +252,7 @@ def make_platform(
|
||||
scheduler = DeadlineScheduler(
|
||||
game_store,
|
||||
registry,
|
||||
heartbeat_ms=50,
|
||||
heartbeat_ms=heartbeat_ms,
|
||||
context_binder=tortoise_mixin.ensure_context,
|
||||
)
|
||||
platform = Platform(
|
||||
|
||||
@@ -112,6 +112,38 @@ async def _finished(store, game_id: str) -> Optional[GameSession]:
|
||||
return None
|
||||
|
||||
|
||||
class NotificationTest(unittest.TestCase):
|
||||
"""Deadline-enqueue notifications wake consumers on other workers."""
|
||||
|
||||
@async_test
|
||||
async def test_notification_wakes_a_long_heartbeat_consumer(self) -> None:
|
||||
# The consumer's heartbeat is a minute away and nothing sets its
|
||||
# local wake event: only the pub/sub notification can deliver the
|
||||
# deadline in time. This is the cross-worker path.
|
||||
_, platform, _ = make_platform(heartbeat_ms=60_000)
|
||||
store = platform.game_store
|
||||
scheduler = platform.scheduler
|
||||
session = _started_session(deadline_in_seconds=0.1)
|
||||
await store.save(session)
|
||||
scheduler.ensure_consumer()
|
||||
# Let the listener task subscribe before announcing.
|
||||
await asyncio.sleep(0.05)
|
||||
|
||||
engine = platform.registry.require(session.game_type)
|
||||
deadline = engine.next_deadline(session)
|
||||
assert deadline is not None
|
||||
member = encode({
|
||||
"game_id": session.id,
|
||||
"kind": deadline.kind,
|
||||
"token": deadline.token,
|
||||
})
|
||||
await store.add_deadline(member, deadline.due_at.timestamp())
|
||||
await store.notify_deadline()
|
||||
|
||||
result = await _wait_for(lambda: _plays_is(store, session.id, 1))
|
||||
self.assertIsNotNone(result, "notification did not wake the consumer")
|
||||
|
||||
|
||||
class ProcessDueTest(unittest.TestCase):
|
||||
"""Direct ``process_due`` behaviour: engine revalidation and idempotency."""
|
||||
|
||||
|
||||
@@ -270,6 +270,17 @@ class InMemoryGameStoreTest(unittest.TestCase):
|
||||
self.assertEqual(60.0, await store.next_deadline())
|
||||
self.assertEqual(["b"], await store.due_deadlines(now=100.0))
|
||||
|
||||
@async_test
|
||||
async def test_deadline_notifications_reach_subscribers(self) -> None:
|
||||
store = InMemoryGameStore(_registry())
|
||||
async with store.subscribe_deadlines() as events:
|
||||
await store.notify_deadline()
|
||||
signal = await asyncio.wait_for(anext(events), timeout=1.0)
|
||||
self.assertIsNone(signal)
|
||||
# The subscription is gone after the context exits: notifying
|
||||
# again is a no-op rather than an error.
|
||||
await store.notify_deadline()
|
||||
|
||||
|
||||
class RedisGameStoreTest(unittest.TestCase):
|
||||
@async_test
|
||||
|
||||
Reference in New Issue
Block a user