Line data Source code
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 "MediaPrefs.h"
10 : #include "mozilla/Atomics.h"
11 : #include "mozilla/ClearOnShutdown.h"
12 : #include "mozilla/Logging.h"
13 : #include "mozilla/Telemetry.h"
14 : #include "mozilla/Services.h"
15 : #include "nsIObserver.h"
16 : #include "nsIObserverService.h"
17 : #include "nsWeakReference.h"
18 : #include "prsystem.h"
19 :
20 : namespace mozilla {
21 :
22 : #undef LOG
23 : LazyLogModule gMemoryBlockCacheLog("MemoryBlockCache");
24 : #define LOG(x, ...) \
25 : MOZ_LOG(gMemoryBlockCacheLog, LogLevel::Debug, ("%p " x, this, ##__VA_ARGS__))
26 :
27 : // Combined sizes of all MemoryBlockCache buffers.
28 : // Initialized to 0 by non-local static initialization.
29 : // Increases when a buffer grows (during initialization or unexpected OOB
30 : // writes), decreases when a MemoryBlockCache (with its buffer) is destroyed.
31 : static Atomic<size_t> gCombinedSizes;
32 :
33 : class MemoryBlockCacheTelemetry final
34 : : public nsIObserver
35 : , public nsSupportsWeakReference
36 : {
37 : public:
38 : NS_DECL_ISUPPORTS
39 : NS_DECL_NSIOBSERVER
40 :
41 : // To be called when the combined size has grown, so that the watermark may
42 : // be updated if needed.
43 : // Ensures MemoryBlockCache telemetry will be reported at shutdown.
44 : // Returns current watermark.
45 : static size_t NotifyCombinedSizeGrown(size_t aNewSize);
46 :
47 : private:
48 0 : MemoryBlockCacheTelemetry() {}
49 0 : ~MemoryBlockCacheTelemetry() {}
50 :
51 : // Singleton instance created when a first MediaCache is registered, and
52 : // released when the last MediaCache is unregistered.
53 : // The observer service will keep a weak reference to it, for notifications.
54 : static StaticRefPtr<MemoryBlockCacheTelemetry> gMemoryBlockCacheTelemetry;
55 :
56 : // Watermark for the combined sizes; can only increase when a buffer grows.
57 : static Atomic<size_t> gCombinedSizesWatermark;
58 : };
59 :
60 : // Initialized to nullptr by non-local static initialization.
61 : /* static */ StaticRefPtr<MemoryBlockCacheTelemetry>
62 3 : MemoryBlockCacheTelemetry::gMemoryBlockCacheTelemetry;
63 :
64 : // Initialized to 0 by non-local static initialization.
65 : /* static */ Atomic<size_t> MemoryBlockCacheTelemetry::gCombinedSizesWatermark;
66 :
67 0 : NS_IMPL_ISUPPORTS(MemoryBlockCacheTelemetry,
68 : nsIObserver,
69 : nsISupportsWeakReference)
70 :
71 : /* static */ size_t
72 0 : MemoryBlockCacheTelemetry::NotifyCombinedSizeGrown(size_t aNewSize)
73 : {
74 0 : NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
75 :
76 : // Ensure gMemoryBlockCacheTelemetry exists.
77 0 : if (!gMemoryBlockCacheTelemetry) {
78 0 : gMemoryBlockCacheTelemetry = new MemoryBlockCacheTelemetry();
79 :
80 : nsCOMPtr<nsIObserverService> observerService =
81 0 : mozilla::services::GetObserverService();
82 0 : if (observerService) {
83 0 : observerService->AddObserver(
84 0 : gMemoryBlockCacheTelemetry, "profile-change-teardown", true);
85 : }
86 :
87 : // Clearing gMemoryBlockCacheTelemetry when handling
88 : // "profile-change-teardown" could run the risk of re-creating it (and then
89 : // leaking it) if some MediaCache work happened after that notification.
90 : // So instead we just request it to be cleared on final shutdown.
91 0 : ClearOnShutdown(&gMemoryBlockCacheTelemetry);
92 : }
93 :
94 : // Update watermark if needed, report current watermark.
95 : for (;;) {
96 0 : size_t oldSize = gMemoryBlockCacheTelemetry->gCombinedSizesWatermark;
97 0 : if (aNewSize < oldSize) {
98 0 : return oldSize;
99 : }
100 0 : if (gMemoryBlockCacheTelemetry->gCombinedSizesWatermark.compareExchange(
101 : oldSize, aNewSize)) {
102 0 : return aNewSize;
103 : }
104 0 : }
105 : }
106 :
107 : NS_IMETHODIMP
108 0 : MemoryBlockCacheTelemetry::Observe(nsISupports* aSubject,
109 : char const* aTopic,
110 : char16_t const* aData)
111 : {
112 0 : NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
113 :
114 0 : if (strcmp(aTopic, "profile-change-teardown") == 0) {
115 0 : uint32_t watermark = static_cast<uint32_t>(gCombinedSizesWatermark);
116 0 : LOG("MemoryBlockCacheTelemetry::~Observe() "
117 : "MEDIACACHE_MEMORY_WATERMARK=%" PRIu32,
118 : watermark);
119 : Telemetry::Accumulate(Telemetry::HistogramID::MEDIACACHE_MEMORY_WATERMARK,
120 0 : watermark);
121 0 : return NS_OK;
122 : }
123 0 : return NS_OK;
124 : }
125 :
126 : enum MemoryBlockCacheTelemetryErrors
127 : {
128 : // Don't change order/numbers! Add new values at the end and update
129 : // MEMORYBLOCKCACHE_ERRORS description in Histograms.json.
130 : InitUnderuse = 0,
131 : InitAllocation = 1,
132 : ReadOverrun = 2,
133 : WriteBlockOverflow = 3,
134 : WriteBlockCannotGrow = 4,
135 : MoveBlockSourceOverrun = 5,
136 : MoveBlockDestOverflow = 6,
137 : MoveBlockCannotGrow = 7,
138 : };
139 :
140 : static int32_t
141 0 : CalculateMaxBlocks(int64_t aContentLength)
142 : {
143 : // Note: It doesn't matter if calculations overflow, Init() would later fail.
144 : // We want at least enough blocks to contain the original content length.
145 : const int32_t requiredBlocks =
146 0 : int32_t((aContentLength - 1) / MediaBlockCacheBase::BLOCK_SIZE + 1);
147 : // Allow at least 1s of ultra HD (25Mbps).
148 : const int32_t workableBlocks =
149 0 : 25 * 1024 * 1024 / 8 / MediaBlockCacheBase::BLOCK_SIZE;
150 0 : return std::max(requiredBlocks, workableBlocks);
151 : }
152 :
153 0 : MemoryBlockCache::MemoryBlockCache(int64_t aContentLength)
154 : // Buffer whole blocks.
155 0 : : mInitialContentLength((aContentLength >= 0) ? size_t(aContentLength) : 0)
156 0 : , mMaxBlocks(CalculateMaxBlocks(aContentLength))
157 : , mMutex("MemoryBlockCache")
158 0 : , mHasGrown(false)
159 : {
160 0 : if (aContentLength <= 0) {
161 0 : LOG("MemoryBlockCache() MEMORYBLOCKCACHE_ERRORS='InitUnderuse'");
162 : Telemetry::Accumulate(Telemetry::HistogramID::MEMORYBLOCKCACHE_ERRORS,
163 0 : InitUnderuse);
164 : }
165 0 : }
166 :
167 0 : MemoryBlockCache::~MemoryBlockCache()
168 : {
169 0 : size_t sizes = static_cast<size_t>(gCombinedSizes -= mBuffer.Length());
170 0 : LOG("~MemoryBlockCache() - destroying buffer of size %zu; combined sizes now "
171 : "%zu",
172 : mBuffer.Length(),
173 : sizes);
174 0 : }
175 :
176 : bool
177 0 : MemoryBlockCache::EnsureBufferCanContain(size_t aContentLength)
178 : {
179 0 : mMutex.AssertCurrentThreadOwns();
180 0 : if (aContentLength == 0) {
181 0 : return true;
182 : }
183 0 : const size_t initialLength = mBuffer.Length();
184 : const size_t desiredLength =
185 0 : ((aContentLength - 1) / BLOCK_SIZE + 1) * BLOCK_SIZE;
186 0 : if (initialLength >= desiredLength) {
187 : // Already large enough.
188 0 : return true;
189 : }
190 : // Need larger buffer. If we are allowed more memory, attempt to re-allocate.
191 0 : const size_t extra = desiredLength - initialLength;
192 : // Only check the very first allocation against the combined MemoryBlockCache
193 : // limit. Further growths will always be allowed, assuming MediaCache won't
194 : // go over GetMaxBlocks() by too much.
195 0 : if (initialLength == 0) {
196 : // Note: There is a small race between testing `atomic + extra > limit` and
197 : // committing to it with `atomic += extra` below; but this is acceptable, as
198 : // in the worst case it may allow a small number of buffers to go past the
199 : // limit.
200 : // The alternative would have been to reserve the space first with
201 : // `atomic += extra` and then undo it with `atomic -= extra` in case of
202 : // failure; but this would have meant potentially preventing other (small
203 : // but successful) allocations.
204 : static const size_t sysmem =
205 0 : std::max<size_t>(PR_GetPhysicalMemorySize(), 32 * 1024 * 1024);
206 : const size_t limit = std::min(
207 0 : size_t(MediaPrefs::MediaMemoryCachesCombinedLimitKb()) * 1024,
208 0 : sysmem * MediaPrefs::MediaMemoryCachesCombinedLimitPcSysmem() / 100);
209 0 : const size_t currentSizes = static_cast<size_t>(gCombinedSizes);
210 0 : if (currentSizes + extra > limit) {
211 0 : LOG("EnsureBufferCanContain(%zu) - buffer size %zu, wanted + %zu = %zu;"
212 : " combined sizes %zu + %zu > limit %zu",
213 : aContentLength,
214 : initialLength,
215 : extra,
216 : desiredLength,
217 : currentSizes,
218 : extra,
219 : limit);
220 0 : return false;
221 : }
222 : }
223 0 : if (!mBuffer.SetLength(desiredLength, mozilla::fallible)) {
224 0 : LOG("EnsureBufferCanContain(%zu) - buffer size %zu, wanted + %zu = %zu, "
225 : "allocation failed",
226 : aContentLength,
227 : initialLength,
228 : extra,
229 : desiredLength);
230 0 : return false;
231 : }
232 0 : MOZ_ASSERT(mBuffer.Length() == desiredLength);
233 0 : const size_t capacity = mBuffer.Capacity();
234 0 : const size_t extraCapacity = capacity - desiredLength;
235 0 : if (extraCapacity != 0) {
236 : // Our buffer was given a larger capacity than the requested length, we may
237 : // as well claim that extra capacity, both for our accounting, and to
238 : // possibly bypass some future growths that would fit in this new capacity.
239 0 : mBuffer.SetLength(capacity);
240 : }
241 : size_t newSizes =
242 0 : static_cast<size_t>(gCombinedSizes += (extra + extraCapacity));
243 : size_t watermark =
244 0 : MemoryBlockCacheTelemetry::NotifyCombinedSizeGrown(newSizes);
245 0 : LOG("EnsureBufferCanContain(%zu) - buffer size %zu + requested %zu + bonus "
246 : "%zu = %zu; combined "
247 : "sizes %zu, watermark %zu",
248 : aContentLength,
249 : initialLength,
250 : extra,
251 : extraCapacity,
252 : capacity,
253 : newSizes,
254 : watermark);
255 0 : mHasGrown = true;
256 0 : return true;
257 : }
258 :
259 : nsresult
260 0 : MemoryBlockCache::Init()
261 : {
262 0 : MutexAutoLock lock(mMutex);
263 0 : if (mBuffer.IsEmpty()) {
264 0 : LOG("Init()");
265 : // Attempt to pre-allocate buffer for expected content length.
266 0 : if (!EnsureBufferCanContain(mInitialContentLength)) {
267 0 : LOG("Init() MEMORYBLOCKCACHE_ERRORS='InitAllocation'");
268 : Telemetry::Accumulate(Telemetry::HistogramID::MEMORYBLOCKCACHE_ERRORS,
269 0 : InitAllocation);
270 0 : return NS_ERROR_FAILURE;
271 : }
272 : } else {
273 0 : LOG("Init() again");
274 : // Re-initialization - Just erase data.
275 0 : MOZ_ASSERT(mBuffer.Length() >= mInitialContentLength);
276 0 : memset(mBuffer.Elements(), 0, mBuffer.Length());
277 : }
278 : // Ignore initial growth.
279 0 : mHasGrown = false;
280 0 : return NS_OK;
281 : }
282 :
283 : nsresult
284 0 : MemoryBlockCache::WriteBlock(uint32_t aBlockIndex,
285 : Span<const uint8_t> aData1,
286 : Span<const uint8_t> aData2)
287 : {
288 0 : MutexAutoLock lock(mMutex);
289 :
290 0 : size_t offset = BlockIndexToOffset(aBlockIndex);
291 0 : if (offset + aData1.Length() + aData2.Length() > mBuffer.Length() &&
292 0 : !mHasGrown) {
293 0 : LOG("WriteBlock() MEMORYBLOCKCACHE_ERRORS='WriteBlockOverflow'");
294 : Telemetry::Accumulate(Telemetry::HistogramID::MEMORYBLOCKCACHE_ERRORS,
295 0 : WriteBlockOverflow);
296 : }
297 0 : if (!EnsureBufferCanContain(offset + aData1.Length() + aData2.Length())) {
298 0 : LOG("WriteBlock() MEMORYBLOCKCACHE_ERRORS='WriteBlockCannotGrow'");
299 : Telemetry::Accumulate(Telemetry::HistogramID::MEMORYBLOCKCACHE_ERRORS,
300 0 : WriteBlockCannotGrow);
301 0 : return NS_ERROR_FAILURE;
302 : }
303 :
304 0 : memcpy(mBuffer.Elements() + offset, aData1.Elements(), aData1.Length());
305 0 : if (aData2.Length() > 0) {
306 0 : memcpy(mBuffer.Elements() + offset + aData1.Length(),
307 0 : aData2.Elements(),
308 0 : aData2.Length());
309 : }
310 :
311 0 : return NS_OK;
312 : }
313 :
314 : nsresult
315 0 : MemoryBlockCache::Read(int64_t aOffset,
316 : uint8_t* aData,
317 : int32_t aLength,
318 : int32_t* aBytes)
319 : {
320 0 : MutexAutoLock lock(mMutex);
321 :
322 0 : MOZ_ASSERT(aOffset >= 0);
323 0 : if (aOffset + aLength > int64_t(mBuffer.Length())) {
324 0 : LOG("Read() MEMORYBLOCKCACHE_ERRORS='ReadOverrun'");
325 : Telemetry::Accumulate(Telemetry::HistogramID::MEMORYBLOCKCACHE_ERRORS,
326 0 : ReadOverrun);
327 0 : return NS_ERROR_FAILURE;
328 : }
329 :
330 0 : memcpy(aData, mBuffer.Elements() + aOffset, aLength);
331 0 : *aBytes = aLength;
332 :
333 0 : return NS_OK;
334 : }
335 :
336 : nsresult
337 0 : MemoryBlockCache::MoveBlock(int32_t aSourceBlockIndex, int32_t aDestBlockIndex)
338 : {
339 0 : MutexAutoLock lock(mMutex);
340 :
341 0 : size_t sourceOffset = BlockIndexToOffset(aSourceBlockIndex);
342 0 : size_t destOffset = BlockIndexToOffset(aDestBlockIndex);
343 0 : if (sourceOffset + BLOCK_SIZE > mBuffer.Length()) {
344 0 : LOG("MoveBlock() MEMORYBLOCKCACHE_ERRORS='MoveBlockSourceOverrun'");
345 : Telemetry::Accumulate(Telemetry::HistogramID::MEMORYBLOCKCACHE_ERRORS,
346 0 : MoveBlockSourceOverrun);
347 0 : return NS_ERROR_FAILURE;
348 : }
349 0 : if (destOffset + BLOCK_SIZE > mBuffer.Length() && !mHasGrown) {
350 0 : LOG("MoveBlock() MEMORYBLOCKCACHE_ERRORS='MoveBlockDestOverflow'");
351 : Telemetry::Accumulate(Telemetry::HistogramID::MEMORYBLOCKCACHE_ERRORS,
352 0 : MoveBlockDestOverflow);
353 : }
354 0 : if (!EnsureBufferCanContain(destOffset + BLOCK_SIZE)) {
355 0 : LOG("MoveBlock() MEMORYBLOCKCACHE_ERRORS='MoveBlockCannotGrow'");
356 : Telemetry::Accumulate(Telemetry::HistogramID::MEMORYBLOCKCACHE_ERRORS,
357 0 : MoveBlockCannotGrow);
358 0 : return NS_ERROR_FAILURE;
359 : }
360 :
361 0 : memcpy(mBuffer.Elements() + destOffset,
362 0 : mBuffer.Elements() + sourceOffset,
363 0 : BLOCK_SIZE);
364 :
365 0 : return NS_OK;
366 : }
367 :
368 9 : } // End namespace mozilla.
369 :
370 : // avoid redefined macro in unified build
371 : #undef LOG
|