-
Notifications
You must be signed in to change notification settings - Fork 51
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Don't retain Receiver Report loss info when stats are disabled #613
Conversation
This prevents unbounded memory growth of the Vec that holds onto packet loss records derived from RTCP Receiver Reports, if the method that would drain that Vec will never get called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for tackling this!
I left an idea on how I think this could be solved in a more type-safe manner. Before you tackle it, let's hear what @algesten thinks of it :)
@@ -153,7 +153,7 @@ pub(crate) struct StreamTxStats { | |||
/// Can be null in case of missing or bad reports | |||
rtt: Option<f32>, | |||
/// losses collecter from RR (known packets, lost ratio) | |||
losses: Vec<(u64, f32)>, | |||
losses: Option<Vec<(u64, f32)>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I would suggest is, to make a newtype called Losses
that has two constructors:
new
no_op
/dummy
etc
We then pass Losses
as a parameter through all layers that don't yet know whether capturing Losses
will be required. At the point where we have the config available, we can then decide which constructor to call.
This newtype can also encapsulate some of the functionality on how the data is actually processed and inserted, i.e. provide a more specific interface for the other components to interact with.
If you wanted to go full on type-safe, we could also define Losses
as an enum such that in the no-op mode, there isn't even an underlying data-structure to operate on / fill.
}; | ||
if let Some(losses) = self.losses.as_mut() { | ||
let ext_seq = { | ||
let prev = losses.last().map(|s| s.0).unwrap_or(r.max_seq as u64); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All this logic could go onto the Losses
newtype.
} | ||
|
||
pub(crate) fn fill(&mut self, snapshot: &mut StatsSnapshot, midrid: MidRid, now: Instant) { | ||
if self.bytes == 0 { | ||
return; | ||
} | ||
|
||
let loss = { | ||
let loss = self.losses.as_mut().and_then(|losses| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar here, this computation can be moved onto the Losses
newtype.
Fixes #610
StreamTxStats
cannot beOption
'd out entirely if stats reports are disabled, because it's still used for other purposes, e.g. RTX ratio calculation. Trying to split it out into a stats-reports and non-stats-reports portion was also awkward, due to overlap.Instead, we give it a hint that
fill()
will never be called, which will prevent it from creating alosses
array at all. Fortunately, bad/missing loss data was already accounted for, so even if it does end up being called, the behavior will still be sane (i.e. no panics, no bogus data).I also considered a solution that would just put an upper bound on the
losses
array instead, but that had a few downsides: