Fix transceiver reusing problem in single-pc mode - #1151
Conversation
Sfu could send empty transceiver to subscriber in single pc mode when publisher unbpulishes track before answer sdp is created. The empty transceiver can be reused in later subscription that changes msid in renegotiation, libwebrtc will not fire onTrack event in the msid changed case cause subscrption failure. Chrome detects the msid change and synthsize onTrack event to handle this case. This PR do the same thing as browser.
ChangesetThe following package versions will be affected by this PR:
|
libwebrtc is typically compiled with `-fno-exceptions`. In the C++ shim we
were calling `transceiver_->mid().value()` on an `std::optional<std::string>`
— which on an empty optional invokes `std::__throw_bad_optional_access()`,
which without exception support `abort()`s the process. The Rust binding
expected this call to return `Err` on no-mid, but the abort happens before
cxx-bridge can translate the throw to an `Err`.
This was exposed by the new `process_remote_track_addition` pass walking
all transceivers after every `setRemoteDescription`: a transceiver freshly
created by a local `AddTrack` is in `pc.transceivers()` but doesn't have
a mid assigned yet until the negotiation completes. The diff iterated to
it, called `mid()`, and the process SIGABRTed.
Fix:
- C++ shim: use `.value_or("")` instead of `.value()`, returning an empty
string when no mid is set.
- Rust binding: treat empty string as `None` so the public API contract
(`mid() -> Option<String>`) is unchanged.
Reproduced deterministically on x86_64 Linux with
`test_v1_publish_ten_video_and_ten_audio_tracks` against a single-PC SFU.
Without this fix the test SIGABRTs on the second negotiation round after
the first track publish. With this fix the test passes consistently.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
| } | ||
|
|
||
| let mut dispatched = self.dispatched_streams.lock(); | ||
| dispatched.retain(|key| current.contains_key(key)); |
There was a problem hiding this comment.
any chance that (mid, stream_id) disappears for one SDP update but later reappears again ?
if this will happen, the current code will dispatch MediaTrack again. Is this intentional behavior ?
There was a problem hiding this comment.
It is intentional, in case like:
- client subscribe, sfu sdp: mid:1, streamid_id: stream_A
- client unsubscribe, sfu sdp: mid:1, no stream
- client resubscribe after awhile, sfu reuse mid:1 to send track and client dispatch the track again.
There was a problem hiding this comment.
just want to double check, at any condition should we clear this dispatched_streams so that we will dipspatch the tracks again ?
dispatched.retain(|key| current.contains_key(key)); here will also remove all keys don't exist in current streams, so we can dispatch it again when the track has been resubscribed.
| let stream = streams.remove(0); | ||
| let mid = transceiver.mid().unwrap_or_default(); | ||
| let already_dispatched = !mid.is_empty() | ||
| && !self.dispatched_streams.lock().insert((mid, stream.id())); |
There was a problem hiding this comment.
just want to double check, at any condition should we clear this dispatched_streams so that we will dipspatch the tracks again ?
Sfu could send empty transceiver to subscriber in single pc
mode when publisher unbpulishes track before answer sdp is
created. The empty transceiver can be reused in later subscription
that changes msid in renegotiation, libwebrtc will not fire
onTrack event in the msid changed case cause subscrption failure.
Chrome detects the msid change and synthsize onTrack event to handle
this case. This PR do the same thing as browser.