From 20afe0ac6627c311635e3106435b42ddc600be6d Mon Sep 17 00:00:00 2001 From: Gatsik <74517072+Gatsik@users.noreply.github.com> Date: Wed, 22 Jan 2025 20:37:11 +0200 Subject: [PATCH] Use cached_property to create Futures and avoid trying to access possibly non-existent event loop in init methods, which allows to construct affected classes without a running event loop fix flaky test --- server/games/game.py | 6 +++++- server/games/ladder_game.py | 7 ++++--- server/matchmaker/search.py | 6 +++++- tests/integration_tests/test_matchmaker.py | 6 ++---- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/server/games/game.py b/server/games/game.py index d337034b2..74f446a58 100644 --- a/server/games/game.py +++ b/server/games/game.py @@ -5,6 +5,7 @@ import pathlib import time from collections import defaultdict +from functools import cached_property from typing import Any, Awaitable, Callable, Iterable, Optional from sqlalchemy import and_, bindparam @@ -132,12 +133,15 @@ def __init__( self.game_options.add_callback("Title", self.on_title_changed) self.mods = {} - self._hosted_future = asyncio.Future() self._finish_lock = asyncio.Lock() self._logger.debug("%s created", self) asyncio.get_event_loop().create_task(self.timeout_game(setup_timeout)) + @cached_property + def _hosted_future(self) -> asyncio.Future: + return asyncio.get_event_loop().create_future() + async def timeout_game(self, timeout: int = 60): await asyncio.sleep(timeout) if self.state is GameState.INITIALIZING: diff --git a/server/games/ladder_game.py b/server/games/ladder_game.py index 475196bcb..23152d518 100644 --- a/server/games/ladder_game.py +++ b/server/games/ladder_game.py @@ -1,5 +1,6 @@ import asyncio import logging +from functools import cached_property from typing import Optional from server.config import config @@ -27,9 +28,9 @@ class LadderGame(Game): init_mode = InitMode.AUTO_LOBBY game_type = GameType.MATCHMAKER - def __init__(self, id, *args, **kwargs): - super().__init__(id, *args, **kwargs) - self._launch_future = asyncio.Future() + @cached_property + def _launch_future(self) -> asyncio.Future: + return asyncio.get_event_loop().create_future() async def wait_hosted(self, timeout: float): return await asyncio.wait_for( diff --git a/server/matchmaker/search.py b/server/matchmaker/search.py index 962b1b98b..aa6ab0ee8 100644 --- a/server/matchmaker/search.py +++ b/server/matchmaker/search.py @@ -3,6 +3,7 @@ import math import statistics import time +from functools import cached_property from typing import Any, Callable, Optional import trueskill @@ -41,13 +42,16 @@ def __init__( self.players = players self.rating_type = rating_type self.start_time = start_time or time.time() - self._match = asyncio.get_event_loop().create_future() self._failed_matching_attempts = 0 self.on_matched = on_matched # Precompute this self.quality_against_self = self.quality_with(self) + @cached_property + def _match(self) -> asyncio.Future: + return asyncio.get_event_loop().create_future() + def adjusted_rating(self, player: Player) -> Rating: """ Returns an adjusted mean with a simple linear interpolation between current mean and a specified base mean diff --git a/tests/integration_tests/test_matchmaker.py b/tests/integration_tests/test_matchmaker.py index be58d7ae4..55362bbf0 100644 --- a/tests/integration_tests/test_matchmaker.py +++ b/tests/integration_tests/test_matchmaker.py @@ -100,9 +100,8 @@ async def test_game_launch_message_game_options(lobby_server, tmp_user): } -@pytest.mark.flaky @fast_forward(15) -async def test_game_matchmaking_start(lobby_server, database): +async def test_game_matchmaking_start(lobby_server, database, game_service): host_id, host, guest_id, guest = await queue_players_for_matchmaking(lobby_server) # The player that queued last will be the host @@ -139,8 +138,7 @@ async def test_game_matchmaking_start(lobby_server, database): }) # Wait for db to be updated - await read_until_launched(host, game_id) - await read_until_launched(guest, game_id) + await game_service[game_id].wait_launched(None) async with database.acquire() as conn: result = await conn.execute(select(