Make nsMediaDecoder::GetBuffered() implementations threadsafe

RESOLVED FIXED in mozilla5

Status

()

defect
RESOLVED FIXED
8 years ago
8 years ago

People

(Reporter: cpearce, Assigned: cpearce)

Tracking

Trunk
mozilla5
Points:
---
Dependency tree / graph

Firefox Tracking Flags

(Not tracked)

Details

Attachments

(6 attachments)

In order to use the GetBuffered() implementations to help us decide when to pause playback in order to buffer, we need to make it threadsafe. Currently it assumes we only call GetBuffered() from the main thread, but it needs to run on the state machine and decode threads for the patches in bug 628665 to work correctly.
To make GetBuffered() threadsafe, we need to ensure that we pin the media stream while determining which ranges are buffered and also while using said ranges. We also need to ensure that new data isn't added to the cache while we're determining what ranges are buffered. We determine what ranges are buffered by looping and calling nsMediaCacheStream::GetNextCachedData() and GetCachedDataEnd(). But if we're unlucky and the range we're on increases in size, we could end up creating a new range for every block of data received. So we need to lock the media cache while determining what byte ranges are buffered.

So I propose we factor out the GetNextCachedData()/GetCachedDataEnd() loop pattern we use in all our GetBuffered() implementations, and put that into nsMediaStream, having it fill a vector of ByteRanges while holding the media cache lock. Outside of the lock (in the GetBuffered()) implementations, we can use the vector of ByteRanges to determine their start and end times, without needing to hold the media cache lock.
OK but we will need to protect other structures too, right?

GetBuffered() is only called while we're in the buffering state, right? Maybe we could simply proxy the call to the main thread via an event.
Yes, the readers need some changes to make them threadsafe too.

We also use GetBuffered() on the decoder thread in HasLowUndecodedData() (once bug bug 628665 lands) to determine whether we should skip-to-keyframe. We also use it to test whether we should go into buffering (but only if HasLowDecodedData() is true). So proxying to the main thread doesn't seem like a good idea.
nsBuiltinDecoderReader contains GetBufferedBytes() and GetSeekRange() functions and a ByteRange class which are only used in nsOggReader for seeking. ByteRange also stores the exact time at the start and end of its byte range. GetBufferedBytes() finds the exact start and end times ranges contain by decoding data, whereas nsOggReader::GetBuffered() finds approximate times using the container format, which is quicker, but not accurate enough to be used during seeking.

Since this stuff is Ogg specific, push it down into nsOggReader, and rename ByteRange to SeekRange, since it represents more than just a range of bytes.

Based on top of patches from bug 580531, bug 638617, and bug 628665.
Attachment #520039 - Flags: review?(roc)
Add a GetCachedRanges(nsTArray<nsByteRange>&) function to nsMediaStream, which fills an array with the byte ranges which are cached in a threadsafe manner (it holds the media cache lock while running, so data can't be added or removed from the cache while running). Add a nsByteRange class in nsMediaStream, which represents a [start,end] byte range. We can then ask the nsMediaStream what ranges it has cached, use the cached ranges to determine the times stored in those ranges, and we won't notice (or be affected or care) if the ranges grow while we're determining the times they contain. Provided we pin the nsMediaStream while using the ranges, the ranges won't shrink either.

Based on top of patches from bug 580531, bug 638617, and bug 628665.
Assignee

Updated

8 years ago
Attachment #520040 - Flags: review?(roc)
Ensure that while we call nsBuiltinDecoderReader::GetBuffered() implementations, we've pinned the media stream. This ensures that data isn't evicted while we're determining the times that cached ranges contain.

Based on top of patches from bug 580531, bug 638617, and bug 628665.
Attachment #520041 - Flags: review?(roc)
Use the nsMediaStream's cached ranges to determine buffered time ranges.

Based on top of patches from bug 580531, bug 638617, and bug 628665.
Attachment #520042 - Flags: review?(roc)
Use the nsMediaStream's cached ranges to determine buffered time ranges. Use a monitor to protect access to the byte range to time mapping in nsWebMBufferedState, to make it threadsafe.

Based on top of patches from bug 580531, bug 638617, and bug 628665.
Attachment #520044 - Flags: review?(kinetik)
Use the nsMediaStream's cached ranges to determine buffered time ranges in the new nsWaveReader which uses the nsBuilinDecoder framework (bug 635649).

Based on top of patches from bug 580531, bug 638617, bug 628665 _and bug 635649_.
Attachment #520045 - Flags: review?(kinetik)
Comment on attachment 520042 [details] [diff] [review]
Patch 4: Ensure Ogg GetBuffered() is threadsafe

Now that nsOggReader::GetSeekRanges calls GetCachedRanges, what ensures that the stream is pinned during GetSeekRanges?
(In reply to comment #10)
> Comment on attachment 520042 [details] [diff] [review]
> Patch 4: Ensure Ogg GetBuffered() is threadsafe
> 
> Now that nsOggReader::GetSeekRanges calls GetCachedRanges, what ensures that
> the stream is pinned during GetSeekRanges?

That's called only while we're seeking, so we should be pinned already. GetCachedRanges() asserts that it's pinned, so we'll hear about it if the stream is not pinned.
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");

This is already asserted higher in the call chain.
Attachment #520045 - Flags: review?(kinetik) → review+
Attachment #520044 - Flags: review?(kinetik) → review+
Assignee

Updated

8 years ago
Whiteboard: [needs landing post ff4]
Assignee

Updated

8 years ago
Depends on: post2.0
Can someone confirm if this is fixed?

Updated

8 years ago
Depends on: 629705
Depends on: 654371
You need to log in before you can comment on or make changes to this bug.