slopus/happy · error
RPC target disconnected
Error message
RPC target disconnected
What it means
During an RPC call, the handler polls room presence to verify the target socket is still connected. If two consecutive presence polls (each RPC_PRESENCE_POLL_MS apart) fail to find the target socket in the room, it concludes the target disconnected and aborts the RPC with 'RPC target disconnected'.
Source
Thrown at packages/happy-server/sources/app/api/socket/rpcHandler.ts:232
// cancel pending broadcasts. Polling fetchSockets is the only way
// to detect "the target socket is gone" and abort fast (~2-4s).
//
// Requires 2 consecutive empty polls before declaring disconnect
// to avoid false positives from transient Redis/adapter timeouts.
const ackPromise = target.timeout(RPC_CALL_TIMEOUT_MS)
.emitWithAck('rpc-request', { method, params });
let presenceAlive = true;
const presencePoll = (async () => {
let consecutiveMisses = 0;
while (presenceAlive) {
await sleep(RPC_PRESENCE_POLL_MS);
if (!presenceAlive) return;
const stillThere = await fetchRoomSockets(io, room, RPC_PRESENCE_FETCH_TIMEOUT_MS, 'presence');
if (!stillThere.some(s => s.id === target.id)) {
consecutiveMisses++;
if (consecutiveMisses >= 2) {
throw new Error('RPC target disconnected');
}
} else {
consecutiveMisses = 0;
}
}
})();
try {
const response = await Promise.race([ackPromise, presencePoll]);
finish('success');
callback?.({ ok: true, result: response });
} catch (error) {
const errorMsg = error instanceof Error ? error.message : 'RPC call failed';
finish(errorMsg === 'RPC target disconnected' ? 'target_disconnected' : 'timeout');
callback?.({ ok: false, error: errorMsg });
} finally {
presenceAlive = false;
}View on GitHub (pinned to b824cd0a46)
Solutions
- Retry the RPC; on reconnect the client will have a fresh socket and the server should route to the new one.
- Treat it as an expected client-disconnect on the caller side and surface a friendly 'peer unavailable' state.
- Resolve the target's current socket id via the presence layer immediately before/instead of caching it.
- Reduce RPC_PRESENCE_POLL_MS / miss threshold if false positives occur, or increase tolerance if disconnects are transient.
Example fix
// before
const result = await rpc(targetSocketId, 'command', payload);
// after
try {
const result = await rpc(targetSocketId, 'command', payload);
} catch (e) {
if (e.message === 'RPC target disconnected') {
const fresh = await getFreshSocketId(peerId);
if (fresh) return rpc(fresh, 'command', payload);
}
throw e;
} Defensive patterns
Strategy: retry
Validate before calling
async function assertTargetOnline(io, room, socketId) {
const sockets = await fetchRoomSockets(io, room, 2000, 'precheck');
if (!sockets.some(s => s.id === socketId)) {
throw new Error('Target not currently connected; resolve fresh socket first');
}
} Try / catch
try {
return await rpcWithPresence(targetSocketId, method, payload);
} catch (e) {
if (e.message === 'RPC target disconnected') {
const fresh = await resolveCurrentSocketId(peerId);
if (fresh) return rpcWithPresence(fresh, method, payload);
markPeerOffline(peerId);
return null;
}
throw e;
} Prevention
- Always resolve the target's socket id from presence immediately before each RPC, never cache it.
- Handle client reconnects by keying RPC targets on a stable peer id, not socket id.
- Design RPC callers to tolerate peer disappearance (backgrounded mobile clients).
- Watch presence churn metrics to distinguish transient drops from real offline peers.
When it happens
Trigger: Target socket disconnects (tab close, network drop, server restart of the client) while an RPC is in flight; target left the socket room; presence fetch (fetchRoomSockets) succeeds but the target id is missing twice in a row.
Common situations: Mobile/web client going to background and its socket timing out mid-RPC; flaky network causing the client to drop and reconnect under a new socket id; RPC sent to a stale socket id after a reconnect.
Related errors
- Not connected to gateway
- The chosen rewind point is no longer present in the source C
- Resume session handler not available
- Authentication failed
- Server unavailable
AI-assisted analysis of slopus/happy@b824cd0a46 (2026-08-31).
Data as JSON: /api/errors/2dc33f200b826552.
Report an issue: GitHub.