Bug 712836. Make MediaResources which have ended abnormally ineligible for cloning. r=cpearce,a=jpr
authorRobert O'Callahan <robert@ocallahan.org>
Tue, 20 Mar 2012 20:55:40 +1300
changeset 90503 f11042d416a09cddba479dc265dc8885250d5570
parent 90502 ed87206d099a12c8030c19270b6f415d1c38fe4a
child 90504 3d220e21f6f74a0cde6b4086dde1bdc242ec24b2
push id1005
push userrocallahan@mozilla.com
push dateThu, 22 Mar 2012 01:30:17 +0000
treeherdermozilla-aurora@f11042d416a0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce, jpr
bugs712836
milestone13.0a2
Bug 712836. Make MediaResources which have ended abnormally ineligible for cloning. r=cpearce,a=jpr
content/html/content/src/nsHTMLMediaElement.cpp
content/media/MediaResource.cpp
content/media/MediaResource.h
content/media/nsMediaCache.cpp
content/media/nsMediaCache.h
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -1405,17 +1405,20 @@ nsHTMLMediaElement::LookupMediaElementUR
   for (PRUint32 i = 0; i < entry->mElements.Length(); ++i) {
     nsHTMLMediaElement* elem = entry->mElements[i];
     bool equal;
     // Look for elements that have the same principal and CORS mode.
     // Ditto for anything else that could cause us to send different headers.
     if (NS_SUCCEEDED(elem->NodePrincipal()->Equals(NodePrincipal(), &equal)) && equal &&
         elem->mCORSMode == mCORSMode) {
       NS_ASSERTION(elem->mDecoder && elem->mDecoder->GetResource(), "Decoder gone");
-      return elem;
+      MediaResource* resource = elem->mDecoder->GetResource();
+      if (resource->CanClone()) {
+        return elem;
+      }
     }
   }
   return nsnull;
 }
 
 nsHTMLMediaElement::nsHTMLMediaElement(already_AddRefed<nsINodeInfo> aNodeInfo)
   : nsGenericHTMLElement(aNodeInfo),
     mCurrentLoadID(0),
--- a/content/media/MediaResource.cpp
+++ b/content/media/MediaResource.cpp
@@ -525,19 +525,25 @@ nsresult ChannelMediaResource::Close()
 already_AddRefed<nsIPrincipal> ChannelMediaResource::GetCurrentPrincipal()
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
   nsCOMPtr<nsIPrincipal> principal = mCacheStream.GetCurrentPrincipal();
   return principal.forget();
 }
 
+bool ChannelMediaResource::CanClone()
+{
+  return mCacheStream.IsAvailableForSharing();
+}
+
 MediaResource* ChannelMediaResource::CloneData(nsMediaDecoder* aDecoder)
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+  NS_ASSERTION(mCacheStream.IsAvailableForSharing(), "Stream can't be cloned");
 
   ChannelMediaResource* resource = new ChannelMediaResource(aDecoder, nsnull, mURI);
   if (resource) {
     // Initially the clone is treated as suspended by the cache, because
     // we don't have a channel. If the cache needs to read data from the clone
     // it will call CacheClientResume (or CacheClientSeek with aResume true)
     // which will recreate the channel. This way, if all of the media data
     // is already in the cache we don't create an unneccesary HTTP channel
@@ -905,16 +911,17 @@ public:
   }
 
   // Main thread
   virtual nsresult Open(nsIStreamListener** aStreamListener);
   virtual nsresult Close();
   virtual void     Suspend(bool aCloseImmediately) {}
   virtual void     Resume() {}
   virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
+  virtual bool     CanClone();
   virtual MediaResource* CloneData(nsMediaDecoder* aDecoder);
   virtual nsresult ReadFromCache(char* aBuffer, PRInt64 aOffset, PRUint32 aCount);
 
   // These methods are called off the main thread.
 
   // Other thread
   virtual void     SetReadMode(nsMediaCacheStream::ReadMode aMode) {}
   virtual void     SetPlaybackRate(PRUint32 aBytesPerSecond) {}
@@ -1082,16 +1089,21 @@ already_AddRefed<nsIPrincipal> FileMedia
   nsCOMPtr<nsIPrincipal> principal;
   nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
   if (!secMan || !mChannel)
     return nsnull;
   secMan->GetChannelPrincipal(mChannel, getter_AddRefs(principal));
   return principal.forget();
 }
 
+bool FileMediaResource::CanClone()
+{
+  return true;
+}
+
 MediaResource* FileMediaResource::CloneData(nsMediaDecoder* aDecoder)
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
   nsHTMLMediaElement* element = aDecoder->GetMediaElement();
   if (!element) {
     // The decoder is being shut down, so we can't clone
     return nsnull;
--- a/content/media/MediaResource.h
+++ b/content/media/MediaResource.h
@@ -186,16 +186,22 @@ public:
   // since we don't expect to resume again any time soon. Otherwise we
   // may resume again soon so resources should be held for a little
   // while.
   virtual void Suspend(bool aCloseImmediately) = 0;
   // Resume any downloads that have been suspended.
   virtual void Resume() = 0;
   // Get the current principal for the channel
   virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal() = 0;
+  // If this returns false, then we shouldn't try to clone this MediaResource
+  // because its underlying resources are not suitable for reuse (e.g.
+  // because the underlying connection has been lost, or this resource
+  // just can't be safely cloned). If this returns true, CloneData could
+  // still fail. If this returns false, CloneData should not be called.
+  virtual bool CanClone() { return false; }
   // Create a new stream of the same type that refers to the same URI
   // with a new channel. Any cached data associated with the original
   // stream should be accessible in the new stream too.
   virtual MediaResource* CloneData(nsMediaDecoder* aDecoder) = 0;
 
   // These methods are called off the main thread.
   // The mode is initially MODE_PLAYBACK.
   virtual void SetReadMode(nsMediaCacheStream::ReadMode aMode) = 0;
@@ -386,16 +392,17 @@ public:
   // Main thread
   virtual nsresult Open(nsIStreamListener** aStreamListener);
   virtual nsresult Close();
   virtual void     Suspend(bool aCloseImmediately);
   virtual void     Resume();
   virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
   // Return true if the stream has been closed.
   bool IsClosed() const { return mCacheStream.IsClosed(); }
+  virtual bool     CanClone();
   virtual MediaResource* CloneData(nsMediaDecoder* aDecoder);
   virtual nsresult ReadFromCache(char* aBuffer, PRInt64 aOffset, PRUint32 aCount);
   virtual void     EnsureCacheUpToDate();
 
   // Other thread
   virtual void     SetReadMode(nsMediaCacheStream::ReadMode aMode);
   virtual void     SetPlaybackRate(PRUint32 aBytesPerSecond);
   virtual nsresult Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes);
--- a/content/media/nsMediaCache.cpp
+++ b/content/media/nsMediaCache.cpp
@@ -173,16 +173,22 @@ public:
   // This can return partial reads.
   nsresult ReadCacheFile(PRInt64 aOffset, void* aData, PRInt32 aLength,
                          PRInt32* aBytes);
   // This will fail if all aLength bytes are not read
   nsresult ReadCacheFileAllBytes(PRInt64 aOffset, void* aData, PRInt32 aLength);
   // This will fail if all aLength bytes are not written
   nsresult WriteCacheFile(PRInt64 aOffset, const void* aData, PRInt32 aLength);
 
+  PRInt64 AllocateResourceID()
+  {
+    mReentrantMonitor.AssertCurrentThreadIn();
+    return mNextResourceID++;
+  }
+
   // mReentrantMonitor must be held, called on main thread.
   // These methods are used by the stream to set up and tear down streams,
   // and to handle reads and writes.
   // Add aStream to the list of streams.
   void OpenStream(nsMediaCacheStream* aStream);
   // Remove aStream from the list of streams.
   void ReleaseStream(nsMediaCacheStream* aStream);
   // Free all blocks belonging to aStream.
@@ -1573,17 +1579,17 @@ nsMediaCache::AllocateAndWriteBlock(nsMe
 void
 nsMediaCache::OpenStream(nsMediaCacheStream* aStream)
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
   LOG(PR_LOG_DEBUG, ("Stream %p opened", aStream));
   mStreams.AppendElement(aStream);
-  aStream->mResourceID = mNextResourceID++;
+  aStream->mResourceID = AllocateResourceID();
 
   // Queue an update since a new stream has been opened.
   gMediaCache->QueueUpdate();
 }
 
 void
 nsMediaCache::ReleaseStream(nsMediaCacheStream* aStream)
 {
@@ -1851,16 +1857,23 @@ nsMediaCacheStream::NotifyDataReceived(P
 
 void
 nsMediaCacheStream::NotifyDataEnded(nsresult aStatus)
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
   ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
 
+  if (NS_FAILED(aStatus)) {
+    // Disconnect from other streams sharing our resource, since they
+    // should continue trying to load. Our load might have been deliberately
+    // canceled and that shouldn't affect other streams.
+    mResourceID = gMediaCache->AllocateResourceID();
+  }
+
   PRInt32 blockOffset = PRInt32(mChannelOffset%BLOCK_SIZE);
   if (blockOffset > 0) {
     // Write back the partial block
     memset(reinterpret_cast<char*>(mPartialBlockBuffer) + blockOffset, 0,
            BLOCK_SIZE - blockOffset);
     gMediaCache->AllocateAndWriteBlock(this, mPartialBlockBuffer,
         mMetadataInPartialBlockBuffer ? MODE_METADATA : MODE_PLAYBACK);
     // Wake up readers who may be waiting for this data
@@ -1877,16 +1890,17 @@ nsMediaCacheStream::NotifyDataEnded(nsre
       NS_ASSERTION(!stream->mDidNotifyDataEnded, "Stream already ended!");
       stream->mDidNotifyDataEnded = true;
       stream->mNotifyDataEndedStatus = aStatus;
       stream->mClient->CacheClientNotifyDataEnded(aStatus);
     }
   }
 
   mChannelEnded = true;
+  gMediaCache->QueueUpdate();
 }
 
 nsMediaCacheStream::~nsMediaCacheStream()
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   NS_ASSERTION(!mPinCount, "Unbalanced Pin");
 
   if (gMediaCache) {
@@ -2329,16 +2343,19 @@ nsMediaCacheStream::Init()
   gMediaCache->OpenStream(this);
   mInitialized = true;
   return NS_OK;
 }
 
 nsresult
 nsMediaCacheStream::InitAsClone(nsMediaCacheStream* aOriginal)
 {
+  if (!aOriginal->IsAvailableForSharing())
+    return NS_ERROR_FAILURE;
+
   if (mInitialized)
     return NS_OK;
 
   nsresult rv = Init();
   if (NS_FAILED(rv))
     return rv;
   mResourceID = aOriginal->mResourceID;
 
--- a/content/media/nsMediaCache.h
+++ b/content/media/nsMediaCache.h
@@ -227,24 +227,25 @@ public:
     MODE_PLAYBACK
   };
 
   // aClient provides the underlying transport that cache will use to read
   // data for this stream.
   nsMediaCacheStream(ChannelMediaResource* aClient)
     : mClient(aClient), mResourceID(0), mInitialized(false),
       mHasHadUpdate(false),
+      mClosed(false),
+      mDidNotifyDataEnded(false),
       mIsSeekable(false), mCacheSuspended(false),
-      mChannelEnded(false), mDidNotifyDataEnded(false),
+      mChannelEnded(false),
       mUsingNullPrincipal(false),
       mChannelOffset(0), mStreamLength(-1),  
       mStreamOffset(0), mPlaybackBytesPerSecond(10000),
       mPinCount(0), mCurrentMode(MODE_PLAYBACK),
-      mMetadataInPartialBlockBuffer(false),
-      mClosed(false) {}
+      mMetadataInPartialBlockBuffer(false) {}
   ~nsMediaCacheStream();
 
   // Set up this stream with the cache. Can fail on OOM. One
   // of InitAsClone or Init must be called before any other method on
   // this class. Does nothing if already initialized.
   nsresult Init();
 
   // Set up this stream with the cache, assuming it's for the same data
@@ -261,16 +262,22 @@ public:
   // we do an HTTP load the seekability may be different (and sometimes
   // is, in practice, due to the effects of caching proxies).
   void SetSeekable(bool aIsSeekable);
   // This must be called (and return) before the ChannelMediaResource
   // used to create this nsMediaCacheStream is deleted.
   void Close();
   // This returns true when the stream has been closed
   bool IsClosed() const { return mClosed; }
+  // Returns true when this stream is can be shared by a new resource load
+  bool IsAvailableForSharing() const
+  {
+    return !mClosed &&
+      (!mDidNotifyDataEnded || NS_SUCCEEDED(mNotifyDataEndedStatus));
+  }
   // Get the principal for this stream.
   nsIPrincipal* GetCurrentPrincipal() { return mPrincipal; }
   // Ensure a global media cache update has run with this stream present.
   // This ensures the cache has had a chance to suspend or unsuspend this stream.
   // Called only on main thread. This can change the state of streams, fire
   // notifications, etc.
   void EnsureCacheUpdate();
 
@@ -461,30 +468,33 @@ private:
   // All streams with the same mResourceID are loading the same
   // underlying resource and should share data.
   PRInt64                mResourceID;
   // Set to true when Init or InitAsClone has been called
   bool                   mInitialized;
   // Set to true when nsMediaCache::Update() has finished while this stream
   // was present.
   bool                   mHasHadUpdate;
+  // Set to true when the stream has been closed either explicitly or
+  // due to an internal cache error
+  bool                   mClosed;
+  // True if CacheClientNotifyDataEnded has been called for this stream.
+  bool                   mDidNotifyDataEnded;
 
   // The following fields are protected by the cache's monitor but are
   // only written on the main thread. 
 
   // The last reported seekability state for the underlying channel
   bool mIsSeekable;
   // True if the cache has suspended our channel because the cache is
   // full and the priority of the data that would be received is lower
   // than the priority of the data already in the cache
   bool mCacheSuspended;
   // True if the channel ended and we haven't seeked it again.
   bool mChannelEnded;
-  // True if CacheClientNotifyDataEnded has been called for this stream.
-  bool mDidNotifyDataEnded;
   // True if mPrincipal is a null principal because we saw data from
   // multiple origins
   bool mUsingNullPrincipal;
   // The offset where the next data from the channel will arrive
   PRInt64      mChannelOffset;
   // The reported or discovered length of the data, or -1 if nothing is
   // known
   PRInt64      mStreamLength;
@@ -505,25 +515,23 @@ private:
   BlockList         mMetadataBlocks;
   // The list of played-back blocks; the first block is the most recently used
   BlockList         mPlayedBlocks;
   // The last reported estimate of the decoder's playback rate
   PRUint32          mPlaybackBytesPerSecond;
   // The number of times this stream has been Pinned without a
   // corresponding Unpin
   PRUint32          mPinCount;
-  // The status used when we did CacheClientNotifyDataEnded
+  // The status used when we did CacheClientNotifyDataEnded. Only valid
+  // when mDidNotifyDataEnded is true.
   nsresult          mNotifyDataEndedStatus;
   // The last reported read mode
   ReadMode          mCurrentMode;
   // True if some data in mPartialBlockBuffer has been read as metadata
   bool              mMetadataInPartialBlockBuffer;
-  // Set to true when the stream has been closed either explicitly or
-  // due to an internal cache error
-  bool              mClosed;
 
   // The following field is protected by the cache's monitor but are
   // only written on the main thread.
 
   // Data received for the block containing mChannelOffset. Data needs
   // to wait here so we can write back a complete block. The first
   // mChannelOffset%BLOCK_SIZE bytes have been filled in with good data,
   // the rest are garbage.