Skip to content

ROUTER identity not updated on client reconnect, causing RPC/NV messages to be lost #381

@hacha

Description

@hacha

Summary

When a client reconnects after a brief sleep/wake cycle (shorter than the server's client_timeout of 5 seconds), the server continues sending RPC and NetworkVariable messages to the old ZeroMQ ROUTER identity, which no longer exists. This causes all control messages (RPC, NV sync, device ID mapping) to be silently dropped, while transform synchronization continues to work normally.

Symptoms

  • After one or more sleep/wake cycles on a Pico 4 UE device, the client:
    • CAN connect to the server and send avatar transforms
    • CAN receive transform updates from other clients (via PUB-SUB)
    • CANNOT receive NetworkVariable changes (via ROUTER-DEALER)
    • CANNOT receive RPC messages (via ROUTER-DEALER)

Root Cause

In server.py, _handle_client_transform() (line 1184-1212), when a client reconnects before the 5-second timeout:

  • is_new_client = False (the device ID is still in self.rooms[room_id])
  • The else branch updates transform_data, last_update, client_no, and is_stealth
  • But it does NOT update "identity" to the new ZeroMQ identity
# server.py:1201-1206
else:
    self.rooms[room_id][device_id]["transform_data"] = data_with_client_no
    self.rooms[room_id][device_id]["last_update"] = time.monotonic()
    self.rooms[room_id][device_id]["client_no"] = client_no
    self.rooms[room_id][device_id]["is_stealth"] = is_stealth
    # ⚠️ "identity" is NOT updated here

Why this happens

  1. Client connects → DEALER socket gets ZMQ identity A → server stores identity A
  2. App sleeps → OnApplicationPause(true)StopNetworking() → sockets destroyed
  3. App resumes → OnApplicationPause(false)StartNetworking() → new sockets → identity B
  4. Client sends transform with identity B
  5. Server: device_id still in self.rooms (timeout not yet reached) → is_new_client = False
  6. Server updates everything except identity → still holds identity A
  7. Server sends RPC/NV via _send_ctrl_to_room_via_router() → sends to identity A → no socket → message lost
  8. Transforms still work because PUB-SUB is topic-based, not identity-based

Why "multiple sleep/wake cycles" triggers it

  • If sleep duration > 5s: client times out, gets removed from rooms, next connect has is_new_client = True → identity correctly stored → works fine
  • If sleep duration < 5s: client still in rooms, is_new_client = False → identity mismatch → broken
  • Repeated cycles increase the probability of hitting a short sleep window

Proposed Fix

Add identity update to the else branch in _handle_client_transform():

else:
    self.rooms[room_id][device_id]["transform_data"] = data_with_client_no
    self.rooms[room_id][device_id]["last_update"] = time.monotonic()
    self.rooms[room_id][device_id]["client_no"] = client_no
    self.rooms[room_id][device_id]["is_stealth"] = is_stealth
    self.rooms[room_id][device_id]["identity"] = client_identity  # ← Add this line

This ensures the server always uses the client's current ZeroMQ identity for ROUTER-based message delivery, regardless of whether the client is new or reconnecting.

Affected Components

  • Server: server.py_handle_client_transform() (lines 1201-1206)
  • Client: ConnectionManager.csNetworkLoop() creates new sockets on every Connect(), generating new ZMQ identities
  • Client: NetSyncManager.csOnApplicationPause() calls StopNetworking()/StartNetworking() which triggers socket recreation

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions