Skip to content

Events emitted while engine.pendingReconnect is still true after Reconnected stay in bufferedEvents until the next reconnect #2183

Description

@yuandao2021

Describe the bug

After a full reconnect (restartConnection), the Room can become connected and emit RoomEvent.Reconnected while engine.pendingReconnect is still true. Events emitted through emitWhenConnected in that window are pushed to room.bufferedEvents, but the buffer is only flushed on the next Reconnected/Resumed. If no further reconnect happens, the events are never delivered (and a later reconnect clears the buffer).

Sequence we observe:

  1. restartConnection arms engine.reconnectTimeout and clears it only in the .finally of the whole attempt, so engine.pendingReconnect stays true until the attempt fully settles.
  2. Room.handleSignalRestarted awaits engine.waitForRestarted(), which resolves immediately if pcState is already Connected. The room sets state connected, flushes bufferedEvents and emits Reconnected before the engine attempt has finished.
  3. In the window "room connected, engine.pendingReconnect === true" (1.2–1.8 s in our measurements), emitWhenConnected takes the buffering branch:
    if (this.state === ConnectionState.Reconnecting || this.isResuming || !this.engine || this.engine.pendingReconnect) {
      this.bufferedEvents.push([event, args]);
    }
  4. Nothing flushes the buffer afterwards. ParticipantConnected, TrackPublished, TrackSubscribed for a remote participant who (re)joins in that window are lost to the application: useTracks-style UIs show the participant without video, or not at all.

Reproduction

  1. Two participants A and B in a room.
  2. Force a full reconnect on A (e.g. room.engine.client.close()/signal drop leading to restartConnection, not a resume).
  3. While A is reconnecting, have B leave and rejoin (or publish a new camera track) so that B's events reach A within ~1–2 s after A's Reconnected.
  4. On A: room.bufferedEvents.length > 0 stays non-zero indefinitely; no TrackSubscribed for B's new track is emitted, although room.remoteParticipants.get(B).trackPublications contains the subscribed track.

Logs

System Info

**livekit-client version:** 2.22.1 (code path unchanged in 2.23.0)
**Browser:** Chrome 13x (headless and desktop), reproducible deterministically on a local stand

Severity

annoyance

Additional Information

Expected behavior

Either Reconnected is emitted only after the engine attempt has fully settled (pendingReconnect === false), or the buffer is flushed when pendingReconnect becomes false while the room is connected.

Workaround

We poll every 500 ms: when room.state === 'connected' && !room.isResuming && !engine.pendingReconnect && !engine.attemptingReconnect && room.bufferedEvents.length > 0, we re-emit the buffered events ourselves (dropping events for participants/publications/tracks that no longer exist) and clear the buffer. With this workaround the lost-tile symptom disappears in our end-to-end test (rejoin-peer-video).

Activity

  1. AiLaoHuYu commented on Oct 26, 2026

    @AiLaoHuYu

    One acceptance criterion I would add is that Reconnected should not be treated as a recovery commit point until the engine attempt has settled. After Reconnected, wait for pendingReconnect === false, then require both the expected TrackSubscribed event and an advancing framesDecoded counter for that publication. Without the second check, fixing the event buffer can still leave a connected-but-frozen tile.

    A compact regression could have participant B leave and republish 200–600 ms after A receives Reconnected. Fail if the event stays buffered, fires more than once on the next resume, or the remote frame counter does not advance within the agreed deadline.

    Commercial disclosure: I maintain a WebRTC source kit and work on recovery testing. This comment is about the recovery success predicate, not about changing stacks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions