content/media/FileBlockCache.cpp

Fri, 16 Jan 2015 04:50:19 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 04:50:19 +0100
branch
TOR_BUG_9701
changeset 13
44a2da4a2ab2
permissions
-rw-r--r--

Replace accessor implementation with direct member state manipulation, by
request https://trac.torproject.org/projects/tor/ticket/9701#comment:32

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #include "FileBlockCache.h"
michael@0 8 #include "VideoUtils.h"
michael@0 9 #include "prio.h"
michael@0 10 #include <algorithm>
michael@0 11
michael@0 12 namespace mozilla {
michael@0 13
michael@0 14 nsresult FileBlockCache::Open(PRFileDesc* aFD)
michael@0 15 {
michael@0 16 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
michael@0 17 NS_ENSURE_TRUE(aFD != nullptr, NS_ERROR_FAILURE);
michael@0 18 {
michael@0 19 MonitorAutoLock mon(mFileMonitor);
michael@0 20 mFD = aFD;
michael@0 21 }
michael@0 22 {
michael@0 23 MonitorAutoLock mon(mDataMonitor);
michael@0 24 nsresult res = NS_NewThread(getter_AddRefs(mThread),
michael@0 25 nullptr,
michael@0 26 MEDIA_THREAD_STACK_SIZE);
michael@0 27 mIsOpen = NS_SUCCEEDED(res);
michael@0 28 return res;
michael@0 29 }
michael@0 30 }
michael@0 31
michael@0 32 FileBlockCache::FileBlockCache()
michael@0 33 : mFileMonitor("MediaCache.Writer.IO.Monitor"),
michael@0 34 mFD(nullptr),
michael@0 35 mFDCurrentPos(0),
michael@0 36 mDataMonitor("MediaCache.Writer.Data.Monitor"),
michael@0 37 mIsWriteScheduled(false),
michael@0 38 mIsOpen(false)
michael@0 39 {
michael@0 40 MOZ_COUNT_CTOR(FileBlockCache);
michael@0 41 }
michael@0 42
michael@0 43 FileBlockCache::~FileBlockCache()
michael@0 44 {
michael@0 45 NS_ASSERTION(!mIsOpen, "Should Close() FileBlockCache before destroying");
michael@0 46 {
michael@0 47 // Note, mThread will be shutdown by the time this runs, so we won't
michael@0 48 // block while taking mFileMonitor.
michael@0 49 MonitorAutoLock mon(mFileMonitor);
michael@0 50 if (mFD) {
michael@0 51 PRStatus prrc;
michael@0 52 prrc = PR_Close(mFD);
michael@0 53 if (prrc != PR_SUCCESS) {
michael@0 54 NS_WARNING("PR_Close() failed.");
michael@0 55 }
michael@0 56 mFD = nullptr;
michael@0 57 }
michael@0 58 }
michael@0 59 MOZ_COUNT_DTOR(FileBlockCache);
michael@0 60 }
michael@0 61
michael@0 62
michael@0 63 void FileBlockCache::Close()
michael@0 64 {
michael@0 65 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
michael@0 66 MonitorAutoLock mon(mDataMonitor);
michael@0 67
michael@0 68 mIsOpen = false;
michael@0 69
michael@0 70 if (mThread) {
michael@0 71 // We must shut down the thread in another runnable. This is called
michael@0 72 // while we're shutting down the media cache, and nsIThread::Shutdown()
michael@0 73 // can cause events to run before it completes, which could end up
michael@0 74 // opening more streams, while the media cache is shutting down and
michael@0 75 // releasing memory etc! Also note we close mFD in the destructor so
michael@0 76 // as to not disturb any IO that's currently running.
michael@0 77 nsCOMPtr<nsIRunnable> event = new ShutdownThreadEvent(mThread);
michael@0 78 mThread = nullptr;
michael@0 79 NS_DispatchToMainThread(event);
michael@0 80 }
michael@0 81 }
michael@0 82
michael@0 83 nsresult FileBlockCache::WriteBlock(uint32_t aBlockIndex, const uint8_t* aData)
michael@0 84 {
michael@0 85 MonitorAutoLock mon(mDataMonitor);
michael@0 86
michael@0 87 if (!mIsOpen)
michael@0 88 return NS_ERROR_FAILURE;
michael@0 89
michael@0 90 // Check if we've already got a pending write scheduled for this block.
michael@0 91 mBlockChanges.EnsureLengthAtLeast(aBlockIndex + 1);
michael@0 92 bool blockAlreadyHadPendingChange = mBlockChanges[aBlockIndex] != nullptr;
michael@0 93 mBlockChanges[aBlockIndex] = new BlockChange(aData);
michael@0 94
michael@0 95 if (!blockAlreadyHadPendingChange || !mChangeIndexList.Contains(aBlockIndex)) {
michael@0 96 // We either didn't already have a pending change for this block, or we
michael@0 97 // did but we didn't have an entry for it in mChangeIndexList (we're in the process
michael@0 98 // of writing it and have removed the block's index out of mChangeIndexList
michael@0 99 // in Run() but not finished writing the block to file yet). Add the blocks
michael@0 100 // index to the end of mChangeIndexList to ensure the block is written as
michael@0 101 // as soon as possible.
michael@0 102 mChangeIndexList.PushBack(aBlockIndex);
michael@0 103 }
michael@0 104 NS_ASSERTION(mChangeIndexList.Contains(aBlockIndex), "Must have entry for new block");
michael@0 105
michael@0 106 EnsureWriteScheduled();
michael@0 107
michael@0 108 return NS_OK;
michael@0 109 }
michael@0 110
michael@0 111 void FileBlockCache::EnsureWriteScheduled()
michael@0 112 {
michael@0 113 mDataMonitor.AssertCurrentThreadOwns();
michael@0 114
michael@0 115 if (!mIsWriteScheduled) {
michael@0 116 mThread->Dispatch(this, NS_DISPATCH_NORMAL);
michael@0 117 mIsWriteScheduled = true;
michael@0 118 }
michael@0 119 }
michael@0 120
michael@0 121 nsresult FileBlockCache::Seek(int64_t aOffset)
michael@0 122 {
michael@0 123 mFileMonitor.AssertCurrentThreadOwns();
michael@0 124
michael@0 125 if (mFDCurrentPos != aOffset) {
michael@0 126 int64_t result = PR_Seek64(mFD, aOffset, PR_SEEK_SET);
michael@0 127 if (result != aOffset) {
michael@0 128 NS_WARNING("Failed to seek media cache file");
michael@0 129 return NS_ERROR_FAILURE;
michael@0 130 }
michael@0 131 mFDCurrentPos = result;
michael@0 132 }
michael@0 133 return NS_OK;
michael@0 134 }
michael@0 135
michael@0 136 nsresult FileBlockCache::ReadFromFile(int64_t aOffset,
michael@0 137 uint8_t* aDest,
michael@0 138 int32_t aBytesToRead,
michael@0 139 int32_t& aBytesRead)
michael@0 140 {
michael@0 141 mFileMonitor.AssertCurrentThreadOwns();
michael@0 142
michael@0 143 nsresult res = Seek(aOffset);
michael@0 144 if (NS_FAILED(res)) return res;
michael@0 145
michael@0 146 aBytesRead = PR_Read(mFD, aDest, aBytesToRead);
michael@0 147 if (aBytesRead <= 0)
michael@0 148 return NS_ERROR_FAILURE;
michael@0 149 mFDCurrentPos += aBytesRead;
michael@0 150
michael@0 151 return NS_OK;
michael@0 152 }
michael@0 153
michael@0 154 nsresult FileBlockCache::WriteBlockToFile(int32_t aBlockIndex,
michael@0 155 const uint8_t* aBlockData)
michael@0 156 {
michael@0 157 mFileMonitor.AssertCurrentThreadOwns();
michael@0 158
michael@0 159 nsresult rv = Seek(BlockIndexToOffset(aBlockIndex));
michael@0 160 if (NS_FAILED(rv)) return rv;
michael@0 161
michael@0 162 int32_t amount = PR_Write(mFD, aBlockData, BLOCK_SIZE);
michael@0 163 if (amount < BLOCK_SIZE) {
michael@0 164 NS_WARNING("Failed to write media cache block!");
michael@0 165 return NS_ERROR_FAILURE;
michael@0 166 }
michael@0 167 mFDCurrentPos += BLOCK_SIZE;
michael@0 168
michael@0 169 return NS_OK;
michael@0 170 }
michael@0 171
michael@0 172 nsresult FileBlockCache::MoveBlockInFile(int32_t aSourceBlockIndex,
michael@0 173 int32_t aDestBlockIndex)
michael@0 174 {
michael@0 175 mFileMonitor.AssertCurrentThreadOwns();
michael@0 176
michael@0 177 uint8_t buf[BLOCK_SIZE];
michael@0 178 int32_t bytesRead = 0;
michael@0 179 if (NS_FAILED(ReadFromFile(BlockIndexToOffset(aSourceBlockIndex),
michael@0 180 buf,
michael@0 181 BLOCK_SIZE,
michael@0 182 bytesRead))) {
michael@0 183 return NS_ERROR_FAILURE;
michael@0 184 }
michael@0 185 return WriteBlockToFile(aDestBlockIndex, buf);
michael@0 186 }
michael@0 187
michael@0 188 nsresult FileBlockCache::Run()
michael@0 189 {
michael@0 190 MonitorAutoLock mon(mDataMonitor);
michael@0 191 NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
michael@0 192 NS_ASSERTION(!mChangeIndexList.IsEmpty(), "Only dispatch when there's work to do");
michael@0 193 NS_ASSERTION(mIsWriteScheduled, "Should report write running or scheduled.");
michael@0 194
michael@0 195 while (!mChangeIndexList.IsEmpty()) {
michael@0 196 if (!mIsOpen) {
michael@0 197 // We've been closed, abort, discarding unwritten changes.
michael@0 198 mIsWriteScheduled = false;
michael@0 199 return NS_ERROR_FAILURE;
michael@0 200 }
michael@0 201
michael@0 202 // Process each pending change. We pop the index out of the change
michael@0 203 // list, but leave the BlockChange in mBlockChanges until the change
michael@0 204 // is written to file. This is so that any read which happens while
michael@0 205 // we drop mDataMonitor to write will refer to the data's source in
michael@0 206 // memory, rather than the not-yet up to date data written to file.
michael@0 207 // This also ensures we will insert a new index into mChangeIndexList
michael@0 208 // when this happens.
michael@0 209
michael@0 210 // Hold a reference to the change, in case another change
michael@0 211 // overwrites the mBlockChanges entry for this block while we drop
michael@0 212 // mDataMonitor to take mFileMonitor.
michael@0 213 int32_t blockIndex = mChangeIndexList.PopFront();
michael@0 214 nsRefPtr<BlockChange> change = mBlockChanges[blockIndex];
michael@0 215 NS_ABORT_IF_FALSE(change,
michael@0 216 "Change index list should only contain entries for blocks with changes");
michael@0 217 {
michael@0 218 MonitorAutoUnlock unlock(mDataMonitor);
michael@0 219 MonitorAutoLock lock(mFileMonitor);
michael@0 220 if (change->IsWrite()) {
michael@0 221 WriteBlockToFile(blockIndex, change->mData.get());
michael@0 222 } else if (change->IsMove()) {
michael@0 223 MoveBlockInFile(change->mSourceBlockIndex, blockIndex);
michael@0 224 }
michael@0 225 }
michael@0 226 // If a new change has not been made to the block while we dropped
michael@0 227 // mDataMonitor, clear reference to the old change. Otherwise, the old
michael@0 228 // reference has been cleared already.
michael@0 229 if (mBlockChanges[blockIndex] == change) {
michael@0 230 mBlockChanges[blockIndex] = nullptr;
michael@0 231 }
michael@0 232 }
michael@0 233
michael@0 234 mIsWriteScheduled = false;
michael@0 235
michael@0 236 return NS_OK;
michael@0 237 }
michael@0 238
michael@0 239 nsresult FileBlockCache::Read(int64_t aOffset,
michael@0 240 uint8_t* aData,
michael@0 241 int32_t aLength,
michael@0 242 int32_t* aBytes)
michael@0 243 {
michael@0 244 MonitorAutoLock mon(mDataMonitor);
michael@0 245
michael@0 246 if (!mFD || (aOffset / BLOCK_SIZE) > INT32_MAX)
michael@0 247 return NS_ERROR_FAILURE;
michael@0 248
michael@0 249 int32_t bytesToRead = aLength;
michael@0 250 int64_t offset = aOffset;
michael@0 251 uint8_t* dst = aData;
michael@0 252 while (bytesToRead > 0) {
michael@0 253 int32_t blockIndex = static_cast<int32_t>(offset / BLOCK_SIZE);
michael@0 254 int32_t start = offset % BLOCK_SIZE;
michael@0 255 int32_t amount = std::min(BLOCK_SIZE - start, bytesToRead);
michael@0 256
michael@0 257 // If the block is not yet written to file, we can just read from
michael@0 258 // the memory buffer, otherwise we need to read from file.
michael@0 259 int32_t bytesRead = 0;
michael@0 260 nsRefPtr<BlockChange> change = mBlockChanges[blockIndex];
michael@0 261 if (change && change->IsWrite()) {
michael@0 262 // Block isn't yet written to file. Read from memory buffer.
michael@0 263 const uint8_t* blockData = change->mData.get();
michael@0 264 memcpy(dst, blockData + start, amount);
michael@0 265 bytesRead = amount;
michael@0 266 } else {
michael@0 267 if (change && change->IsMove()) {
michael@0 268 // The target block is the destination of a not-yet-completed move
michael@0 269 // action, so read from the move's source block from file. Note we
michael@0 270 // *don't* follow a chain of moves here, as a move's source index
michael@0 271 // is resolved when MoveBlock() is called, and the move's source's
michael@0 272 // block could be have itself been subject to a move (or write)
michael@0 273 // which happened *after* this move was recorded.
michael@0 274 blockIndex = mBlockChanges[blockIndex]->mSourceBlockIndex;
michael@0 275 }
michael@0 276 // Block has been written to file, either as the source block of a move,
michael@0 277 // or as a stable (all changes made) block. Read the data directly
michael@0 278 // from file.
michael@0 279 nsresult res;
michael@0 280 {
michael@0 281 MonitorAutoUnlock unlock(mDataMonitor);
michael@0 282 MonitorAutoLock lock(mFileMonitor);
michael@0 283 res = ReadFromFile(BlockIndexToOffset(blockIndex) + start,
michael@0 284 dst,
michael@0 285 amount,
michael@0 286 bytesRead);
michael@0 287 }
michael@0 288 NS_ENSURE_SUCCESS(res,res);
michael@0 289 }
michael@0 290 dst += bytesRead;
michael@0 291 offset += bytesRead;
michael@0 292 bytesToRead -= bytesRead;
michael@0 293 }
michael@0 294 *aBytes = aLength - bytesToRead;
michael@0 295 return NS_OK;
michael@0 296 }
michael@0 297
michael@0 298 nsresult FileBlockCache::MoveBlock(int32_t aSourceBlockIndex, int32_t aDestBlockIndex)
michael@0 299 {
michael@0 300 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
michael@0 301 MonitorAutoLock mon(mDataMonitor);
michael@0 302
michael@0 303 if (!mIsOpen)
michael@0 304 return NS_ERROR_FAILURE;
michael@0 305
michael@0 306 mBlockChanges.EnsureLengthAtLeast(std::max(aSourceBlockIndex, aDestBlockIndex) + 1);
michael@0 307
michael@0 308 // The source block's contents may be the destination of another pending
michael@0 309 // move, which in turn can be the destination of another pending move,
michael@0 310 // etc. Resolve the final source block, so that if one of the blocks in
michael@0 311 // the chain of moves is overwritten, we don't lose the reference to the
michael@0 312 // contents of the destination block.
michael@0 313 int32_t sourceIndex = aSourceBlockIndex;
michael@0 314 BlockChange* sourceBlock = nullptr;
michael@0 315 while ((sourceBlock = mBlockChanges[sourceIndex]) &&
michael@0 316 sourceBlock->IsMove()) {
michael@0 317 sourceIndex = sourceBlock->mSourceBlockIndex;
michael@0 318 }
michael@0 319
michael@0 320 if (mBlockChanges[aDestBlockIndex] == nullptr ||
michael@0 321 !mChangeIndexList.Contains(aDestBlockIndex)) {
michael@0 322 // Only add another entry to the change index list if we don't already
michael@0 323 // have one for this block. We won't have an entry when either there's
michael@0 324 // no pending change for this block, or if there is a pending change for
michael@0 325 // this block and we're in the process of writing it (we've popped the
michael@0 326 // block's index out of mChangeIndexList in Run() but not finished writing
michael@0 327 // the block to file yet.
michael@0 328 mChangeIndexList.PushBack(aDestBlockIndex);
michael@0 329 }
michael@0 330
michael@0 331 // If the source block hasn't yet been written to file then the dest block
michael@0 332 // simply contains that same write. Resolve this as a write instead.
michael@0 333 if (sourceBlock && sourceBlock->IsWrite()) {
michael@0 334 mBlockChanges[aDestBlockIndex] = new BlockChange(sourceBlock->mData.get());
michael@0 335 } else {
michael@0 336 mBlockChanges[aDestBlockIndex] = new BlockChange(sourceIndex);
michael@0 337 }
michael@0 338
michael@0 339 EnsureWriteScheduled();
michael@0 340
michael@0 341 NS_ASSERTION(mChangeIndexList.Contains(aDestBlockIndex),
michael@0 342 "Should have scheduled block for change");
michael@0 343
michael@0 344 return NS_OK;
michael@0 345 }
michael@0 346
michael@0 347 } // End namespace mozilla.

mercurial