ppy/osu · error · InvalidOperationException

Cannot join a multiplayer room while already in one.

Error message

Cannot join a multiplayer room while already in one.

What it means

Thrown by MultiplayerClient.JoinRoom when Room is already non-null. The user may only occupy one room; a second join is treated as a programming error rather than a server request.

Source

Thrown at osu.Game/Online/Multiplayer/MultiplayerClient.cs:261

            await joinOrLeaveTaskChain.Add(async () =>
            {
                await runOnUpdateThreadAsync(() => pendingRequests.Clear(), cancellationSource.Token).ConfigureAwait(false);
                var multiplayerRoom = await CreateRoomInternal(new MultiplayerRoom(room)).ConfigureAwait(false);
                await setupJoinedRoom(room, multiplayerRoom, cancellationSource.Token).ConfigureAwait(false);
            }, cancellationSource.Token).ConfigureAwait(false);
        }

        /// <summary>
        /// Joins the <see cref="MultiplayerRoom"/> for a given API <see cref="Room"/>.
        /// </summary>
        /// <param name="room">The API <see cref="Room"/>.</param>
        /// <param name="password">An optional password to use for the join operation.</param>
        /// <exception cref="InvalidOperationException">If the current user is already in another room, or <paramref name="room"/> does not represent an active room.</exception>
        public async Task JoinRoom(Room room, string? password = null)
        {
            if (Room != null)
                throw new InvalidOperationException("Cannot join a multiplayer room while already in one.");

            if (room.RoomID == null)
                throw new InvalidOperationException("Cannot join an inactive room.");

            var cancellationSource = joinCancellationSource = new CancellationTokenSource();

            await joinOrLeaveTaskChain.Add(async () =>
            {
                await runOnUpdateThreadAsync(() => pendingRequests.Clear(), cancellationSource.Token).ConfigureAwait(false);
                var multiplayerRoom = await JoinRoomInternal(room.RoomID.Value, password ?? room.Password).ConfigureAwait(false);
                await setupJoinedRoom(room, multiplayerRoom, cancellationSource.Token).ConfigureAwait(false);
            }, cancellationSource.Token).ConfigureAwait(false);
        }

        /// <summary>
        /// Performs post-join setup of a <see cref="MultiplayerRoom"/>.
        /// </summary>
        /// <param name="apiRoom">The incoming API <see cref="Room"/> that was requested to be joined.</param>

View on GitHub (pinned to d9c73e12ad)

Solutions

  1. Ensure LeaveRoom() is awaited before JoinRoom.
  2. Gate the join control on client.Room == null.
  3. Wrap join attempts in a helper that leaves first if needed: if (client.Room != null) await client.LeaveRoom();

Example fix

// before
await client.JoinRoom(room, password);

// after
if (client.Room != null)
    await client.LeaveRoom();
await client.JoinRoom(room, password);
Defensive patterns

Strategy: validation

Validate before calling

if (client.Room != null)
    await client.LeaveRoom();
await client.JoinRoom(room, password);

Try / catch

try { await client.JoinRoom(room, password); }
catch (InvalidOperationException) { /* occupied or inactive: handle in UI */ }

Prevention

When it happens

Trigger: Calling JoinRoom while already in a room without leaving first. Typically a UI flow bug, e.g. navigating from a room into another via a join action that did not first part the current room.

Common situations: Listing/lounge click that joins a new room while the previous one was not left; reconnect logic that joins again before clearing Room.

Related errors


AI-assisted analysis of ppy/osu@d9c73e12ad (2026-08-13). Data as JSON: /api/errors/ece3e73278cc4582. Report an issue: GitHub.