In peer-routing mode installControlSession() returns early when the
transport does not implement transport.ControlPlane (e.g. datachannel),
leaving acceptHandshake() never called and s.sessionID never set.
servePeer() then spin-waits forever for a sessionID that never arrives,
so the client never receives the SERVER_WELCOME frame.
Mirror the legacy path from waitHandshake/serveSingle: if controlConn
is nil, drive acceptHandshake directly on the per-peer smux session
before entering the AcceptStream loop.
xmppKeepalive was using fire-and-forget Send(); on a half-open TCP
connection pings were sent but the missing reply went undetected,
leaving the session stuck. Switch to SendIQWait so a timeout triggers
a reconnect.
In peer-routing mode the server may send the initial welcome frame as a
broadcast (receiverEpoch=0) before it has learned the client's
localEpoch. The previous requireTargetedPeer guard dropped every
broadcast unconditionally. Tighten the guard: only drop frames whose
senderEpoch is unknown or belongs to a different peer; frames from the
already-latched peer are always accepted.
inject a clean VP8 keyframe every ~2s during bulk so the SFU keeps
forwarding the track instead of stalling after its decode timeout.
add RTCP interceptors and IPv4-only ICE, priority control/data mux,
publisher PC reconnect with stable control epoch, and longer smux
keepalive/liveness windows to survive the reconnect gap
Video-paced transports (vp8channel, videochannel, seichannel) need
longer operation timeouts due to frame pacing and KCP batching:
- streamPatternForDuration: 15s -> 60s for video transports
- sustainedEcho: 5s -> 60s for video transports
Part of fix for issue #95.
Related to #95
After a session reinstall, the old client's control loop may have
already written a CONTROL_PING frame to the link before its context
was cancelled. This frame arrives at the server's new smux session and
is routed to the handshake stream (matching stream ID), where the
framing layer interprets the JSON bytes as an impossibly large length
prefix ('{\x22ve' = 2065856101 > 65536).
Previously this triggered a full reinstall, which could cascade into
a reconnect storm. Now acceptHandshake and acceptPeerHandshake
recognize ErrFrameTooLarge as a stale-frame artifact: they close the
polluted stream and retry AcceptStream up to 3 times, giving the real
CLIENT_HELLO a chance to arrive on the next stream.
The lightweight soft-rejoin path (jSess.Rejoin) skips Jicofo focus
allocation and joins the MUC with a bare presence. After the client
leaves and Jicofo idle-terminates the now-empty conference
(session-terminate <expired/>), the room/focus is torn down. A bare
rejoin presence is then rejected by Prosody with
<presence type='error'><not-allowed/>, and the library's JoinMUC matches
a stale status-110 left in its stanza buffer and falsely reports success.
The engine then waits forever for a session-initiate that never arrives
while actually being outside the room, so the client can never reconnect.
Re-establish the session from scratch via j.JoinMUC instead, which runs
dial -> focus allocation -> MUC join in the correct order (focus first,
so Jicofo recreates the room), exactly like the initial Connect, but
WITHOUT blocking on session-initiate. The fresh session-initiate is
awaited separately via WaitJingleReinitiate once a peer rejoins, so the
non-blocking reconnect contract is preserved.
Verified on a live deployment: two consecutive reconnect cycles now
complete (bridge open sctp -> reconnected -> session opened) where the
old path hung after "waiting for session-initiate".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
j.Join() blocks on WaitJingle until Jicofo sends session-initiate,
which only happens when a second participant joins the room. With the
transport-level connect timeout (30-60s), the server crashes if no one
joins in time.
Switch to j.JoinMUC() which returns immediately after joining the MUC.
A background goroutine (waitForJingle) waits for session-initiate and
then opens the bridge + negotiates the PeerConnection. This way Connect
succeeds as soon as the XMPP connection is established, regardless of
whether another participant is present.