michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 sts=2 et cindent: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef FILE_BLOCK_CACHE_H_ michael@0: #define FILE_BLOCK_CACHE_H_ michael@0: michael@0: #include "mozilla/Attributes.h" michael@0: #include "mozilla/Monitor.h" michael@0: #include "nsTArray.h" michael@0: #include "MediaCache.h" michael@0: #include "nsDeque.h" michael@0: #include "nsThreadUtils.h" michael@0: michael@0: struct PRFileDesc; michael@0: michael@0: namespace mozilla { michael@0: michael@0: // Manages file I/O for the media cache. Data comes in over the network michael@0: // via callbacks on the main thread, however we don't want to write the michael@0: // incoming data to the media cache on the main thread, as this could block michael@0: // causing UI jank. michael@0: // michael@0: // So FileBlockCache provides an abstraction for a temporary file accessible michael@0: // as an array of blocks, which supports a block move operation, and michael@0: // allows synchronous reading and writing from any thread, with writes being michael@0: // buffered so as not to block. michael@0: // michael@0: // Writes and cache block moves (which require reading) are deferred to michael@0: // their own non-main thread. This object also ensures that data which has michael@0: // been scheduled to be written, but hasn't actually *been* written, is read michael@0: // as if it had, i.e. pending writes are cached in readable memory until michael@0: // they're flushed to file. michael@0: // michael@0: // To improve efficiency, writes can only be done at block granularity, michael@0: // whereas reads can be done with byte granularity. michael@0: // michael@0: // Note it's also recommended not to read from the media cache from the main michael@0: // thread to prevent jank. michael@0: // michael@0: // When WriteBlock() or MoveBlock() are called, data about how to complete michael@0: // the block change is added to mBlockChanges, indexed by block index, and michael@0: // the block index is appended to the mChangeIndexList. This enables michael@0: // us to quickly tell if a block has been changed, and ensures we can perform michael@0: // the changes in the correct order. An event is dispatched to perform the michael@0: // changes listed in mBlockChanges to file. Read() checks mBlockChanges and michael@0: // determines the current data to return, reading from file or from michael@0: // mBlockChanges as necessary. michael@0: class FileBlockCache : public nsRunnable { michael@0: public: michael@0: enum { michael@0: BLOCK_SIZE = MediaCacheStream::BLOCK_SIZE michael@0: }; michael@0: michael@0: FileBlockCache(); michael@0: michael@0: ~FileBlockCache(); michael@0: michael@0: // Assumes ownership of aFD. michael@0: nsresult Open(PRFileDesc* aFD); michael@0: michael@0: // Closes writer, shuts down thread. michael@0: void Close(); michael@0: michael@0: // Can be called on any thread. This defers to a non-main thread. michael@0: nsresult WriteBlock(uint32_t aBlockIndex, const uint8_t* aData); michael@0: michael@0: // Performs block writes and block moves on its own thread. michael@0: NS_IMETHOD Run() MOZ_OVERRIDE; michael@0: michael@0: // Synchronously reads data from file. May read from file or memory michael@0: // depending on whether written blocks have been flushed to file yet. michael@0: // Not recommended to be called from the main thread, as can cause jank. michael@0: nsresult Read(int64_t aOffset, michael@0: uint8_t* aData, michael@0: int32_t aLength, michael@0: int32_t* aBytes); michael@0: michael@0: // Moves a block asynchronously. Can be called on any thread. michael@0: // This defers file I/O to a non-main thread. michael@0: nsresult MoveBlock(int32_t aSourceBlockIndex, int32_t aDestBlockIndex); michael@0: michael@0: // Represents a change yet to be made to a block in the file. The change michael@0: // is either a write (and the data to be written is stored in this struct) michael@0: // or a move (and the index of the source block is stored instead). michael@0: struct BlockChange MOZ_FINAL { michael@0: michael@0: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BlockChange) michael@0: michael@0: // This block is waiting in memory to be written. michael@0: // Stores a copy of the block, so we can write it asynchronously. michael@0: BlockChange(const uint8_t* aData) michael@0: : mSourceBlockIndex(-1) michael@0: { michael@0: mData = new uint8_t[BLOCK_SIZE]; michael@0: memcpy(mData.get(), aData, BLOCK_SIZE); michael@0: } michael@0: michael@0: // This block's contents are located in another file michael@0: // block, i.e. this block has been moved. michael@0: BlockChange(int32_t aSourceBlockIndex) michael@0: : mSourceBlockIndex(aSourceBlockIndex) {} michael@0: michael@0: nsAutoArrayPtr mData; michael@0: const int32_t mSourceBlockIndex; michael@0: michael@0: bool IsMove() const { michael@0: return mSourceBlockIndex != -1; michael@0: } michael@0: bool IsWrite() const { michael@0: return mSourceBlockIndex == -1 && michael@0: mData.get() != nullptr; michael@0: } michael@0: michael@0: private: michael@0: // Private destructor, to discourage deletion outside of Release(): michael@0: ~BlockChange() michael@0: { michael@0: } michael@0: }; michael@0: michael@0: class Int32Queue : private nsDeque { michael@0: public: michael@0: int32_t PopFront() { michael@0: int32_t front = ObjectAt(0); michael@0: nsDeque::PopFront(); michael@0: return front; michael@0: } michael@0: michael@0: void PushBack(int32_t aValue) { michael@0: nsDeque::Push(reinterpret_cast(aValue)); michael@0: } michael@0: michael@0: bool Contains(int32_t aValue) { michael@0: for (int32_t i = 0; i < GetSize(); ++i) { michael@0: if (ObjectAt(i) == aValue) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool IsEmpty() { michael@0: return nsDeque::GetSize() == 0; michael@0: } michael@0: michael@0: private: michael@0: int32_t ObjectAt(int32_t aIndex) { michael@0: void* v = nsDeque::ObjectAt(aIndex); michael@0: return reinterpret_cast(v); michael@0: } michael@0: }; michael@0: michael@0: private: michael@0: int64_t BlockIndexToOffset(int32_t aBlockIndex) { michael@0: return static_cast(aBlockIndex) * BLOCK_SIZE; michael@0: } michael@0: michael@0: // Monitor which controls access to mFD and mFDCurrentPos. Don't hold michael@0: // mDataMonitor while holding mFileMonitor! mFileMonitor must be owned michael@0: // while accessing any of the following data fields or methods. michael@0: Monitor mFileMonitor; michael@0: // Moves a block already committed to file. michael@0: nsresult MoveBlockInFile(int32_t aSourceBlockIndex, michael@0: int32_t aDestBlockIndex); michael@0: // Seeks file pointer. michael@0: nsresult Seek(int64_t aOffset); michael@0: // Reads data from file offset. michael@0: nsresult ReadFromFile(int64_t aOffset, michael@0: uint8_t* aDest, michael@0: int32_t aBytesToRead, michael@0: int32_t& aBytesRead); michael@0: nsresult WriteBlockToFile(int32_t aBlockIndex, const uint8_t* aBlockData); michael@0: // File descriptor we're writing to. This is created externally, but michael@0: // shutdown by us. michael@0: PRFileDesc* mFD; michael@0: // The current file offset in the file. michael@0: int64_t mFDCurrentPos; michael@0: michael@0: // Monitor which controls access to all data in this class, except mFD michael@0: // and mFDCurrentPos. Don't hold mDataMonitor while holding mFileMonitor! michael@0: // mDataMonitor must be owned while accessing any of the following data michael@0: // fields or methods. michael@0: Monitor mDataMonitor; michael@0: // Ensures we either are running the event to preform IO, or an event michael@0: // has been dispatched to preform the IO. michael@0: // mDataMonitor must be owned while calling this. michael@0: void EnsureWriteScheduled(); michael@0: // Array of block changes to made. If mBlockChanges[offset/BLOCK_SIZE] == nullptr, michael@0: // then the block has no pending changes to be written, but if michael@0: // mBlockChanges[offset/BLOCK_SIZE] != nullptr, then either there's a block michael@0: // cached in memory waiting to be written, or this block is the target of a michael@0: // block move. michael@0: nsTArray< nsRefPtr > mBlockChanges; michael@0: // Thread upon which block writes and block moves are performed. This is michael@0: // created upon open, and shutdown (asynchronously) upon close (on the michael@0: // main thread). michael@0: nsCOMPtr mThread; michael@0: // Queue of pending block indexes that need to be written or moved. michael@0: //nsAutoTArray mChangeIndexList; michael@0: Int32Queue mChangeIndexList; michael@0: // True if we've dispatched an event to commit all pending block changes michael@0: // to file on mThread. michael@0: bool mIsWriteScheduled; michael@0: // True if the writer is ready to write data to file. michael@0: bool mIsOpen; michael@0: }; michael@0: michael@0: } // End namespace mozilla. michael@0: michael@0: #endif /* FILE_BLOCK_CACHE_H_ */