Errors and Disconnects

How Errors Are Reported

Errors reach your code through three channels. Failures of an operation you started arrive as the Err state of that operation's Result, this is where expected failures such as a full room land. Errors that occur outside any pending operation are broadcast through OnError(ErrorCode, message). Loss of the connection itself is reported through OnDisconnected(DisconnectCause).

Every failure is described by an Error value. Code is the machine-readable ErrorCode your logic branches on; Message is a human-readable UTF-8 string meant for logs and diagnostics, not for parsing.

The standard handling shape: check the result, branch on the code, log the message.

C++

Result<MutableRoomView> joined = co_await client.JoinRoom(PHOTON_STR("battle-01"));

if (joined.IsErr())
{
    switch (joined.GetErrorCode())
    {
    case ErrorCode::RoomNotFound:
        // fall back to creating the room
        break;
    case ErrorCode::RoomFull:
        // tell the player and return to matchmaking
        break;
    default:
        std::printf("join failed: %s\n",
                    reinterpret_cast<const char*>(joined.GetError().Message.c_str()));
        break;
    }
}

Error Codes

ErrorCode values are grouped by area: connection (ConnectionFailed, Timeout, ...), authentication (InvalidAuthentication, MaxCCUReached, ...), room and matchmaking (RoomFull, NoMatchFound, ...), operation limits (OperationDenied, RateLimited, ...), server (InternalServerError, PluginError, ...), client state (NotConnected, NotInRoom, InvalidState) and events (EventSizeMismatch, EventEncodeFailed, EventDecodeFailed, EventPayloadTypeMismatch). The complete list lives in the enums reference.

Code Typical Trigger Sensible Reaction
RoomFull Joining a room whose MaxPlayers is reached. Pick another room or fall back to random matchmaking.
RoomNotFound Joining a named room that does not exist or has closed. Create the room, or use JoinOrCreateRoom up front.
RoomAlreadyExists Creating a room whose name is taken. Join it instead, or use JoinOrCreateRoom up front.
NoMatchFound Random matchmaking found no suitable room. Create a room, or relax the matchmaking filter and retry.
InvalidState The operation is not valid in the current client state. Fix the call order; do not retry blindly.
MaxCCUReached The application's concurrent-user limit is exhausted. Increase the max CCU by upgrading your plan in the Photon Dashboard.
RateLimited Operations were issued faster than the server allows. Throttle the offending call site and retry after a delay.
PluginError A server-side plugin rejected or failed the operation. Log the message; the fix is usually server-side.
EventSizeMismatch An event arrived whose payload size does not match sizeof(T) of a SubscribeEvent<T>() subscription. Version the event code, or receive it raw and branch on the size.
EventPayloadTypeMismatch A byte-span event subscription received a structured payload. Subscribe with const RealtimeValue& for that code.

InvalidState deserves special attention: it means the operation was called in a state where it cannot work, Connect() while already connected, room operations while not in a room. It signals a call-ordering bug on your side rather than a network problem, so treat it as a defect to fix, not an error to retry.

Handling Disconnects

Distinguish deliberate from unexpected disconnects. A Disconnect() you issue resolves its own Task, so its completion is the natural place to react. Unexpected drops, timeouts, server-side closes, fire OnDisconnected(DisconnectCause), and GetDisconnectCause() retains the last cause for post-mortem checks.

Causes Recommended Reaction
TimeoutDisconnect, Exception, ExceptionOnConnect Transient network trouble. Attempt Reconnect(), then a fresh Connect().
DisconnectByServer, DisconnectByServerLogic The server closed the connection. Inform the player and reconnect with backoff.
AuthenticationTicketExpired The authentication ticket aged out. Re-authenticate and connect again.
InvalidAuthentication, CustomAuthenticationFailed, InvalidRegion, DashboardVersionInvalid Credentials or configuration are wrong. Fix the app id, auth values or dashboard settings; retrying unchanged fails again.
ClientVersionTooOld, ClientVersionInvalid The client build is no longer accepted. Prompt the player to update.
MaxCCUReached, DisconnectByServerUserLimit, DisconnectByOperationLimit A server-enforced limit was hit. Back off before retrying.
OperationNotAllowedInCurrentState A local call-ordering bug triggered the disconnect. Fix the code.
None No disconnect has been recorded.

A handler separating transient timeouts from server-initiated closes.

C++

RealtimeCore::Common::ScopedSubscription onDisconnected = client.OnDisconnected.Subscribe(
    [](DisconnectCause cause) {
        switch (cause)
        {
        case DisconnectCause::TimeoutDisconnect:
        case DisconnectCause::Exception:
            // transient: try to restore the session
            break;
        case DisconnectCause::DisconnectByServer:
        case DisconnectCause::DisconnectByServerLogic:
            // server-initiated: inform the player before retrying
            break;
        default:
            // configuration or version problems: do not auto-retry
            break;
        }
    });

Warnings

OnWarning(int warningCode) reports non-fatal conditions the SDK noticed. Treat warnings as log-worthy signals: record them with enough context to correlate against later problems, but do not build control flow on them.

Recovery Patterns

The primary recovery tool is Reconnect(). Called from the Disconnected state, it re-establishes the previous session and rejoins the room the client was last in, provided the room was created with a PlayerTtlMs large enough that the player's slot is still reserved. See Connection and Regions for the reconnect semantics and rejoin preconditions.

When Reconnect() is not enough, escalate step by step. Try Reconnect() first; if it fails, make a fresh Connect() followed by JoinRoom with Rejoin = true; if the room or the player slot is gone, re-enter matchmaking from scratch.

A recovery flow with OrElse falling back from Reconnect() to a full connect-and-rejoin.

C++

Task<Result<void>> ConnectAndRejoin(RealtimeClient& client, RealtimeCore::Common::StringType roomName)
{
    Result<void> connected = co_await client.Connect();
    if (connected.IsErr())
    {
        co_return connected;
    }

    JoinRoomOptions options;
    options.Rejoin = true;
    Result<MutableRoomView> joined = co_await client.JoinRoom(roomName, options);
    co_return joined.IsOk() ? Result<void>::Ok() : Result<void>::Err(joined.GetError());
}

Task<Result<void>> RecoverSession(RealtimeClient& client, RealtimeCore::Common::StringType roomName)
{
    return client.Reconnect()
        .OrElse([&client, roomName](const Error&) { return ConnectAndRejoin(client, roomName); });
}

For failures that are expected within a flow, a full room during matchmaking, a name collision when creating, prefer recovering in place with OrElse inside the operation chain over out-of-band retry loops. See Asynchronous Operations for the combinators.

Back to top