content/media/FileBlockCache.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/content/media/FileBlockCache.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,347 @@
     1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* vim:set ts=2 sw=2 sts=2 et cindent: */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.8 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#include "FileBlockCache.h"
    1.11 +#include "VideoUtils.h"
    1.12 +#include "prio.h"
    1.13 +#include <algorithm>
    1.14 +
    1.15 +namespace mozilla {
    1.16 +
    1.17 +nsresult FileBlockCache::Open(PRFileDesc* aFD)
    1.18 +{
    1.19 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
    1.20 +  NS_ENSURE_TRUE(aFD != nullptr, NS_ERROR_FAILURE);
    1.21 +  {
    1.22 +    MonitorAutoLock mon(mFileMonitor);
    1.23 +    mFD = aFD;
    1.24 +  }
    1.25 +  {
    1.26 +    MonitorAutoLock mon(mDataMonitor);
    1.27 +    nsresult res = NS_NewThread(getter_AddRefs(mThread),
    1.28 +                                nullptr,
    1.29 +                                MEDIA_THREAD_STACK_SIZE);
    1.30 +    mIsOpen = NS_SUCCEEDED(res);
    1.31 +    return res;
    1.32 +  }
    1.33 +}
    1.34 +
    1.35 +FileBlockCache::FileBlockCache()
    1.36 +  : mFileMonitor("MediaCache.Writer.IO.Monitor"),
    1.37 +    mFD(nullptr),
    1.38 +    mFDCurrentPos(0),
    1.39 +    mDataMonitor("MediaCache.Writer.Data.Monitor"),
    1.40 +    mIsWriteScheduled(false),
    1.41 +    mIsOpen(false)
    1.42 +{
    1.43 +  MOZ_COUNT_CTOR(FileBlockCache);
    1.44 +}
    1.45 +
    1.46 +FileBlockCache::~FileBlockCache()
    1.47 +{
    1.48 +  NS_ASSERTION(!mIsOpen, "Should Close() FileBlockCache before destroying");
    1.49 +  {
    1.50 +    // Note, mThread will be shutdown by the time this runs, so we won't
    1.51 +    // block while taking mFileMonitor.
    1.52 +    MonitorAutoLock mon(mFileMonitor);
    1.53 +    if (mFD) {
    1.54 +      PRStatus prrc;
    1.55 +      prrc = PR_Close(mFD);
    1.56 +      if (prrc != PR_SUCCESS) {
    1.57 +        NS_WARNING("PR_Close() failed.");
    1.58 +      }
    1.59 +      mFD = nullptr;
    1.60 +    }
    1.61 +  }
    1.62 +  MOZ_COUNT_DTOR(FileBlockCache);
    1.63 +}
    1.64 +
    1.65 +
    1.66 +void FileBlockCache::Close()
    1.67 +{
    1.68 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
    1.69 +  MonitorAutoLock mon(mDataMonitor);
    1.70 +
    1.71 +  mIsOpen = false;
    1.72 +
    1.73 +  if (mThread) {
    1.74 +    // We must shut down the thread in another runnable. This is called
    1.75 +    // while we're shutting down the media cache, and nsIThread::Shutdown()
    1.76 +    // can cause events to run before it completes, which could end up
    1.77 +    // opening more streams, while the media cache is shutting down and
    1.78 +    // releasing memory etc! Also note we close mFD in the destructor so
    1.79 +    // as to not disturb any IO that's currently running.
    1.80 +    nsCOMPtr<nsIRunnable> event = new ShutdownThreadEvent(mThread);
    1.81 +    mThread = nullptr;
    1.82 +    NS_DispatchToMainThread(event);
    1.83 +  }
    1.84 +}
    1.85 +
    1.86 +nsresult FileBlockCache::WriteBlock(uint32_t aBlockIndex, const uint8_t* aData)
    1.87 +{
    1.88 +  MonitorAutoLock mon(mDataMonitor);
    1.89 +
    1.90 +  if (!mIsOpen)
    1.91 +    return NS_ERROR_FAILURE;
    1.92 +
    1.93 +  // Check if we've already got a pending write scheduled for this block.
    1.94 +  mBlockChanges.EnsureLengthAtLeast(aBlockIndex + 1);
    1.95 +  bool blockAlreadyHadPendingChange = mBlockChanges[aBlockIndex] != nullptr;
    1.96 +  mBlockChanges[aBlockIndex] = new BlockChange(aData);
    1.97 +
    1.98 +  if (!blockAlreadyHadPendingChange || !mChangeIndexList.Contains(aBlockIndex)) {
    1.99 +    // We either didn't already have a pending change for this block, or we
   1.100 +    // did but we didn't have an entry for it in mChangeIndexList (we're in the process
   1.101 +    // of writing it and have removed the block's index out of mChangeIndexList
   1.102 +    // in Run() but not finished writing the block to file yet). Add the blocks
   1.103 +    // index to the end of mChangeIndexList to ensure the block is written as
   1.104 +    // as soon as possible.
   1.105 +    mChangeIndexList.PushBack(aBlockIndex);
   1.106 +  }
   1.107 +  NS_ASSERTION(mChangeIndexList.Contains(aBlockIndex), "Must have entry for new block");
   1.108 +
   1.109 +  EnsureWriteScheduled();
   1.110 +
   1.111 +  return NS_OK;
   1.112 +}
   1.113 +
   1.114 +void FileBlockCache::EnsureWriteScheduled()
   1.115 +{
   1.116 +  mDataMonitor.AssertCurrentThreadOwns();
   1.117 +
   1.118 +  if (!mIsWriteScheduled) {
   1.119 +    mThread->Dispatch(this, NS_DISPATCH_NORMAL);
   1.120 +    mIsWriteScheduled = true;
   1.121 +  }
   1.122 +}
   1.123 +
   1.124 +nsresult FileBlockCache::Seek(int64_t aOffset)
   1.125 +{
   1.126 +  mFileMonitor.AssertCurrentThreadOwns();
   1.127 +
   1.128 +  if (mFDCurrentPos != aOffset) {
   1.129 +    int64_t result = PR_Seek64(mFD, aOffset, PR_SEEK_SET);
   1.130 +    if (result != aOffset) {
   1.131 +      NS_WARNING("Failed to seek media cache file");
   1.132 +      return NS_ERROR_FAILURE;
   1.133 +    }
   1.134 +    mFDCurrentPos = result;
   1.135 +  }
   1.136 +  return NS_OK;
   1.137 +}
   1.138 +
   1.139 +nsresult FileBlockCache::ReadFromFile(int64_t aOffset,
   1.140 +                                      uint8_t* aDest,
   1.141 +                                      int32_t aBytesToRead,
   1.142 +                                      int32_t& aBytesRead)
   1.143 +{
   1.144 +  mFileMonitor.AssertCurrentThreadOwns();
   1.145 +
   1.146 +  nsresult res = Seek(aOffset);
   1.147 +  if (NS_FAILED(res)) return res;
   1.148 +  
   1.149 +  aBytesRead = PR_Read(mFD, aDest, aBytesToRead);
   1.150 +  if (aBytesRead <= 0)
   1.151 +    return NS_ERROR_FAILURE;
   1.152 +  mFDCurrentPos += aBytesRead;
   1.153 +  
   1.154 +  return NS_OK;
   1.155 +}
   1.156 +
   1.157 +nsresult FileBlockCache::WriteBlockToFile(int32_t aBlockIndex,
   1.158 +                                          const uint8_t* aBlockData)
   1.159 +{
   1.160 +  mFileMonitor.AssertCurrentThreadOwns();
   1.161 +
   1.162 +  nsresult rv = Seek(BlockIndexToOffset(aBlockIndex));
   1.163 +  if (NS_FAILED(rv)) return rv;
   1.164 +
   1.165 +  int32_t amount = PR_Write(mFD, aBlockData, BLOCK_SIZE);
   1.166 +  if (amount < BLOCK_SIZE) {
   1.167 +    NS_WARNING("Failed to write media cache block!");
   1.168 +    return NS_ERROR_FAILURE;
   1.169 +  }
   1.170 +  mFDCurrentPos += BLOCK_SIZE;
   1.171 +
   1.172 +  return NS_OK;
   1.173 +}
   1.174 +
   1.175 +nsresult FileBlockCache::MoveBlockInFile(int32_t aSourceBlockIndex,
   1.176 +                                         int32_t aDestBlockIndex)
   1.177 +{
   1.178 +  mFileMonitor.AssertCurrentThreadOwns();
   1.179 +
   1.180 +  uint8_t buf[BLOCK_SIZE];
   1.181 +  int32_t bytesRead = 0;
   1.182 +  if (NS_FAILED(ReadFromFile(BlockIndexToOffset(aSourceBlockIndex),
   1.183 +                             buf,
   1.184 +                             BLOCK_SIZE,
   1.185 +                             bytesRead))) {
   1.186 +    return NS_ERROR_FAILURE;
   1.187 +  }
   1.188 +  return WriteBlockToFile(aDestBlockIndex, buf);
   1.189 +}
   1.190 +
   1.191 +nsresult FileBlockCache::Run()
   1.192 +{
   1.193 +  MonitorAutoLock mon(mDataMonitor);
   1.194 +  NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
   1.195 +  NS_ASSERTION(!mChangeIndexList.IsEmpty(), "Only dispatch when there's work to do");
   1.196 +  NS_ASSERTION(mIsWriteScheduled, "Should report write running or scheduled.");
   1.197 +
   1.198 +  while (!mChangeIndexList.IsEmpty()) {
   1.199 +    if (!mIsOpen) {
   1.200 +      // We've been closed, abort, discarding unwritten changes.
   1.201 +      mIsWriteScheduled = false;
   1.202 +      return NS_ERROR_FAILURE;
   1.203 +    }
   1.204 +
   1.205 +    // Process each pending change. We pop the index out of the change
   1.206 +    // list, but leave the BlockChange in mBlockChanges until the change
   1.207 +    // is written to file. This is so that any read which happens while
   1.208 +    // we drop mDataMonitor to write will refer to the data's source in
   1.209 +    // memory, rather than the not-yet up to date data written to file.
   1.210 +    // This also ensures we will insert a new index into mChangeIndexList
   1.211 +    // when this happens.
   1.212 +
   1.213 +    // Hold a reference to the change, in case another change
   1.214 +    // overwrites the mBlockChanges entry for this block while we drop
   1.215 +    // mDataMonitor to take mFileMonitor.
   1.216 +    int32_t blockIndex = mChangeIndexList.PopFront();
   1.217 +    nsRefPtr<BlockChange> change = mBlockChanges[blockIndex];
   1.218 +    NS_ABORT_IF_FALSE(change,
   1.219 +      "Change index list should only contain entries for blocks with changes");
   1.220 +    {
   1.221 +      MonitorAutoUnlock unlock(mDataMonitor);
   1.222 +      MonitorAutoLock lock(mFileMonitor);
   1.223 +      if (change->IsWrite()) {
   1.224 +        WriteBlockToFile(blockIndex, change->mData.get());
   1.225 +      } else if (change->IsMove()) {
   1.226 +        MoveBlockInFile(change->mSourceBlockIndex, blockIndex);
   1.227 +      }
   1.228 +    }
   1.229 +    // If a new change has not been made to the block while we dropped
   1.230 +    // mDataMonitor, clear reference to the old change. Otherwise, the old
   1.231 +    // reference has been cleared already.
   1.232 +    if (mBlockChanges[blockIndex] == change) {
   1.233 +      mBlockChanges[blockIndex] = nullptr;
   1.234 +    }
   1.235 +  }
   1.236 +
   1.237 +  mIsWriteScheduled = false;
   1.238 +
   1.239 +  return NS_OK;
   1.240 +}
   1.241 +
   1.242 +nsresult FileBlockCache::Read(int64_t aOffset,
   1.243 +                              uint8_t* aData,
   1.244 +                              int32_t aLength,
   1.245 +                              int32_t* aBytes)
   1.246 +{
   1.247 +  MonitorAutoLock mon(mDataMonitor);
   1.248 +
   1.249 +  if (!mFD || (aOffset / BLOCK_SIZE) > INT32_MAX)
   1.250 +    return NS_ERROR_FAILURE;
   1.251 +
   1.252 +  int32_t bytesToRead = aLength;
   1.253 +  int64_t offset = aOffset;
   1.254 +  uint8_t* dst = aData;
   1.255 +  while (bytesToRead > 0) {
   1.256 +    int32_t blockIndex = static_cast<int32_t>(offset / BLOCK_SIZE);
   1.257 +    int32_t start = offset % BLOCK_SIZE;
   1.258 +    int32_t amount = std::min(BLOCK_SIZE - start, bytesToRead);
   1.259 +
   1.260 +    // If the block is not yet written to file, we can just read from
   1.261 +    // the memory buffer, otherwise we need to read from file.
   1.262 +    int32_t bytesRead = 0;
   1.263 +    nsRefPtr<BlockChange> change = mBlockChanges[blockIndex];
   1.264 +    if (change && change->IsWrite()) {
   1.265 +      // Block isn't yet written to file. Read from memory buffer.
   1.266 +      const uint8_t* blockData = change->mData.get();
   1.267 +      memcpy(dst, blockData + start, amount);
   1.268 +      bytesRead = amount;
   1.269 +    } else {
   1.270 +      if (change && change->IsMove()) {
   1.271 +        // The target block is the destination of a not-yet-completed move
   1.272 +        // action, so read from the move's source block from file. Note we
   1.273 +        // *don't* follow a chain of moves here, as a move's source index
   1.274 +        // is resolved when MoveBlock() is called, and the move's source's
   1.275 +        // block could be have itself been subject to a move (or write)
   1.276 +        // which happened *after* this move was recorded.
   1.277 +        blockIndex = mBlockChanges[blockIndex]->mSourceBlockIndex;
   1.278 +      }
   1.279 +      // Block has been written to file, either as the source block of a move,
   1.280 +      // or as a stable (all changes made) block. Read the data directly
   1.281 +      // from file.
   1.282 +      nsresult res;
   1.283 +      {
   1.284 +        MonitorAutoUnlock unlock(mDataMonitor);
   1.285 +        MonitorAutoLock lock(mFileMonitor);
   1.286 +        res = ReadFromFile(BlockIndexToOffset(blockIndex) + start,
   1.287 +                           dst,
   1.288 +                           amount,
   1.289 +                           bytesRead);
   1.290 +      }
   1.291 +      NS_ENSURE_SUCCESS(res,res);
   1.292 +    }
   1.293 +    dst += bytesRead;
   1.294 +    offset += bytesRead;
   1.295 +    bytesToRead -= bytesRead;
   1.296 +  }
   1.297 +  *aBytes = aLength - bytesToRead;
   1.298 +  return NS_OK;
   1.299 +}
   1.300 +
   1.301 +nsresult FileBlockCache::MoveBlock(int32_t aSourceBlockIndex, int32_t aDestBlockIndex)
   1.302 +{
   1.303 +  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   1.304 +  MonitorAutoLock mon(mDataMonitor);
   1.305 +
   1.306 +  if (!mIsOpen)
   1.307 +    return NS_ERROR_FAILURE;
   1.308 +
   1.309 +  mBlockChanges.EnsureLengthAtLeast(std::max(aSourceBlockIndex, aDestBlockIndex) + 1);
   1.310 +
   1.311 +  // The source block's contents may be the destination of another pending
   1.312 +  // move, which in turn can be the destination of another pending move,
   1.313 +  // etc. Resolve the final source block, so that if one of the blocks in
   1.314 +  // the chain of moves is overwritten, we don't lose the reference to the
   1.315 +  // contents of the destination block.
   1.316 +  int32_t sourceIndex = aSourceBlockIndex;
   1.317 +  BlockChange* sourceBlock = nullptr;
   1.318 +  while ((sourceBlock = mBlockChanges[sourceIndex]) &&
   1.319 +          sourceBlock->IsMove()) {
   1.320 +    sourceIndex = sourceBlock->mSourceBlockIndex;
   1.321 +  }
   1.322 +
   1.323 +  if (mBlockChanges[aDestBlockIndex] == nullptr ||
   1.324 +      !mChangeIndexList.Contains(aDestBlockIndex)) {
   1.325 +    // Only add another entry to the change index list if we don't already
   1.326 +    // have one for this block. We won't have an entry when either there's
   1.327 +    // no pending change for this block, or if there is a pending change for
   1.328 +    // this block and we're in the process of writing it (we've popped the
   1.329 +    // block's index out of mChangeIndexList in Run() but not finished writing
   1.330 +    // the block to file yet.
   1.331 +    mChangeIndexList.PushBack(aDestBlockIndex);
   1.332 +  }
   1.333 +  
   1.334 +  // If the source block hasn't yet been written to file then the dest block
   1.335 +  // simply contains that same write. Resolve this as a write instead.
   1.336 +  if (sourceBlock && sourceBlock->IsWrite()) {
   1.337 +    mBlockChanges[aDestBlockIndex] = new BlockChange(sourceBlock->mData.get());
   1.338 +  } else {
   1.339 +    mBlockChanges[aDestBlockIndex] = new BlockChange(sourceIndex);
   1.340 +  }
   1.341 +
   1.342 +  EnsureWriteScheduled();
   1.343 +
   1.344 +  NS_ASSERTION(mChangeIndexList.Contains(aDestBlockIndex),
   1.345 +    "Should have scheduled block for change");
   1.346 +
   1.347 +  return NS_OK;
   1.348 +}
   1.349 +
   1.350 +} // End namespace mozilla.

mercurial