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.