diff --git a/server/gameconnection.py b/server/gameconnection.py index 2f3d9f6b1..73774be53 100644 --- a/server/gameconnection.py +++ b/server/gameconnection.py @@ -114,6 +114,13 @@ async def _handle_idle_state(self): """ assert self.game + if self.state is not GameConnectionState.INITIALIZING: + self._logger.warning( + "Ignoring unexpected 'Idle' state when state was %s", + self.state, + ) + return + if self.player == self.game.host: self.game.state = GameState.LOBBY self._state = GameConnectionState.CONNECTED_TO_HOST @@ -132,6 +139,13 @@ async def _handle_lobby_state(self): """ player_state = self.player.state if player_state == PlayerState.HOSTING: + if self.game.hosted_at is not None: + self._logger.warning( + "Ignoring unexpected 'Lobby' state sent when the game was " + "already hosted.", + ) + return + await self.send_HostGame(self.game.map.folder_name) self.game.set_hosted() # If the player is joining, we connect him to host diff --git a/tests/integration_tests/test_game.py b/tests/integration_tests/test_game.py index 3c90ecb45..62dfb51e7 100644 --- a/tests/integration_tests/test_game.py +++ b/tests/integration_tests/test_game.py @@ -1320,6 +1320,48 @@ async def test_ladder_game_not_joinable(lobby_server): } +@fast_forward(60) +async def test_gamestate_lobby_double_game_instance(lobby_server): + """There was a bug in the client that was causing multiple game instances + to launch simultaneously. This could cause the GpgNet messages in the game + startup sequence to be sent twice. + """ + _, _, host_proto = await connect_and_sign_in( + ("test", "test_password"), lobby_server + ) + _, _, guest_proto = await connect_and_sign_in( + ("Rhiza", "puff_the_magic_dragon"), lobby_server + ) + await read_until_command(host_proto, "game_info") + await read_until_command(guest_proto, "game_info") + + await host_proto.send_message({ + "command": "game_host", + "mod": "faf", + "visibility": "public", + }) + msg = await read_until_command(host_proto, "game_launch") + game_id = int(msg["uid"]) + + for _ in range(2): + await host_proto.send_message({ + "target": "game", + "command": "GameState", + "args": ["Idle"] + }) + + for _ in range(2): + await host_proto.send_message({ + "target": "game", + "command": "GameState", + "args": ["Lobby"] + }) + + await read_until_command(host_proto, "HostGame", target="game") + + await join_game(guest_proto, game_id) + + @pytest.mark.flaky @fast_forward(60) async def test_gamestate_ended_clears_references( diff --git a/tests/unit_tests/test_gameconnection.py b/tests/unit_tests/test_gameconnection.py index f39b9facb..06821e838 100644 --- a/tests/unit_tests/test_gameconnection.py +++ b/tests/unit_tests/test_gameconnection.py @@ -99,18 +99,33 @@ async def test_connect_to_peer_disconnected(game_connection): await game_connection.connect_to_peer(peer) -async def test_handle_action_GameState_idle_adds_connection( +async def test_handle_action_GameState_idle_hosting( game: Game, game_connection: GameConnection, players ): - players.joining.game = game game_connection.player = players.hosting game_connection.game = game await game_connection.handle_action("GameState", ["Idle"]) - game.add_game_connection.assert_called_with(game_connection) + game.add_game_connection.assert_called_once_with(game_connection) + + +async def test_handle_action_GameState_idle_joining( + game: Game, + game_connection: GameConnection, + players +): + game_connection.player = players.joining + game_connection.game = game + + await game_connection.handle_action("GameState", ["Idle"]) + + assert players.joining.state == PlayerState.JOINING + assert game_connection.state == GameConnectionState.INITIALIZED + + game.add_game_connection.assert_not_called() async def test_handle_action_GameState_idle_sets_player_state(