ppy/osu · error · InvalidOperationException
Already joined a room
Error message
Already joined a room
What it means
JoinRoomInternal enforces a one-active-room-per-client invariant (mirroring the real server): if RoomJoined is already true OR ServerAPIRoom is non-null, joining again throws before any state mutates. The client must leave its current room before joining another.
Source
Thrown at osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs:236
break;
}
}
public void ChangeUserBeatmapAvailability(int userId, BeatmapAvailability newBeatmapAvailability)
{
Debug.Assert(ServerRoom != null);
var user = ServerRoom.Users.Single(u => u.UserID == userId);
user.BeatmapAvailability = newBeatmapAvailability;
((IMultiplayerClient)this).UserBeatmapAvailabilityChanged(clone(userId), clone(user.BeatmapAvailability));
}
protected override async Task<MultiplayerRoom> JoinRoomInternal(long roomId, string? password = null)
{
if (RoomJoined || ServerAPIRoom != null)
throw new InvalidOperationException("Already joined a room");
roomId = clone(roomId);
password = clone(password);
ServerAPIRoom = ServerSideRooms.Single(r => r.RoomID == roomId);
if (password != ServerAPIRoom.Password)
throw new InvalidOperationException("Invalid password.");
lastPlaylistItemId = ServerAPIRoom.Playlist.Max(item => item.ID);
var localUser = new MultiplayerRoomUser(api.LocalUser.Value.Id)
{
User = api.LocalUser.Value
};
ServerRoom = new MultiplayerRoom(roomId)
{
View on GitHub (pinned to d9c73e12ad)
Solutions
- Call LeaveRoom() before joining a new room.
- Add a teardown step that leaves the room so each test starts clean.
- If the prior join failed mid-way, explicitly null out state by leaving before retrying.
Example fix
// before await client.JoinRoom(room1); await client.JoinRoom(room2); // throws // after await client.JoinRoom(room1); await client.LeaveRoom(); await client.JoinRoom(room2);
Defensive patterns
Strategy: validation
Validate before calling
if (client.RoomJoined || client.ServerAPIRoom != null)
await client.LeaveRoom();
await client.JoinRoom(room); Prevention
- Always pair a JoinRoom with a LeaveRoom in test teardown.
- Before joining a second room, check RoomJoined and leave first.
- Keep one TestMultiplayerClient responsible for at most one active room at a time.
When it happens
Trigger: Calling JoinRoom a second time without an intervening LeaveRoom; reusing one TestMultiplayerClient instance across two room-join steps.
Common situations: Test setup that joins a room but never tears it down; sequential join steps in one test without cleanup; a previous join left partial state after a failure.
Related errors
- Accessing the client-side API room via {nameof(TestMultiplay
- Accessing the client-side room via {nameof(TestMultiplayerCl
- Invalid password.
- Local user is not the room host.
- Attempted to change an item that doesn't exist.
AI-assisted analysis of ppy/osu@d9c73e12ad (2026-08-13).
Data as JSON: /api/errors/ce34d8c5caad6ade.
Report an issue: GitHub.