Skip to content

Presence diff stops processing leaves after an untracked key #1630

Closed

Description

@hsusul

Bug report

AsyncRealtimePresence._sync_diff uses break where it means continue. When a presence_diff message contains leaves for a key the client is not currently tracking, the loop aborts and every remaining key in that diff is silently ignored — those presences stay in presence.state forever and on_presence_leave never fires for them.

https://github.com/supabase/supabase-py/blob/main/src/realtime/src/realtime/_async/presence.py#L114-L119

for key, left_presences in leaves.items():
    current_presences = self.state.get(key, [])

    if len(current_presences) == 0:
        break   # aborts the whole loop; should skip just this key

Phoenix's presence.js — which this file is a port of — returns from the per-key callback there, i.e. continue.

Since leaves comes straight off the wire in _on_diff_event, the server decides both the contents and the key order. A diff that reports an untracked key before a tracked one is enough to lose the tracked leave.

Reproduction

from realtime._async.presence import AsyncRealtimePresence

presence = AsyncRealtimePresence()
presence.on_leave(lambda key, current, left: print("on_leave", key))

presence._on_state_event({"bob": {"metas": [{"phx_ref": "ref-bob"}]}})
print("state:", presence.state)

# alice is unknown to this client (it joined after she left / never saw her state)
presence._on_diff_event(
    {
        "joins": {},
        "leaves": {
            "alice": {"metas": [{"phx_ref": "ref-alice"}]},
            "bob": {"metas": [{"phx_ref": "ref-bob"}]},
        },
    }
)
print("state:", presence.state)

Expected behavior

state: {'bob': [{'presence_ref': 'ref-bob'}]}
on_leave bob
state: {}

The unknown key is skipped and bob's leave is still processed.

Actual behavior

state: {'bob': [{'presence_ref': 'ref-bob'}]}
state: {'bob': [{'presence_ref': 'ref-bob'}]}

No on_leave callback, and bob remains in presence.state indefinitely — a ghost user in the UI until a full presence_state resync happens.

System information

  • supabase-py: main @ bb7ecc5
  • Package: realtime

Metadata

Metadata

Assignees

No one assigned

    Labels

    pythonPull requests that update Python coderealtimeIssues related to realtime-py

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions