Bug 1371882 - Implement MemoryBlockCache - r?cpearce draft
authorGerald Squelart <gsquelart@mozilla.com>
Fri, 09 Jun 2017 15:37:23 +1200
changeset 595160 6fb8b5d967dc21b6ae7a6489f287508f0b3fed35
parent 595159 eb59cf2b0424123e68acfdb7d0c0de6f701e2ea8
child 595161 4b7e2d0829a8286be5f6d30841961db7882b488e
push id64265
push usergsquelart@mozilla.com
push dateFri, 16 Jun 2017 03:37:56 +0000
reviewerscpearce
bugs1371882
milestone56.0a1
Bug 1371882 - Implement MemoryBlockCache - r?cpearce Memory-backed block cache. At initialization, allocates memory needed to store the expected content length. If MediaCache attempts to write/move beyond the expected size, we grow the buffer accordingly, as we cannot fully trust HTTP headers. (Future patch will ensure we put a limit to this growth.) MozReview-Commit-ID: GHxYMGXYrwI
dom/media/MemoryBlockCache.cpp
dom/media/MemoryBlockCache.h
dom/media/moz.build
new file mode 100644
--- /dev/null
+++ b/dom/media/MemoryBlockCache.cpp
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MemoryBlockCache.h"
+
+#include "mozilla/Logging.h"
+
+namespace mozilla {
+
+#undef LOG
+LazyLogModule gMemoryBlockCacheLog("MemoryBlockCache");
+#define LOG(x, ...)                                                            \
+  MOZ_LOG(gMemoryBlockCacheLog, LogLevel::Debug, ("%p " x, this, ##__VA_ARGS__))
+
+MemoryBlockCache::MemoryBlockCache(int64_t aContentLength)
+  // Buffer whole blocks.
+  : mInitialContentLength((aContentLength >= 0) ? size_t(aContentLength) : 0)
+  , mMutex("MemoryBlockCache")
+{
+}
+
+MemoryBlockCache::~MemoryBlockCache()
+{
+  MOZ_ASSERT(mBuffer.IsEmpty());
+}
+
+bool
+MemoryBlockCache::EnsureBufferCanContain(size_t aContentLength)
+{
+  mMutex.AssertCurrentThreadOwns();
+  if (aContentLength == 0) {
+    return true;
+  }
+  size_t desiredLength = ((aContentLength - 1) / BLOCK_SIZE + 1) * BLOCK_SIZE;
+  if (mBuffer.Length() >= desiredLength) {
+    // Already large enough.
+    return true;
+  }
+  // Need larger buffer, attempt to re-allocate.
+  return mBuffer.SetLength(desiredLength, mozilla::fallible);
+}
+
+nsresult
+MemoryBlockCache::Init()
+{
+  LOG("@%p Init()", this);
+  MutexAutoLock lock(mMutex);
+  // Attempt to pre-allocate buffer for expected content length.
+  return EnsureBufferCanContain(mInitialContentLength) ? NS_OK
+                                                       : NS_ERROR_FAILURE;
+}
+
+void
+MemoryBlockCache::Close()
+{
+  LOG("@%p Close()", this);
+  MutexAutoLock lock(mMutex);
+  mBuffer.SetLength(0);
+}
+
+nsresult
+MemoryBlockCache::WriteBlock(uint32_t aBlockIndex,
+                             Span<const uint8_t> aData1,
+                             Span<const uint8_t> aData2)
+{
+  MutexAutoLock lock(mMutex);
+
+  size_t offset = BlockIndexToOffset(aBlockIndex);
+  if (!EnsureBufferCanContain(offset + aData1.Length() + aData2.Length())) {
+    return NS_ERROR_FAILURE;
+  }
+
+  memcpy(mBuffer.Elements() + offset, aData1.Elements(), aData1.Length());
+  if (aData2.Length() > 0) {
+    memcpy(mBuffer.Elements() + offset + aData1.Length(),
+           aData2.Elements(),
+           aData2.Length());
+  }
+
+  return NS_OK;
+}
+
+nsresult
+MemoryBlockCache::Read(int64_t aOffset,
+                       uint8_t* aData,
+                       int32_t aLength,
+                       int32_t* aBytes)
+{
+  MutexAutoLock lock(mMutex);
+
+  MOZ_ASSERT(aOffset >= 0);
+  if (aOffset + aLength > int64_t(mBuffer.Length())) {
+    return NS_ERROR_FAILURE;
+  }
+
+  memcpy(aData, mBuffer.Elements() + aOffset, aLength);
+  *aBytes = aLength;
+
+  return NS_OK;
+}
+
+nsresult
+MemoryBlockCache::MoveBlock(int32_t aSourceBlockIndex, int32_t aDestBlockIndex)
+{
+  MutexAutoLock lock(mMutex);
+
+  size_t sourceOffset = BlockIndexToOffset(aSourceBlockIndex);
+  size_t destOffset = BlockIndexToOffset(aDestBlockIndex);
+  if (sourceOffset + BLOCK_SIZE > mBuffer.Length() ||
+      !EnsureBufferCanContain(destOffset + BLOCK_SIZE)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  memcpy(mBuffer.Elements() + destOffset,
+         mBuffer.Elements() + sourceOffset,
+         BLOCK_SIZE);
+
+  return NS_OK;
+}
+
+} // End namespace mozilla.
+
+// avoid redefined macro in unified build
+#undef LOG
copy from dom/media/MediaBlockCacheBase.h
copy to dom/media/MemoryBlockCache.h
--- a/dom/media/MediaBlockCacheBase.h
+++ b/dom/media/MemoryBlockCache.h
@@ -1,77 +1,84 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#ifndef MEDIA_BLOCK_CACHE_BASE_H_
-#define MEDIA_BLOCK_CACHE_BASE_H_
+#ifndef MEMORY_BLOCK_CACHE_H_
+#define MEMORY_BLOCK_CACHE_H_
 
-#include "MediaCache.h"
-#include "mozilla/Span.h"
+#include "MediaBlockCacheBase.h"
+#include "mozilla/Mutex.h"
 
 namespace mozilla {
 
 // Manages block management for the media cache. Data comes in over the network
 // via callbacks on the main thread, however we don't want to write the
 // incoming data to the media cache on the main thread, as this could block
 // causing UI jank.
 //
-// So MediaBlockCacheBase provides an abstraction for a temporary memory buffer or file accessible
+// So MediaBlockCacheBase provides an abstraction for a temporary memory buffer
 // as an array of blocks, which supports a block move operation, and
-// allows synchronous reading and writing from any thread, with writes being
-// buffered as needed so as not to block.
+// allows synchronous reading and writing from any thread.
 //
 // Writes and cache block moves (which require reading) may be deferred to
 // their own non-main thread. This object also ensures that data which has
 // been scheduled to be written, but hasn't actually *been* written, is read
 // as if it had, i.e. pending writes are cached in readable memory until
 // they're flushed to file.
 //
 // To improve efficiency, writes can only be done at block granularity,
 // whereas reads can be done with byte granularity.
-//
-// Note it's also recommended not to read from the media cache from the main
-// thread to prevent jank.
-class MediaBlockCacheBase
+class MemoryBlockCache : public MediaBlockCacheBase
 {
 public:
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaBlockCacheBase)
-
-  static_assert(
-    MediaCacheStream::BLOCK_SIZE <
-      static_cast<decltype(MediaCacheStream::BLOCK_SIZE)>(INT32_MAX),
-    "MediaCacheStream::BLOCK_SIZE should fit in 31 bits");
-  static const int32_t BLOCK_SIZE = MediaCacheStream::BLOCK_SIZE;
+  explicit MemoryBlockCache(int64_t aContentLength);
 
 protected:
-  virtual ~MediaBlockCacheBase() {}
+  virtual ~MemoryBlockCache();
 
 public:
-  virtual nsresult Init() = 0;
+  // Allocate initial buffer.
+  virtual nsresult Init() override;
 
-  // Closes writer, shuts down thread.
-  virtual void Close() = 0;
+  // Empty buffer.
+  virtual void Close() override;
 
-  // Can be called on any thread. This defers to a non-main thread.
+  // Can be called on any thread.
   virtual nsresult WriteBlock(uint32_t aBlockIndex,
                               Span<const uint8_t> aData1,
-                              Span<const uint8_t> aData2) = 0;
+                              Span<const uint8_t> aData2) override;
 
-  // Synchronously reads data from file. May read from file or memory
-  // depending on whether written blocks have been flushed to file yet.
-  // Not recommended to be called from the main thread, as can cause jank.
+  // Synchronously reads data from buffer.
   virtual nsresult Read(int64_t aOffset,
                         uint8_t* aData,
                         int32_t aLength,
-                        int32_t* aBytes) = 0;
+                        int32_t* aBytes) override;
+
+  // Moves a block. Can be called on any thread.
+  virtual nsresult MoveBlock(int32_t aSourceBlockIndex,
+                             int32_t aDestBlockIndex) override;
+
+private:
+  static size_t BlockIndexToOffset(uint32_t aBlockIndex)
+  {
+    return static_cast<size_t>(aBlockIndex) * BLOCK_SIZE;
+  }
 
-  // Moves a block asynchronously. Can be called on any thread.
-  // This defers file I/O to a non-main thread.
-  virtual nsresult MoveBlock(int32_t aSourceBlockIndex,
-                             int32_t aDestBlockIndex) = 0;
+  // Ensure the buffer has at least a multiple of BLOCK_SIZE that can contain
+  // aContentLength bytes. Buffer size can only grow.
+  // Returns false if allocation failed.
+  bool EnsureBufferCanContain(size_t aContentLength);
+
+  // Initial content length.
+  const size_t mInitialContentLength;
+
+  // Mutex which controls access to all members below.
+  Mutex mMutex;
+
+  nsTArray<uint8_t> mBuffer;
 };
 
 } // End namespace mozilla.
 
-#endif /* MEDIA_BLOCK_CACHE_BASE_H_ */
+#endif /* MEMORY_BLOCK_CACHE_H_ */
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -130,16 +130,17 @@ EXPORTS += [
     'MediaShutdownManager.h',
     'MediaStatistics.h',
     'MediaStreamGraph.h',
     'MediaStreamListener.h',
     'MediaStreamVideoSink.h',
     'MediaTimer.h',
     'MediaTrack.h',
     'MediaTrackList.h',
+    'MemoryBlockCache.h',
     'MP3Decoder.h',
     'MP3Demuxer.h',
     'MP3FrameParser.h',
     'nsIDocumentActivity.h',
     'PrincipalChangeObserver.h',
     'QueueObject.h',
     'SeekJob.h',
     'SeekTarget.h',
@@ -237,16 +238,17 @@ UNIFIED_SOURCES += [
     'MediaStreamError.cpp',
     'MediaStreamGraph.cpp',
     'MediaStreamListener.cpp',
     'MediaStreamTrack.cpp',
     'MediaStreamVideoSink.cpp',
     'MediaTimer.cpp',
     'MediaTrack.cpp',
     'MediaTrackList.cpp',
+    'MemoryBlockCache.cpp',
     'MP3Decoder.cpp',
     'MP3Demuxer.cpp',
     'MP3FrameParser.cpp',
     'QueueObject.cpp',
     'SeekJob.cpp',
     'StreamTracks.cpp',
     'TextTrack.cpp',
     'TextTrackCue.cpp',