FileBlockCache.h (8049B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef FILE_BLOCK_CACHE_H_ 8 #define FILE_BLOCK_CACHE_H_ 9 10 #include <deque> 11 12 #include "MediaBlockCacheBase.h" 13 #include "mozilla/AbstractThread.h" 14 #include "mozilla/MozPromise.h" 15 #include "mozilla/Mutex.h" 16 #include "mozilla/UniquePtr.h" 17 #include "nsDeque.h" 18 #include "nsTArray.h" 19 #include "nsThreadUtils.h" 20 21 struct PRFileDesc; 22 23 namespace mozilla { 24 25 // Manages file I/O for the media cache. Data comes in over the network 26 // via callbacks on the main thread, however we don't want to write the 27 // incoming data to the media cache on the main thread, as this could block 28 // causing UI jank. 29 // 30 // So FileBlockCache provides an abstraction for a temporary file accessible 31 // as an array of blocks, which supports a block move operation, and 32 // allows synchronous reading and writing from any thread, with writes being 33 // buffered so as not to block. 34 // 35 // Writes and cache block moves (which require reading) are deferred to 36 // their own non-main thread. This object also ensures that data which has 37 // been scheduled to be written, but hasn't actually *been* written, is read 38 // as if it had, i.e. pending writes are cached in readable memory until 39 // they're flushed to file. 40 // 41 // To improve efficiency, writes can only be done at block granularity, 42 // whereas reads can be done with byte granularity. 43 // 44 // Note it's also recommended not to read from the media cache from the main 45 // thread to prevent jank. 46 // 47 // When WriteBlock() or MoveBlock() are called, data about how to complete 48 // the block change is added to mBlockChanges, indexed by block index, and 49 // the block index is appended to the mChangeIndexList. This enables 50 // us to quickly tell if a block has been changed, and ensures we can perform 51 // the changes in the correct order. An event is dispatched to perform the 52 // changes listed in mBlockChanges to file. Read() checks mBlockChanges and 53 // determines the current data to return, reading from file or from 54 // mBlockChanges as necessary. 55 class FileBlockCache : public MediaBlockCacheBase { 56 public: 57 FileBlockCache(); 58 59 protected: 60 virtual ~FileBlockCache(); 61 62 public: 63 // Launch thread and open temporary file. 64 nsresult Init() override; 65 66 // Will discard pending changes if any. 67 void Flush() override; 68 69 // Maximum number of blocks allowed in this block cache. 70 // Calculated from "media.cache_size" pref. 71 size_t GetMaxBlocks(size_t aCacheSizeInKB) const override; 72 73 // Can be called on any thread. This defers to a non-main thread. 74 nsresult WriteBlock(uint32_t aBlockIndex, Span<const uint8_t> aData1, 75 Span<const uint8_t> aData2) override; 76 77 // Synchronously reads data from file. May read from file or memory 78 // depending on whether written blocks have been flushed to file yet. 79 // Not recommended to be called from the main thread, as can cause jank. 80 nsresult Read(int64_t aOffset, uint8_t* aData, int32_t aLength) override; 81 82 // Moves a block asynchronously. Can be called on any thread. 83 // This defers file I/O to a non-main thread. 84 nsresult MoveBlock(int32_t aSourceBlockIndex, 85 int32_t aDestBlockIndex) override; 86 87 // Represents a change yet to be made to a block in the file. The change 88 // is either a write (and the data to be written is stored in this struct) 89 // or a move (and the index of the source block is stored instead). 90 struct BlockChange final { 91 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BlockChange) 92 93 // This block is waiting in memory to be written. 94 // Stores a copy of the block, so we can write it asynchronously. 95 explicit BlockChange(const uint8_t* aData) : mSourceBlockIndex(-1) { 96 mData = MakeUnique<uint8_t[]>(BLOCK_SIZE); 97 memcpy(mData.get(), aData, BLOCK_SIZE); 98 } 99 100 BlockChange(Span<const uint8_t> aData1, Span<const uint8_t> aData2) 101 : mSourceBlockIndex(-1) { 102 MOZ_ASSERT(aData1.Length() + aData2.Length() == BLOCK_SIZE); 103 mData = MakeUnique<uint8_t[]>(BLOCK_SIZE); 104 memcpy(mData.get(), aData1.Elements(), aData1.Length()); 105 memcpy(mData.get() + aData1.Length(), aData2.Elements(), aData2.Length()); 106 } 107 108 // This block's contents are located in another file 109 // block, i.e. this block has been moved. 110 explicit BlockChange(int32_t aSourceBlockIndex) 111 : mSourceBlockIndex(aSourceBlockIndex) {} 112 113 UniquePtr<uint8_t[]> mData; 114 const int32_t mSourceBlockIndex; 115 116 bool IsMove() const { return mSourceBlockIndex != -1; } 117 bool IsWrite() const { 118 return mSourceBlockIndex == -1 && mData.get() != nullptr; 119 } 120 121 private: 122 // Private destructor, to discourage deletion outside of Release(): 123 ~BlockChange() = default; 124 }; 125 126 private: 127 int64_t BlockIndexToOffset(int32_t aBlockIndex) { 128 return static_cast<int64_t>(aBlockIndex) * BLOCK_SIZE; 129 } 130 131 void SetCacheFile(PRFileDesc* aFD); 132 133 // Close file in thread and terminate thread. 134 void Close(); 135 136 // Performs block writes and block moves on its own thread. 137 void PerformBlockIOs(); 138 139 // Mutex which controls access to mFD and mFDCurrentPos. Don't hold 140 // mDataMutex while holding mFileMutex! mFileMutex must be owned 141 // while accessing any of the following data fields or methods. 142 Mutex mFileMutex; 143 // Moves a block already committed to file. 144 nsresult MoveBlockInFile(int32_t aSourceBlockIndex, int32_t aDestBlockIndex); 145 // Seeks file pointer. 146 nsresult Seek(int64_t aOffset); 147 // Reads data from file offset. 148 nsresult ReadFromFile(int64_t aOffset, uint8_t* aDest, int32_t aBytesToRead, 149 int32_t& aBytesRead); 150 nsresult WriteBlockToFile(int32_t aBlockIndex, const uint8_t* aBlockData); 151 // File descriptor we're writing to. This is created externally, but 152 // shutdown by us. 153 PRFileDesc* mFD MOZ_PT_GUARDED_BY(mFileMutex); 154 // The current file offset in the file. 155 int64_t mFDCurrentPos MOZ_GUARDED_BY(mFileMutex); 156 157 // Mutex which controls access to all data in this class, except mFD 158 // and mFDCurrentPos. Don't hold mDataMutex while holding mFileMutex! 159 // mDataMutex must be owned while accessing any of the following data 160 // fields or methods. 161 Mutex mDataMutex; 162 // Ensures we either are running the event to preform IO, or an event 163 // has been dispatched to preform the IO. 164 // mDataMutex must be owned while calling this. 165 void EnsureWriteScheduled(); 166 167 // Array of block changes to made. If mBlockChanges[offset/BLOCK_SIZE] == 168 // nullptr, then the block has no pending changes to be written, but if 169 // mBlockChanges[offset/BLOCK_SIZE] != nullptr, then either there's a block 170 // cached in memory waiting to be written, or this block is the target of a 171 // block move. 172 nsTArray<RefPtr<BlockChange> > mBlockChanges MOZ_GUARDED_BY(mDataMutex); 173 // Event target upon which block writes and block moves are performed. This is 174 // created upon open, and dropped on close. 175 nsCOMPtr<nsISerialEventTarget> mBackgroundET MOZ_GUARDED_BY(mDataMutex); 176 // Queue of pending block indexes that need to be written or moved. 177 std::deque<int32_t> mChangeIndexList MOZ_GUARDED_BY(mDataMutex); 178 // True if we've dispatched an event to commit all pending block changes 179 // to file on mBackgroundET. 180 bool mIsWriteScheduled MOZ_GUARDED_BY(mDataMutex); 181 // True when a read is happening. Pending writes may be postponed, to give 182 // higher priority to reads (which may be blocking the caller). 183 bool mIsReading MOZ_GUARDED_BY(mDataMutex); 184 // True if we've got a temporary file descriptor. Note: we don't use mFD 185 // directly as that's synchronized via mFileMutex and we need to make 186 // decisions about whether we can write while holding mDataMutex. 187 bool mInitialized MOZ_GUARDED_BY(mDataMutex) = false; 188 }; 189 190 } // End namespace mozilla. 191 192 #endif /* FILE_BLOCK_CACHE_H_ */