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