From 69ce859f552b80c58ddd7639a8fc422935fc8151 Mon Sep 17 00:00:00 2001 From: tongfengyuan <71140753@chinatelecom.cn> Date: Tue, 1 Sep 2026 22:39:30 +0800 Subject: [PATCH] fix(media): break OutboundClockSync Arc cycle leaking ~70 KB/call The OutboundClockSync RTP observer was registered on the PeerConnection holding a strong 'pc', and later a strong 'RtpSender'. Because the observer is also attached to the RTP transport (attach_registered_observers) and the sender holds that same transport, this forms the cycle: transport.observers -> OutboundClockSync -> sender -> sender.transport which reference counting never frees, retaining the RtpTransport + its IngressTap observer (~70 KB) per call. Introduced by 5f4a3106. Hold a Weak instead and upgrade on use. --- crates/rustpbx-media/src/leg.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/crates/rustpbx-media/src/leg.rs b/crates/rustpbx-media/src/leg.rs index 40f8b486e..5973eea38 100644 --- a/crates/rustpbx-media/src/leg.rs +++ b/crates/rustpbx-media/src/leg.rs @@ -353,9 +353,14 @@ impl LegInner { let was_relay = Arc::new(AtomicBool::new(false)); // Keep paced-sender RTCP SR / next-seq coherent while rewrite owns the - // shared WebRTC outbound SSRC. + // shared WebRTC outbound SSRC. Hold the audio sender as a WEAK ref so + // this observer cannot keep the PeerConnection or RtpTransport alive in + // a cycle: the observer is attached to the transport, and a strong + // sender would hold that same transport (transport -> observer -> + // sender -> transport), which reference counting never frees. + let audio_sender = audio_rtp_sender(&pc).map(|s| Arc::downgrade(&s)); pc.add_observer(Arc::new(OutboundClockSync { - pc: pc.clone(), + sender: audio_sender, relay_active: was_relay.clone(), })); @@ -985,7 +990,7 @@ fn seed_rewrite_options_from_destination( /// While rewrite owns the shared playback SSRC, feed each outbound packet into /// the paced sender so RTCP SR and the next IVR sequence stay coherent. struct OutboundClockSync { - pc: PeerConnection, + sender: Option>, relay_active: Arc, } @@ -994,7 +999,7 @@ impl rustrtc::peer_connection::RtpObserver for OutboundClockSync { if !self.relay_active.load(Ordering::SeqCst) { return; } - let Some(sender) = audio_rtp_sender(&self.pc) else { + let Some(sender) = self.sender.as_ref().and_then(|w| w.upgrade()) else { return; }; if packet.header.ssrc != sender.ssrc() {