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

  1. Retry the RPC; on reconnect the client will have a fresh socket and the server should route to the new one.
  2. Treat it as an expected client-disconnect on the caller side and surface a friendly 'peer unavailable' state.
  3. Resolve the target's current socket id via the presence layer immediately before/instead of caching it.
  4. 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

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


AI-assisted analysis of slopus/happy@b824cd0a46 (2026-08-31). Data as JSON: /api/errors/2dc33f200b826552. Report an issue: GitHub.