Skip to content

Commit f35dc4e

Browse files
committed
Handle nil stats getter in collect stats
It is possible to call GetStats() after the peer connection is closed. The statsGetter gets cleared when peer connection is closed. That is causing a panic when RtpReceiver.collectStats runs.
1 parent caef6a9 commit f35dc4e

File tree

2 files changed

+48
-30
lines changed

2 files changed

+48
-30
lines changed

rtpreceiver.go

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,10 @@ func (r *RTPReceiver) Stop() error { //nolint:cyclop
404404
}
405405

406406
func (r *RTPReceiver) collectStats(collector *statsReportCollector, statsGetter stats.Getter) {
407+
if statsGetter == nil {
408+
return
409+
}
410+
407411
r.mu.Lock()
408412
defer r.mu.Unlock()
409413

@@ -438,32 +442,7 @@ func (r *RTPReceiver) collectStats(collector *statsReportCollector, statsGetter
438442
TransportID: "iceTransport",
439443
CodecID: codecID,
440444
}
441-
442-
stats := statsGetter.Get(uint32(remoteTrack.SSRC()))
443-
if stats != nil { //nolint:nestif // nested to keep mapping local
444-
// Wrap-around casting by design, with warnings if overflow/underflow is detected.
445-
pr := stats.InboundRTPStreamStats.PacketsReceived
446-
if pr > math.MaxUint32 {
447-
r.log.Warnf("Inbound PacketsReceived exceeds uint32 and will wrap: %d", pr)
448-
}
449-
inboundStats.PacketsReceived = uint32(pr) //nolint:gosec
450-
451-
pl := stats.InboundRTPStreamStats.PacketsLost
452-
if pl > math.MaxInt32 || pl < math.MinInt32 {
453-
r.log.Warnf("Inbound PacketsLost exceeds int32 range and will wrap: %d", pl)
454-
}
455-
inboundStats.PacketsLost = int32(pl) //nolint:gosec
456-
457-
inboundStats.Jitter = stats.InboundRTPStreamStats.Jitter
458-
inboundStats.BytesReceived = stats.InboundRTPStreamStats.BytesReceived
459-
inboundStats.HeaderBytesReceived = stats.InboundRTPStreamStats.HeaderBytesReceived
460-
timestamp := stats.InboundRTPStreamStats.LastPacketReceivedTimestamp
461-
inboundStats.LastPacketReceivedTimestamp = StatsTimestamp(
462-
timestamp.UnixNano() / int64(time.Millisecond))
463-
inboundStats.FIRCount = stats.InboundRTPStreamStats.FIRCount
464-
inboundStats.PLICount = stats.InboundRTPStreamStats.PLICount
465-
inboundStats.NACKCount = stats.InboundRTPStreamStats.NACKCount
466-
}
445+
r.populateInboundStats(&inboundStats, statsGetter, remoteTrack)
467446

468447
collector.Collect(inboundID, inboundStats)
469448

@@ -473,6 +452,40 @@ func (r *RTPReceiver) collectStats(collector *statsReportCollector, statsGetter
473452
}
474453
}
475454

455+
func (r *RTPReceiver) populateInboundStats(
456+
inboundStats *InboundRTPStreamStats,
457+
statsGetter stats.Getter,
458+
remoteTrack *TrackRemote,
459+
) {
460+
stats := statsGetter.Get(uint32(remoteTrack.SSRC()))
461+
if stats == nil {
462+
return
463+
}
464+
465+
// Wrap-around casting by design, with warnings if overflow/underflow is detected.
466+
pr := stats.InboundRTPStreamStats.PacketsReceived
467+
if pr > math.MaxUint32 {
468+
r.log.Warnf("Inbound PacketsReceived exceeds uint32 and will wrap: %d", pr)
469+
}
470+
inboundStats.PacketsReceived = uint32(pr) //nolint:gosec
471+
472+
pl := stats.InboundRTPStreamStats.PacketsLost
473+
if pl > math.MaxInt32 || pl < math.MinInt32 {
474+
r.log.Warnf("Inbound PacketsLost exceeds int32 range and will wrap: %d", pl)
475+
}
476+
inboundStats.PacketsLost = int32(pl) //nolint:gosec
477+
478+
inboundStats.Jitter = stats.InboundRTPStreamStats.Jitter
479+
inboundStats.BytesReceived = stats.InboundRTPStreamStats.BytesReceived
480+
inboundStats.HeaderBytesReceived = stats.InboundRTPStreamStats.HeaderBytesReceived
481+
timestamp := stats.InboundRTPStreamStats.LastPacketReceivedTimestamp
482+
inboundStats.LastPacketReceivedTimestamp = StatsTimestamp(
483+
timestamp.UnixNano() / int64(time.Millisecond))
484+
inboundStats.FIRCount = stats.InboundRTPStreamStats.FIRCount
485+
inboundStats.PLICount = stats.InboundRTPStreamStats.PLICount
486+
inboundStats.NACKCount = stats.InboundRTPStreamStats.NACKCount
487+
}
488+
476489
func (r *RTPReceiver) collectAudioPlayoutStats(
477490
collector *statsReportCollector,
478491
nowTime time.Time,

rtpreceiver_test.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,19 +100,24 @@ func TestRTPReceiver_CollectStats_Mapping(t *testing.T) {
100100
}}
101101

102102
// Minimal RTPReceiver with one track
103-
r := &RTPReceiver{
103+
receiver := &RTPReceiver{
104104
kind: RTPCodecTypeVideo,
105105
log: logging.NewDefaultLoggerFactory().NewLogger("RTPReceiverTest"),
106106
}
107-
tr := newTrackRemote(RTPCodecTypeVideo, ssrc, 0, "", r)
108-
r.tracks = []trackStreams{{track: tr}}
107+
tr := newTrackRemote(RTPCodecTypeVideo, ssrc, 0, "", receiver)
108+
receiver.tracks = []trackStreams{{track: tr}}
109109

110110
collector := newStatsReportCollector()
111-
r.collectStats(collector, fg)
111+
receiver.collectStats(collector, nil)
112112
report := collector.Ready()
113113

114114
// Fetch the generated inbound-rtp stat by ID
115115
statID := "inbound-rtp-1234"
116+
_, ok := report[statID]
117+
require.False(t, ok, "unexpected inbound stat")
118+
119+
receiver.collectStats(collector, fg)
120+
report = collector.Ready()
116121
got, ok := report[statID]
117122
require.True(t, ok, "missing inbound stat")
118123

0 commit comments

Comments
 (0)