tor-browser

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

MemoryBlockCache.cpp (8134B)


      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 #include "MemoryBlockCache.h"
      8 
      9 #include "mozilla/Atomics.h"
     10 #include "mozilla/ClearOnShutdown.h"
     11 #include "mozilla/Logging.h"
     12 #include "mozilla/Services.h"
     13 #include "mozilla/StaticPrefs_media.h"
     14 #include "nsWeakReference.h"
     15 #include "prsystem.h"
     16 
     17 namespace mozilla {
     18 
     19 #undef LOG
     20 LazyLogModule gMemoryBlockCacheLog("MemoryBlockCache");
     21 #define LOG(x, ...) \
     22  MOZ_LOG(gMemoryBlockCacheLog, LogLevel::Debug, ("%p " x, this, ##__VA_ARGS__))
     23 
     24 // Combined sizes of all MemoryBlockCache buffers.
     25 // Initialized to 0 by non-local static initialization.
     26 // Increases when a buffer grows (during initialization or unexpected OOB
     27 // writes), decreases when a MemoryBlockCache (with its buffer) is destroyed.
     28 static Atomic<size_t> gCombinedSizes;
     29 
     30 static int32_t CalculateMaxBlocks(int64_t aContentLength) {
     31  int64_t maxSize = int64_t(StaticPrefs::media_memory_cache_max_size()) * 1024;
     32  MOZ_ASSERT(aContentLength <= maxSize);
     33  MOZ_ASSERT(maxSize % MediaBlockCacheBase::BLOCK_SIZE == 0);
     34  // Note: It doesn't matter if calculations overflow, Init() would later fail.
     35  // We want at least enough blocks to contain the original content length.
     36  const int32_t requiredBlocks = maxSize / MediaBlockCacheBase::BLOCK_SIZE;
     37  // Allow at least 1s of ultra HD (25Mbps).
     38  const int32_t workableBlocks =
     39      25 * 1024 * 1024 / 8 / MediaBlockCacheBase::BLOCK_SIZE;
     40  return std::max(requiredBlocks, workableBlocks);
     41 }
     42 
     43 MemoryBlockCache::MemoryBlockCache(int64_t aContentLength)
     44    // Buffer whole blocks.
     45    : mInitialContentLength((aContentLength >= 0) ? size_t(aContentLength) : 0),
     46      mMaxBlocks(CalculateMaxBlocks(aContentLength)),
     47      mMutex("MemoryBlockCache"),
     48      mHasGrown(false) {
     49  if (aContentLength <= 0) {
     50    LOG("MemoryBlockCache() MEMORYBLOCKCACHE_ERRORS='InitUnderuse'");
     51  }
     52 }
     53 
     54 MemoryBlockCache::~MemoryBlockCache() {
     55  MOZ_ASSERT(gCombinedSizes >= mBuffer.Length());
     56  size_t sizes = static_cast<size_t>(gCombinedSizes -= mBuffer.Length());
     57  LOG("~MemoryBlockCache() - destroying buffer of size %zu; combined sizes now "
     58      "%zu",
     59      mBuffer.Length(), sizes);
     60 }
     61 
     62 bool MemoryBlockCache::EnsureBufferCanContain(size_t aContentLength) {
     63  mMutex.AssertCurrentThreadOwns();
     64  if (aContentLength == 0) {
     65    return true;
     66  }
     67  const size_t initialLength = mBuffer.Length();
     68  const size_t desiredLength =
     69      ((aContentLength - 1) / BLOCK_SIZE + 1) * BLOCK_SIZE;
     70  if (initialLength >= desiredLength) {
     71    // Already large enough.
     72    return true;
     73  }
     74  // Need larger buffer. If we are allowed more memory, attempt to re-allocate.
     75  const size_t extra = desiredLength - initialLength;
     76  // Only check the very first allocation against the combined MemoryBlockCache
     77  // limit. Further growths will always be allowed, assuming MediaCache won't
     78  // go over GetMaxBlocks() by too much.
     79  if (initialLength == 0) {
     80    // Note: There is a small race between testing `atomic + extra > limit` and
     81    // committing to it with `atomic += extra` below; but this is acceptable, as
     82    // in the worst case it may allow a small number of buffers to go past the
     83    // limit.
     84    // The alternative would have been to reserve the space first with
     85    // `atomic += extra` and then undo it with `atomic -= extra` in case of
     86    // failure; but this would have meant potentially preventing other (small
     87    // but successful) allocations.
     88    static const size_t sysmem =
     89        std::max<size_t>(PR_GetPhysicalMemorySize(), 32 * 1024 * 1024);
     90    const size_t limit = std::min(
     91        size_t(StaticPrefs::media_memory_caches_combined_limit_kb()) * 1024,
     92        sysmem * StaticPrefs::media_memory_caches_combined_limit_pc_sysmem() /
     93            100);
     94    const size_t currentSizes = static_cast<size_t>(gCombinedSizes);
     95    if (currentSizes + extra > limit) {
     96      LOG("EnsureBufferCanContain(%zu) - buffer size %zu, wanted + %zu = %zu;"
     97          " combined sizes %zu + %zu > limit %zu",
     98          aContentLength, initialLength, extra, desiredLength, currentSizes,
     99          extra, limit);
    100      return false;
    101    }
    102  }
    103  if (!mBuffer.SetLength(desiredLength, mozilla::fallible)) {
    104    LOG("EnsureBufferCanContain(%zu) - buffer size %zu, wanted + %zu = %zu, "
    105        "allocation failed",
    106        aContentLength, initialLength, extra, desiredLength);
    107    return false;
    108  }
    109  MOZ_ASSERT(mBuffer.Length() == desiredLength);
    110  const size_t capacity = mBuffer.Capacity();
    111  const size_t extraCapacity = capacity - desiredLength;
    112  if (extraCapacity != 0) {
    113    // Our buffer was given a larger capacity than the requested length, we may
    114    // as well claim that extra capacity, both for our accounting, and to
    115    // possibly bypass some future growths that would fit in this new capacity.
    116    mBuffer.SetLength(capacity);
    117  }
    118  const size_t newSizes = gCombinedSizes += (extra + extraCapacity);
    119  LOG("EnsureBufferCanContain(%zu) - buffer size %zu + requested %zu + bonus "
    120      "%zu = %zu; combined sizes %zu",
    121      aContentLength, initialLength, extra, extraCapacity, capacity, newSizes);
    122  mHasGrown = true;
    123  return true;
    124 }
    125 
    126 nsresult MemoryBlockCache::Init() {
    127  LOG("Init()");
    128  MutexAutoLock lock(mMutex);
    129  MOZ_ASSERT(mBuffer.IsEmpty());
    130  // Attempt to pre-allocate buffer for expected content length.
    131  if (!EnsureBufferCanContain(mInitialContentLength)) {
    132    LOG("Init() MEMORYBLOCKCACHE_ERRORS='InitAllocation'");
    133    return NS_ERROR_FAILURE;
    134  }
    135  return NS_OK;
    136 }
    137 
    138 void MemoryBlockCache::Flush() {
    139  LOG("Flush()");
    140  MutexAutoLock lock(mMutex);
    141  MOZ_ASSERT(mBuffer.Length() >= mInitialContentLength);
    142  memset(mBuffer.Elements(), 0, mBuffer.Length());
    143  mHasGrown = false;
    144 }
    145 
    146 nsresult MemoryBlockCache::WriteBlock(uint32_t aBlockIndex,
    147                                      Span<const uint8_t> aData1,
    148                                      Span<const uint8_t> aData2) {
    149  MutexAutoLock lock(mMutex);
    150 
    151  size_t offset = BlockIndexToOffset(aBlockIndex);
    152  if (offset + aData1.Length() + aData2.Length() > mBuffer.Length() &&
    153      !mHasGrown) {
    154    LOG("WriteBlock() MEMORYBLOCKCACHE_ERRORS='WriteBlockOverflow'");
    155  }
    156  if (!EnsureBufferCanContain(offset + aData1.Length() + aData2.Length())) {
    157    LOG("WriteBlock() MEMORYBLOCKCACHE_ERRORS='WriteBlockCannotGrow'");
    158    return NS_ERROR_FAILURE;
    159  }
    160 
    161  memcpy(mBuffer.Elements() + offset, aData1.Elements(), aData1.Length());
    162  if (aData2.Length() > 0) {
    163    memcpy(mBuffer.Elements() + offset + aData1.Length(), aData2.Elements(),
    164           aData2.Length());
    165  }
    166 
    167  return NS_OK;
    168 }
    169 
    170 nsresult MemoryBlockCache::Read(int64_t aOffset, uint8_t* aData,
    171                                int32_t aLength) {
    172  MutexAutoLock lock(mMutex);
    173 
    174  MOZ_ASSERT(aOffset >= 0);
    175  if (aOffset + aLength > int64_t(mBuffer.Length())) {
    176    LOG("Read() MEMORYBLOCKCACHE_ERRORS='ReadOverrun'");
    177    return NS_ERROR_FAILURE;
    178  }
    179 
    180  memcpy(aData, mBuffer.Elements() + aOffset, aLength);
    181  return NS_OK;
    182 }
    183 
    184 nsresult MemoryBlockCache::MoveBlock(int32_t aSourceBlockIndex,
    185                                     int32_t aDestBlockIndex) {
    186  MutexAutoLock lock(mMutex);
    187 
    188  size_t sourceOffset = BlockIndexToOffset(aSourceBlockIndex);
    189  size_t destOffset = BlockIndexToOffset(aDestBlockIndex);
    190  if (sourceOffset + BLOCK_SIZE > mBuffer.Length()) {
    191    LOG("MoveBlock() MEMORYBLOCKCACHE_ERRORS='MoveBlockSourceOverrun'");
    192    return NS_ERROR_FAILURE;
    193  }
    194  if (destOffset + BLOCK_SIZE > mBuffer.Length() && !mHasGrown) {
    195    LOG("MoveBlock() MEMORYBLOCKCACHE_ERRORS='MoveBlockDestOverflow'");
    196  }
    197  if (!EnsureBufferCanContain(destOffset + BLOCK_SIZE)) {
    198    LOG("MoveBlock() MEMORYBLOCKCACHE_ERRORS='MoveBlockCannotGrow'");
    199    return NS_ERROR_FAILURE;
    200  }
    201 
    202  memcpy(mBuffer.Elements() + destOffset, mBuffer.Elements() + sourceOffset,
    203         BLOCK_SIZE);
    204 
    205  return NS_OK;
    206 }
    207 
    208 }  // End namespace mozilla.
    209 
    210 // avoid redefined macro in unified build
    211 #undef LOG