summaryrefslogtreecommitdiffstats
path: root/dom/media/FileBlockCache.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/FileBlockCache.cpp')
-rw-r--r--dom/media/FileBlockCache.cpp365
1 files changed, 365 insertions, 0 deletions
diff --git a/dom/media/FileBlockCache.cpp b/dom/media/FileBlockCache.cpp
new file mode 100644
index 000000000..ce6a34cce
--- /dev/null
+++ b/dom/media/FileBlockCache.cpp
@@ -0,0 +1,365 @@
+/* -*- 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 "mozilla/SharedThreadPool.h"
+#include "FileBlockCache.h"
+#include "VideoUtils.h"
+#include "prio.h"
+#include <algorithm>
+
+namespace mozilla {
+
+nsresult FileBlockCache::Open(PRFileDesc* aFD)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+ NS_ENSURE_TRUE(aFD != nullptr, NS_ERROR_FAILURE);
+ {
+ MonitorAutoLock mon(mFileMonitor);
+ mFD = aFD;
+ }
+ {
+ MonitorAutoLock mon(mDataMonitor);
+ nsresult res = NS_NewThread(getter_AddRefs(mThread),
+ nullptr,
+ SharedThreadPool::kStackSize);
+ mIsOpen = NS_SUCCEEDED(res);
+ return res;
+ }
+}
+
+FileBlockCache::FileBlockCache()
+ : mFileMonitor("MediaCache.Writer.IO.Monitor"),
+ mFD(nullptr),
+ mFDCurrentPos(0),
+ mDataMonitor("MediaCache.Writer.Data.Monitor"),
+ mIsWriteScheduled(false),
+ mIsOpen(false)
+{
+ MOZ_COUNT_CTOR(FileBlockCache);
+}
+
+FileBlockCache::~FileBlockCache()
+{
+ NS_ASSERTION(!mIsOpen, "Should Close() FileBlockCache before destroying");
+ {
+ // Note, mThread will be shutdown by the time this runs, so we won't
+ // block while taking mFileMonitor.
+ MonitorAutoLock mon(mFileMonitor);
+ if (mFD) {
+ PRStatus prrc;
+ prrc = PR_Close(mFD);
+ if (prrc != PR_SUCCESS) {
+ NS_WARNING("PR_Close() failed.");
+ }
+ mFD = nullptr;
+ }
+ }
+ MOZ_COUNT_DTOR(FileBlockCache);
+}
+
+
+void FileBlockCache::Close()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+ MonitorAutoLock mon(mDataMonitor);
+
+ mIsOpen = false;
+
+ if (mThread) {
+ // We must shut down the thread in another runnable. This is called
+ // while we're shutting down the media cache, and nsIThread::Shutdown()
+ // can cause events to run before it completes, which could end up
+ // opening more streams, while the media cache is shutting down and
+ // releasing memory etc! Also note we close mFD in the destructor so
+ // as to not disturb any IO that's currently running.
+ nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+ if (mainThread) {
+ nsCOMPtr<nsIRunnable> event = new ShutdownThreadEvent(mThread);
+ mainThread->Dispatch(event.forget(), NS_DISPATCH_NORMAL);
+ } else {
+ // we're on Mainthread already, *and* the event queues are already
+ // shut down, so no events should occur - certainly not creations of
+ // new streams.
+ mThread->Shutdown();
+ }
+ }
+}
+
+template<typename Container, typename Value>
+bool
+ContainerContains(const Container& aContainer, const Value& value)
+{
+ return std::find(aContainer.begin(), aContainer.end(), value)
+ != aContainer.end();
+}
+
+nsresult FileBlockCache::WriteBlock(uint32_t aBlockIndex, const uint8_t* aData)
+{
+ MonitorAutoLock mon(mDataMonitor);
+
+ if (!mIsOpen)
+ return NS_ERROR_FAILURE;
+
+ // Check if we've already got a pending write scheduled for this block.
+ mBlockChanges.EnsureLengthAtLeast(aBlockIndex + 1);
+ bool blockAlreadyHadPendingChange = mBlockChanges[aBlockIndex] != nullptr;
+ mBlockChanges[aBlockIndex] = new BlockChange(aData);
+
+ if (!blockAlreadyHadPendingChange || !ContainerContains(mChangeIndexList, aBlockIndex)) {
+ // We either didn't already have a pending change for this block, or we
+ // did but we didn't have an entry for it in mChangeIndexList (we're in the process
+ // of writing it and have removed the block's index out of mChangeIndexList
+ // in Run() but not finished writing the block to file yet). Add the blocks
+ // index to the end of mChangeIndexList to ensure the block is written as
+ // as soon as possible.
+ mChangeIndexList.push_back(aBlockIndex);
+ }
+ NS_ASSERTION(ContainerContains(mChangeIndexList, aBlockIndex), "Must have entry for new block");
+
+ EnsureWriteScheduled();
+
+ return NS_OK;
+}
+
+void FileBlockCache::EnsureWriteScheduled()
+{
+ mDataMonitor.AssertCurrentThreadOwns();
+
+ if (!mIsWriteScheduled) {
+ mThread->Dispatch(this, NS_DISPATCH_NORMAL);
+ mIsWriteScheduled = true;
+ }
+}
+
+nsresult FileBlockCache::Seek(int64_t aOffset)
+{
+ mFileMonitor.AssertCurrentThreadOwns();
+
+ if (mFDCurrentPos != aOffset) {
+ int64_t result = PR_Seek64(mFD, aOffset, PR_SEEK_SET);
+ if (result != aOffset) {
+ NS_WARNING("Failed to seek media cache file");
+ return NS_ERROR_FAILURE;
+ }
+ mFDCurrentPos = result;
+ }
+ return NS_OK;
+}
+
+nsresult FileBlockCache::ReadFromFile(int64_t aOffset,
+ uint8_t* aDest,
+ int32_t aBytesToRead,
+ int32_t& aBytesRead)
+{
+ mFileMonitor.AssertCurrentThreadOwns();
+
+ nsresult res = Seek(aOffset);
+ if (NS_FAILED(res)) return res;
+
+ aBytesRead = PR_Read(mFD, aDest, aBytesToRead);
+ if (aBytesRead <= 0)
+ return NS_ERROR_FAILURE;
+ mFDCurrentPos += aBytesRead;
+
+ return NS_OK;
+}
+
+nsresult FileBlockCache::WriteBlockToFile(int32_t aBlockIndex,
+ const uint8_t* aBlockData)
+{
+ mFileMonitor.AssertCurrentThreadOwns();
+
+ nsresult rv = Seek(BlockIndexToOffset(aBlockIndex));
+ if (NS_FAILED(rv)) return rv;
+
+ int32_t amount = PR_Write(mFD, aBlockData, BLOCK_SIZE);
+ if (amount < BLOCK_SIZE) {
+ NS_WARNING("Failed to write media cache block!");
+ return NS_ERROR_FAILURE;
+ }
+ mFDCurrentPos += BLOCK_SIZE;
+
+ return NS_OK;
+}
+
+nsresult FileBlockCache::MoveBlockInFile(int32_t aSourceBlockIndex,
+ int32_t aDestBlockIndex)
+{
+ mFileMonitor.AssertCurrentThreadOwns();
+
+ uint8_t buf[BLOCK_SIZE];
+ int32_t bytesRead = 0;
+ if (NS_FAILED(ReadFromFile(BlockIndexToOffset(aSourceBlockIndex),
+ buf,
+ BLOCK_SIZE,
+ bytesRead))) {
+ return NS_ERROR_FAILURE;
+ }
+ return WriteBlockToFile(aDestBlockIndex, buf);
+}
+
+nsresult FileBlockCache::Run()
+{
+ MonitorAutoLock mon(mDataMonitor);
+ NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
+ NS_ASSERTION(!mChangeIndexList.empty(), "Only dispatch when there's work to do");
+ NS_ASSERTION(mIsWriteScheduled, "Should report write running or scheduled.");
+
+ while (!mChangeIndexList.empty()) {
+ if (!mIsOpen) {
+ // We've been closed, abort, discarding unwritten changes.
+ mIsWriteScheduled = false;
+ return NS_ERROR_FAILURE;
+ }
+
+ // Process each pending change. We pop the index out of the change
+ // list, but leave the BlockChange in mBlockChanges until the change
+ // is written to file. This is so that any read which happens while
+ // we drop mDataMonitor to write will refer to the data's source in
+ // memory, rather than the not-yet up to date data written to file.
+ // This also ensures we will insert a new index into mChangeIndexList
+ // when this happens.
+
+ // Hold a reference to the change, in case another change
+ // overwrites the mBlockChanges entry for this block while we drop
+ // mDataMonitor to take mFileMonitor.
+ int32_t blockIndex = mChangeIndexList.front();
+ mChangeIndexList.pop_front();
+ RefPtr<BlockChange> change = mBlockChanges[blockIndex];
+ MOZ_ASSERT(change,
+ "Change index list should only contain entries for blocks "
+ "with changes");
+ {
+ MonitorAutoUnlock unlock(mDataMonitor);
+ MonitorAutoLock lock(mFileMonitor);
+ if (change->IsWrite()) {
+ WriteBlockToFile(blockIndex, change->mData.get());
+ } else if (change->IsMove()) {
+ MoveBlockInFile(change->mSourceBlockIndex, blockIndex);
+ }
+ }
+ // If a new change has not been made to the block while we dropped
+ // mDataMonitor, clear reference to the old change. Otherwise, the old
+ // reference has been cleared already.
+ if (mBlockChanges[blockIndex] == change) {
+ mBlockChanges[blockIndex] = nullptr;
+ }
+ }
+
+ mIsWriteScheduled = false;
+
+ return NS_OK;
+}
+
+nsresult FileBlockCache::Read(int64_t aOffset,
+ uint8_t* aData,
+ int32_t aLength,
+ int32_t* aBytes)
+{
+ MonitorAutoLock mon(mDataMonitor);
+
+ if (!mFD || (aOffset / BLOCK_SIZE) > INT32_MAX)
+ return NS_ERROR_FAILURE;
+
+ int32_t bytesToRead = aLength;
+ int64_t offset = aOffset;
+ uint8_t* dst = aData;
+ while (bytesToRead > 0) {
+ int32_t blockIndex = static_cast<int32_t>(offset / BLOCK_SIZE);
+ int32_t start = offset % BLOCK_SIZE;
+ int32_t amount = std::min(BLOCK_SIZE - start, bytesToRead);
+
+ // If the block is not yet written to file, we can just read from
+ // the memory buffer, otherwise we need to read from file.
+ int32_t bytesRead = 0;
+ RefPtr<BlockChange> change = mBlockChanges[blockIndex];
+ if (change && change->IsWrite()) {
+ // Block isn't yet written to file. Read from memory buffer.
+ const uint8_t* blockData = change->mData.get();
+ memcpy(dst, blockData + start, amount);
+ bytesRead = amount;
+ } else {
+ if (change && change->IsMove()) {
+ // The target block is the destination of a not-yet-completed move
+ // action, so read from the move's source block from file. Note we
+ // *don't* follow a chain of moves here, as a move's source index
+ // is resolved when MoveBlock() is called, and the move's source's
+ // block could be have itself been subject to a move (or write)
+ // which happened *after* this move was recorded.
+ blockIndex = mBlockChanges[blockIndex]->mSourceBlockIndex;
+ }
+ // Block has been written to file, either as the source block of a move,
+ // or as a stable (all changes made) block. Read the data directly
+ // from file.
+ nsresult res;
+ {
+ MonitorAutoUnlock unlock(mDataMonitor);
+ MonitorAutoLock lock(mFileMonitor);
+ res = ReadFromFile(BlockIndexToOffset(blockIndex) + start,
+ dst,
+ amount,
+ bytesRead);
+ }
+ NS_ENSURE_SUCCESS(res,res);
+ }
+ dst += bytesRead;
+ offset += bytesRead;
+ bytesToRead -= bytesRead;
+ }
+ *aBytes = aLength - bytesToRead;
+ return NS_OK;
+}
+
+nsresult FileBlockCache::MoveBlock(int32_t aSourceBlockIndex, int32_t aDestBlockIndex)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+ MonitorAutoLock mon(mDataMonitor);
+
+ if (!mIsOpen)
+ return NS_ERROR_FAILURE;
+
+ mBlockChanges.EnsureLengthAtLeast(std::max(aSourceBlockIndex, aDestBlockIndex) + 1);
+
+ // The source block's contents may be the destination of another pending
+ // move, which in turn can be the destination of another pending move,
+ // etc. Resolve the final source block, so that if one of the blocks in
+ // the chain of moves is overwritten, we don't lose the reference to the
+ // contents of the destination block.
+ int32_t sourceIndex = aSourceBlockIndex;
+ BlockChange* sourceBlock = nullptr;
+ while ((sourceBlock = mBlockChanges[sourceIndex]) &&
+ sourceBlock->IsMove()) {
+ sourceIndex = sourceBlock->mSourceBlockIndex;
+ }
+
+ if (mBlockChanges[aDestBlockIndex] == nullptr ||
+ !ContainerContains(mChangeIndexList, aDestBlockIndex)) {
+ // Only add another entry to the change index list if we don't already
+ // have one for this block. We won't have an entry when either there's
+ // no pending change for this block, or if there is a pending change for
+ // this block and we're in the process of writing it (we've popped the
+ // block's index out of mChangeIndexList in Run() but not finished writing
+ // the block to file yet.
+ mChangeIndexList.push_back(aDestBlockIndex);
+ }
+
+ // If the source block hasn't yet been written to file then the dest block
+ // simply contains that same write. Resolve this as a write instead.
+ if (sourceBlock && sourceBlock->IsWrite()) {
+ mBlockChanges[aDestBlockIndex] = new BlockChange(sourceBlock->mData.get());
+ } else {
+ mBlockChanges[aDestBlockIndex] = new BlockChange(sourceIndex);
+ }
+
+ EnsureWriteScheduled();
+
+ NS_ASSERTION(ContainerContains(mChangeIndexList, aDestBlockIndex),
+ "Should have scheduled block for change");
+
+ return NS_OK;
+}
+
+} // End namespace mozilla.