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.