- From: Henrik Boström <hbos@google.com>
- Date: Fri, 22 Feb 2019 17:18:10 +0100
- To: Jan-Ivar Bruaroey <jib@mozilla.com>
- Cc: public-webrtc@w3.org
- Message-ID: <CAEbRw2wvoE9QNqNeWYRsxRCQT1dMtsuP83wC9YCYomT4A3esgw@mail.gmail.com>
Based on this thread, the issue and PR discussions as well as editor meetings, we have consensus to remove stopped transceivers immediately. I will update the PR. On Thu, Feb 21, 2019 at 3:52 PM Jan-Ivar Bruaroey <jib@mozilla.com> wrote: > On 2/15/19 5:03 PM, Henrik Boström wrote: > > As always, this is more complicated than I expected. > > On Fri, Feb 15, 2019 at 6:17 PM youenn fablet <yfablet@apple.com> wrote: > >> On Feb 15, 2019, at 3:02 AM, Henrik Boström <hbos@google.com> wrote: >> ... This prevents the transceivers, and thus the associated >> senders/receivers and tracks, from being garbage collected. A stress test >> running Chrome that did add/removeTrack a lot ran out of memory when we >> switched from Plan B to Unified Plan due to this resulting in a few >> thousand transceivers in less than a minute. (I don't know the true >> footprint, but even if each transceiver just has a single video frame in >> its buffer of 1280x720x4, that's several megabytes, multiplied by thousands >> of transceivers => gigabytes of data). >> >> >> I agree with the concern. >> That said, implementations could probably do some clean-up when a >> transceiver is stopped so that it becomes very lightweight. >> Is there anything preventing implementations to do so? >> > > Good point. The transceiver is irreversibly stopped, and its track is > irreversibly ended. I think it may be lightweight, but only if we don't > have to store the a frame on the ended track. > What happens if you attach an ended track to a video element? Does it show > an the last received frame or no frame (black)? > > > https://jsfiddle.net/jib1/4cxpLu08/ shows neither Chrome nor Firefox show > a frame today on re-attach, but see > https://github.com/w3c/mediacapture-main/issues/555 for a discussion that > might affect that. > > > This may be a central question to the importance or non-importance of the > resource issue (and whether the OOM was a Chrome bug or a spec concern). > > > On Fri, Feb 15, 2019 at 7:06 PM Jan-Ivar Bruaroey <jib@mozilla.com> wrote: > >> Thanks Henrik for a good summary! I agree we should do something, but I'd >> caution against big changes. Comments inline. >> >> On 2/15/19 6:02 AM, Henrik Boström wrote: >> >> ... TL;DR: Keeping transceivers around prevents garbage collection of a >> possibly ever-growing list of tracks. Can we stop doing that? >> >> If an application does addTrack() and removeTrack() with renegotiations >> in-between, the list of m= sections and transceivers will grow over time. >> >> This will remain true. Developers who operate with the >> addTrack()/removeTrack() symmetric mental model, will continue to >> experience this side-effect. transceiver.stop() does not rescue that mental >> model, since it stops both directions. >> >> So the short answer to your "Can we stop doing that?" question is >> "probably not". >> > > Good point. I don't think we'll fix the problem for people who have the > wrong mental model. Stopping a transceiver would have to be a conscious > act, where you don't stop a transceiver that may be used for receiving. > > That said, I agree we should fix the main side-effect of >> transceiver.stop() you point out: that it holds on to objects long after >> the underlying machinery it's supposed to represent (the network >> negotiation slot) has been repurposed. Transceivers were meant to be true >> to the underlying behaviors, and this fails that. >> >> ... Related issues/questions: >> >> 1. If you care about mids of stopped transceivers you'd have to >> record them yourself. >> 2. There is no "onstopped" event in case a transceiver is stopped due >> to setRemoteDescription(). Should we add one, or is it enough that the >> application checks whether the transceiver still exists by querying >> getTransceivers() in track.onmute? >> >> `mute` only fires remotely AFAIK. However, transceiver.stop() [1] does >> reliably fire `ended` on transceiver.receiver.track on both ends. [2] But >> in neither case are you given a pointer to the transceiver. >> > Yes, "onended" is the one we want not "onmute". Do you need the > transceiver pointer though? When you add a listener to "onended" you know > the transceiver already (or you can look it up in the pc using the track). > You can do this: > > function onstoppedPromise(pc, transceiver) { > return new Promise(resolve => { > transceiver.receiver.track.onended => { > resolve(transceiver); > } > }); > } > pc.ontrack = e => > onstoppedPromise(e.transceiver).then(processEndedTransceiver); > > That works! > > Please discuss. >> >> I think you hit on the most confusing situation: where the other side >> stopped your transceiver. >> >> The current spec API for this is to examine the boolean >> `transceiver.stopped`, once you return to "stable". I think that works, has >> a couple of years of this working group behind it, Firefox implements it, >> and it has neither of the issues you mention. >> >> With the change you propose, the transceiver would effectively disappear >> from all lists we maintain for developers, before developers get a chance >> to react. Finding out which transceiver(s) were stopped might be hard. >> Ironically, the `transceiver.stopped` API would still exist, but developers >> would have to cache the list ahead of time to use it. >> > How about instead: >> >> - we remove the stopped transceiver on subsequent renegotiation, when >> its m-line is reused?—If we're clever, we may even be able to insert the >> new transceiver that inherits the m-line, in its place, preserving the >> index order of the other transceivers in getTransceivers(). >> >> Thoughts? >> > I see the benefit of keeping the transceiver around during negotiation; it > might be nice to have an easily accessible list of transceivers that is > complete and comparable to the SDP, including stopped ones. > > This is where it gets hairy: > > *If we're clever, we may even be able to insert the new transceiver > that inherits the m-line, in its place, preserving the index order of the > other transceivers in getTransceivers()* > The "new transceiver" might not be newly created, it might be a > transceiver that was created (at index *n*) and is now tied to a previous > m-line, say index *i*. Do we move the transceiver? > > > Good point. m-lines become eligible for re-use on transition to "stable" > state, but there's a window between then and the previous "stable" state > where if addTransceiver() were to be called would create transceivers that > should get first dibs on these m-lines. > > What if we instead removed stopped transceivers in > addTransceiver()/addTack()? I.e. we'd "overwrite" stopped transceivers with > new ones, mimicking what should happen at the m-line level once we > renegotiate. > > This would seem to preserve indices, IF we want to go that way. I do worry > about it not being POLA though. > > A lot boils down to what we want to be true about transceiver indices: > what do we want to be true? > > I believe the statement "the transceiver index is the SDP m-line index" is *not > true* when both endpoints add transceivers before negotiating, and > possibly other edge cases. What *is true* is "the transceivers are listed > in insertion order". If you add in-place insertion of recycled m-sections, > or move transceivers around, you would sacrifice what was always true > (insertion order) in order to maintain support for what was sometimes true > but not always (transceiver index matches SDP). I'm not sure maintaining > this illusion is worth the complexity, especially when mid is what you > *should* use when comparing SDP or the other endpoint's set of > transceivers. > > Here's a third option: > > - Stopping a transceiver does not immediately remove it from the set > of transceivers, but when the signaling state goes from any non-stable > state to stable, all stopped transceivers are removed. > > It doesn't try to make the index match (if you can't guarantee, don't try) > but it maintains insertion order, and stopping a transceiver with stop() or > setRemoteDescription(offer) does not immediately make it disappear. But if > you are the offerer, transceivers stopped by the answer would disappear > when you do setRemoteDescription(answer). > > Yeah so this would explicitly not solve the case I think would be most > surprising, where the other side stopped us. > > We should probably just pick what's simpler. > .: Jan-Ivar :. > > Is that better or worse? > >> .: Jan-Ivar :. >> >> [1] http://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-stop >> [2] https://blog.mozilla.org/webrtc/rtcrtptransceiver-explored/ >> >
Received on Friday, 22 February 2019 16:18:45 UTC