DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Implementation

Mercurial (d96ee8115c5a)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef FILE_BLOCK_CACHE_H_
#define FILE_BLOCK_CACHE_H_

#include "mozilla/Attributes.h"
#include "mozilla/MozPromise.h"
#include "mozilla/Mutex.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/AbstractThread.h"
#include "nsTArray.h"
#include "MediaBlockCacheBase.h"
#include "nsDeque.h"
#include "nsThreadUtils.h"
#include <deque>

struct PRFileDesc;

namespace mozilla {

// Manages file I/O for the media cache. Data comes in over the network
// via callbacks on the main thread, however we don't want to write the
// incoming data to the media cache on the main thread, as this could block
// causing UI jank.
//
// So FileBlockCache provides an abstraction for a temporary file accessible
// as an array of blocks, which supports a block move operation, and
// allows synchronous reading and writing from any thread, with writes being
// buffered so as not to block.
//
// Writes and cache block moves (which require reading) are deferred to
// their own non-main thread. This object also ensures that data which has
// been scheduled to be written, but hasn't actually *been* written, is read
// as if it had, i.e. pending writes are cached in readable memory until
// they're flushed to file.
//
// To improve efficiency, writes can only be done at block granularity,
// whereas reads can be done with byte granularity.
//
// Note it's also recommended not to read from the media cache from the main
// thread to prevent jank.
//
// When WriteBlock() or MoveBlock() are called, data about how to complete
// the block change is added to mBlockChanges, indexed by block index, and
// the block index is appended to the mChangeIndexList. This enables
// us to quickly tell if a block has been changed, and ensures we can perform
// the changes in the correct order. An event is dispatched to perform the
// changes listed in mBlockChanges to file. Read() checks mBlockChanges and
// determines the current data to return, reading from file or from
// mBlockChanges as necessary.
class FileBlockCache : public MediaBlockCacheBase {
 public:
  FileBlockCache();

 protected:
  virtual ~FileBlockCache();

 public:
  // Launch thread and open temporary file.
  nsresult Init() override;

  // Will discard pending changes if any.
  void Flush() override;

  // Maximum number of blocks allowed in this block cache.
  // Calculated from "media.cache_size" pref.
  int32_t GetMaxBlocks() const override;

  // Can be called on any thread. This defers to a non-main thread.
  nsresult WriteBlock(uint32_t aBlockIndex, Span<const uint8_t> aData1,
                      Span<const uint8_t> aData2) override;

  // Synchronously reads data from file. May read from file or memory
  // depending on whether written blocks have been flushed to file yet.
  // Not recommended to be called from the main thread, as can cause jank.
  nsresult Read(int64_t aOffset, uint8_t* aData, int32_t aLength,
                int32_t* aBytes) override;

  // Moves a block asynchronously. Can be called on any thread.
  // This defers file I/O to a non-main thread.
  nsresult MoveBlock(int32_t aSourceBlockIndex,
                     int32_t aDestBlockIndex) override;

  // Represents a change yet to be made to a block in the file. The change
  // is either a write (and the data to be written is stored in this struct)
  // or a move (and the index of the source block is stored instead).
  struct BlockChange final {
    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BlockChange)

    // This block is waiting in memory to be written.
    // Stores a copy of the block, so we can write it asynchronously.
    explicit BlockChange(const uint8_t* aData) : mSourceBlockIndex(-1) {
      mData = MakeUnique<uint8_t[]>(BLOCK_SIZE);
      memcpy(mData.get(), aData, BLOCK_SIZE);
    }

    BlockChange(Span<const uint8_t> aData1, Span<const uint8_t> aData2)
        : mSourceBlockIndex(-1) {
      MOZ_ASSERT(aData1.Length() + aData2.Length() == BLOCK_SIZE);
      mData = MakeUnique<uint8_t[]>(BLOCK_SIZE);
      memcpy(mData.get(), aData1.Elements(), aData1.Length());
      memcpy(mData.get() + aData1.Length(), aData2.Elements(), aData2.Length());
    }

    // This block's contents are located in another file
    // block, i.e. this block has been moved.
    explicit BlockChange(int32_t aSourceBlockIndex)
        : mSourceBlockIndex(aSourceBlockIndex) {}

    UniquePtr<uint8_t[]> mData;
    const int32_t mSourceBlockIndex;

    bool IsMove() const { return mSourceBlockIndex != -1; }
    bool IsWrite() const {
      return mSourceBlockIndex == -1 && mData.get() != nullptr;
    }

   private:
    // Private destructor, to discourage deletion outside of Release():
    ~BlockChange() {}
  };

 private:
  int64_t BlockIndexToOffset(int32_t aBlockIndex) {
    return static_cast<int64_t>(aBlockIndex) * BLOCK_SIZE;
  }

  void SetCacheFile(PRFileDesc* aFD);

  // Close file in thread and terminate thread.
  void Close();

  // Performs block writes and block moves on its own thread.
  void PerformBlockIOs();

  // Mutex which controls access to mFD and mFDCurrentPos. Don't hold
  // mDataMutex while holding mFileMutex! mFileMutex must be owned
  // while accessing any of the following data fields or methods.
  Mutex mFileMutex;
  // Moves a block already committed to file.
  nsresult MoveBlockInFile(int32_t aSourceBlockIndex, int32_t aDestBlockIndex);
  // Seeks file pointer.
  nsresult Seek(int64_t aOffset);
  // Reads data from file offset.
  nsresult ReadFromFile(int64_t aOffset, uint8_t* aDest, int32_t aBytesToRead,
                        int32_t& aBytesRead);
  nsresult WriteBlockToFile(int32_t aBlockIndex, const uint8_t* aBlockData);
  // File descriptor we're writing to. This is created externally, but
  // shutdown by us.
  PRFileDesc* mFD;
  // The current file offset in the file.
  int64_t mFDCurrentPos;

  // Mutex which controls access to all data in this class, except mFD
  // and mFDCurrentPos. Don't hold mDataMutex while holding mFileMutex!
  // mDataMutex must be owned while accessing any of the following data
  // fields or methods.
  Mutex mDataMutex;
  // Ensures we either are running the event to preform IO, or an event
  // has been dispatched to preform the IO.
  // mDataMutex must be owned while calling this.
  void EnsureWriteScheduled();

  // Array of block changes to made. If mBlockChanges[offset/BLOCK_SIZE] ==
  // nullptr, then the block has no pending changes to be written, but if
  // mBlockChanges[offset/BLOCK_SIZE] != nullptr, then either there's a block
  // cached in memory waiting to be written, or this block is the target of a
  // block move.
  nsTArray<RefPtr<BlockChange> > mBlockChanges;
  // Thread upon which block writes and block moves are performed. This is
  // created upon open, and shutdown (asynchronously) upon close (on the
  // main thread).
  nsCOMPtr<nsIThread> mThread;
  // Queue of pending block indexes that need to be written or moved.
  std::deque<int32_t> mChangeIndexList;
  // True if we've dispatched an event to commit all pending block changes
  // to file on mThread.
  bool mIsWriteScheduled;
  // True when a read is happening. Pending writes may be postponed, to give
  // higher priority to reads (which may be blocking the caller).
  bool mIsReading;
  // True if we've got a temporary file descriptor. Note: we don't use mFD
  // directly as that's synchronized via mFileMutex and we need to make
  // decisions about whether we can write while holding mDataMutex.
  bool mInitialized = false;
};

}  // End namespace mozilla.

#endif /* FILE_BLOCK_CACHE_H_ */