tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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_ */