no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / dom / media / FileBlockCache.cpp
blob3989c0583356aa2d242a7caa9b281ed09cfbf415
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/. */
7 #include "FileBlockCache.h"
8 #include "MediaCache.h"
9 #include "VideoUtils.h"
10 #include "prio.h"
11 #include <algorithm>
12 #include "nsAnonymousTemporaryFile.h"
13 #include "nsIThreadManager.h"
14 #include "mozilla/dom/ContentChild.h"
15 #include "mozilla/ScopeExit.h"
16 #include "nsXULAppAPI.h"
18 namespace mozilla {
20 #undef LOG
21 LazyLogModule gFileBlockCacheLog("FileBlockCache");
22 #define LOG(x, ...) \
23 MOZ_LOG(gFileBlockCacheLog, LogLevel::Debug, ("%p " x, this, ##__VA_ARGS__))
25 static void CloseFD(PRFileDesc* aFD) {
26 PRStatus prrc;
27 prrc = PR_Close(aFD);
28 if (prrc != PR_SUCCESS) {
29 NS_WARNING("PR_Close() failed.");
33 void FileBlockCache::SetCacheFile(PRFileDesc* aFD) {
34 LOG("SetCacheFile aFD=%p", aFD);
35 if (!aFD) {
36 // Failed to get a temporary file. Shutdown.
37 Close();
38 return;
41 MutexAutoLock lock(mFileMutex);
42 mFD = aFD;
45 MutexAutoLock lock(mDataMutex);
46 LOG("SetFileCache mBackgroundET=%p, mIsWriteScheduled %d",
47 mBackgroundET.get(), mIsWriteScheduled);
48 if (mBackgroundET) {
49 // Still open, complete the initialization.
50 mInitialized = true;
51 if (mIsWriteScheduled) {
52 // A write was scheduled while waiting for FD. We need to run/dispatch a
53 // task to service the request.
54 nsCOMPtr<nsIRunnable> event = mozilla::NewRunnableMethod(
55 "FileBlockCache::SetCacheFile -> PerformBlockIOs", this,
56 &FileBlockCache::PerformBlockIOs);
57 mBackgroundET->Dispatch(event.forget(), NS_DISPATCH_EVENT_MAY_BLOCK);
59 return;
62 // We've been closed while waiting for the file descriptor.
63 // Close the file descriptor we've just received, if still there.
64 MutexAutoLock lock(mFileMutex);
65 if (mFD) {
66 CloseFD(mFD);
67 mFD = nullptr;
71 nsresult FileBlockCache::Init() {
72 LOG("Init()");
73 MutexAutoLock mon(mDataMutex);
74 MOZ_ASSERT(!mBackgroundET);
75 nsresult rv = NS_CreateBackgroundTaskQueue("FileBlockCache",
76 getter_AddRefs(mBackgroundET));
77 if (NS_FAILED(rv)) {
78 return rv;
81 if (XRE_IsParentProcess()) {
82 RefPtr<FileBlockCache> self = this;
83 rv = mBackgroundET->Dispatch(
84 NS_NewRunnableFunction("FileBlockCache::Init",
85 [self] {
86 PRFileDesc* fd = nullptr;
87 nsresult rv =
88 NS_OpenAnonymousTemporaryFile(&fd);
89 if (NS_SUCCEEDED(rv)) {
90 self->SetCacheFile(fd);
91 } else {
92 self->Close();
94 }),
95 NS_DISPATCH_EVENT_MAY_BLOCK);
96 } else {
97 // We must request a temporary file descriptor from the parent process.
98 RefPtr<FileBlockCache> self = this;
99 rv = dom::ContentChild::GetSingleton()->AsyncOpenAnonymousTemporaryFile(
100 [self](PRFileDesc* aFD) { self->SetCacheFile(aFD); });
103 if (NS_FAILED(rv)) {
104 Close();
107 return rv;
110 void FileBlockCache::Flush() {
111 LOG("Flush()");
112 MutexAutoLock mon(mDataMutex);
113 MOZ_ASSERT(mBackgroundET);
115 // Dispatch a task so we won't clear the arrays while PerformBlockIOs() is
116 // dropping the data lock and cause InvalidArrayIndex.
117 RefPtr<FileBlockCache> self = this;
118 mBackgroundET->Dispatch(
119 NS_NewRunnableFunction("FileBlockCache::Flush", [self]() {
120 MutexAutoLock mon(self->mDataMutex);
121 // Just discard pending changes, assume MediaCache won't read from
122 // blocks it hasn't written to.
123 self->mChangeIndexList.clear();
124 self->mBlockChanges.Clear();
125 }));
128 size_t FileBlockCache::GetMaxBlocks(size_t aCacheSizeInKB) const {
129 // We look up the cache size every time. This means dynamic changes
130 // to the pref are applied.
131 // Ensure we can divide BLOCK_SIZE by 1024.
132 static_assert(MediaCacheStream::BLOCK_SIZE % 1024 == 0,
133 "BLOCK_SIZE should be a multiple of 1024");
134 // Ensure BLOCK_SIZE/1024 is at least 2.
135 static_assert(MediaCacheStream::BLOCK_SIZE / 1024 >= 2,
136 "BLOCK_SIZE / 1024 should be at least 2");
137 // Ensure we can convert BLOCK_SIZE/1024 to a uint32_t without truncation.
138 static_assert(MediaCacheStream::BLOCK_SIZE / 1024 <= int64_t(UINT32_MAX),
139 "BLOCK_SIZE / 1024 should be at most UINT32_MAX");
140 // Since BLOCK_SIZE is a strict multiple of 1024,
141 // aCacheSizeInKB * 1024 / BLOCK_SIZE == aCacheSizeInKB / (BLOCK_SIZE /
142 // 1024), but the latter formula avoids a potential overflow from `* 1024`.
143 // And because BLOCK_SIZE/1024 is at least 2, the maximum cache size
144 // INT32_MAX*2 will give a maxBlocks that can fit in an int32_t.
145 constexpr size_t blockSizeKb = size_t(MediaCacheStream::BLOCK_SIZE / 1024);
146 const size_t maxBlocks = aCacheSizeInKB / blockSizeKb;
147 return std::max(maxBlocks, size_t(1));
150 FileBlockCache::FileBlockCache()
151 : mFileMutex("MediaCache.Writer.IO.Mutex"),
152 mFD(nullptr),
153 mFDCurrentPos(0),
154 mDataMutex("MediaCache.Writer.Data.Mutex"),
155 mIsWriteScheduled(false),
156 mIsReading(false) {}
158 FileBlockCache::~FileBlockCache() { Close(); }
160 void FileBlockCache::Close() {
161 LOG("Close()");
163 nsCOMPtr<nsISerialEventTarget> thread;
165 MutexAutoLock mon(mDataMutex);
166 if (!mBackgroundET) {
167 return;
169 thread.swap(mBackgroundET);
172 PRFileDesc* fd;
174 MutexAutoLock lock(mFileMutex);
175 fd = mFD;
176 mFD = nullptr;
179 // Let the thread close the FD, and then trigger its own shutdown.
180 // Note that mBackgroundET is now empty, so no other task will be posted
181 // there. Also mBackgroundET and mFD are empty and therefore can be reused
182 // immediately.
183 nsresult rv = thread->Dispatch(NS_NewRunnableFunction("FileBlockCache::Close",
184 [thread, fd] {
185 if (fd) {
186 CloseFD(fd);
188 // No need to shutdown
189 // background task
190 // queues.
192 NS_DISPATCH_EVENT_MAY_BLOCK);
193 NS_ENSURE_SUCCESS_VOID(rv);
196 template <typename Container, typename Value>
197 bool ContainerContains(const Container& aContainer, const Value& value) {
198 return std::find(aContainer.begin(), aContainer.end(), value) !=
199 aContainer.end();
202 nsresult FileBlockCache::WriteBlock(uint32_t aBlockIndex,
203 Span<const uint8_t> aData1,
204 Span<const uint8_t> aData2) {
205 MutexAutoLock mon(mDataMutex);
207 if (!mBackgroundET) {
208 return NS_ERROR_FAILURE;
211 // Check if we've already got a pending write scheduled for this block.
212 mBlockChanges.EnsureLengthAtLeast(aBlockIndex + 1);
213 bool blockAlreadyHadPendingChange = mBlockChanges[aBlockIndex] != nullptr;
214 mBlockChanges[aBlockIndex] = new BlockChange(aData1, aData2);
216 if (!blockAlreadyHadPendingChange ||
217 !ContainerContains(mChangeIndexList, aBlockIndex)) {
218 // We either didn't already have a pending change for this block, or we
219 // did but we didn't have an entry for it in mChangeIndexList (we're in the
220 // process of writing it and have removed the block's index out of
221 // mChangeIndexList in Run() but not finished writing the block to file
222 // yet). Add the blocks index to the end of mChangeIndexList to ensure the
223 // block is written as as soon as possible.
224 mChangeIndexList.push_back(aBlockIndex);
226 NS_ASSERTION(ContainerContains(mChangeIndexList, aBlockIndex),
227 "Must have entry for new block");
229 EnsureWriteScheduled();
231 return NS_OK;
234 void FileBlockCache::EnsureWriteScheduled() {
235 mDataMutex.AssertCurrentThreadOwns();
236 MOZ_ASSERT(mBackgroundET);
238 if (mIsWriteScheduled || mIsReading) {
239 return;
241 mIsWriteScheduled = true;
242 if (!mInitialized) {
243 // We're still waiting on a file descriptor. When it arrives,
244 // the write will be scheduled.
245 return;
247 nsCOMPtr<nsIRunnable> event = mozilla::NewRunnableMethod(
248 "FileBlockCache::EnsureWriteScheduled -> PerformBlockIOs", this,
249 &FileBlockCache::PerformBlockIOs);
250 mBackgroundET->Dispatch(event.forget(), NS_DISPATCH_EVENT_MAY_BLOCK);
253 nsresult FileBlockCache::Seek(int64_t aOffset) {
254 mFileMutex.AssertCurrentThreadOwns();
256 if (mFDCurrentPos != aOffset) {
257 MOZ_ASSERT(mFD);
258 int64_t result = PR_Seek64(mFD, aOffset, PR_SEEK_SET);
259 if (result != aOffset) {
260 NS_WARNING("Failed to seek media cache file");
261 return NS_ERROR_FAILURE;
263 mFDCurrentPos = result;
265 return NS_OK;
268 nsresult FileBlockCache::ReadFromFile(int64_t aOffset, uint8_t* aDest,
269 int32_t aBytesToRead,
270 int32_t& aBytesRead) {
271 LOG("ReadFromFile(offset=%" PRIu64 ", len=%u)", aOffset, aBytesToRead);
272 mFileMutex.AssertCurrentThreadOwns();
273 MOZ_ASSERT(mFD);
275 nsresult res = Seek(aOffset);
276 if (NS_FAILED(res)) return res;
278 aBytesRead = PR_Read(mFD, aDest, aBytesToRead);
279 if (aBytesRead <= 0) return NS_ERROR_FAILURE;
280 mFDCurrentPos += aBytesRead;
282 return NS_OK;
285 nsresult FileBlockCache::WriteBlockToFile(int32_t aBlockIndex,
286 const uint8_t* aBlockData) {
287 LOG("WriteBlockToFile(index=%u)", aBlockIndex);
289 mFileMutex.AssertCurrentThreadOwns();
290 MOZ_ASSERT(mFD);
292 nsresult rv = Seek(BlockIndexToOffset(aBlockIndex));
293 if (NS_FAILED(rv)) return rv;
295 int32_t amount = PR_Write(mFD, aBlockData, BLOCK_SIZE);
296 if (amount < BLOCK_SIZE) {
297 NS_WARNING("Failed to write media cache block!");
298 return NS_ERROR_FAILURE;
300 mFDCurrentPos += BLOCK_SIZE;
302 return NS_OK;
305 nsresult FileBlockCache::MoveBlockInFile(int32_t aSourceBlockIndex,
306 int32_t aDestBlockIndex) {
307 LOG("MoveBlockInFile(src=%u, dest=%u)", aSourceBlockIndex, aDestBlockIndex);
309 mFileMutex.AssertCurrentThreadOwns();
311 uint8_t buf[BLOCK_SIZE];
312 int32_t bytesRead = 0;
313 if (NS_FAILED(ReadFromFile(BlockIndexToOffset(aSourceBlockIndex), buf,
314 BLOCK_SIZE, bytesRead))) {
315 return NS_ERROR_FAILURE;
317 return WriteBlockToFile(aDestBlockIndex, buf);
320 void FileBlockCache::PerformBlockIOs() {
321 MutexAutoLock mon(mDataMutex);
322 MOZ_ASSERT(mBackgroundET->IsOnCurrentThread());
323 NS_ASSERTION(mIsWriteScheduled, "Should report write running or scheduled.");
325 LOG("Run() mFD=%p mBackgroundET=%p", mFD, mBackgroundET.get());
327 while (!mChangeIndexList.empty()) {
328 if (!mBackgroundET) {
329 // We've been closed, abort, discarding unwritten changes.
330 mIsWriteScheduled = false;
331 return;
334 if (mIsReading) {
335 // We're trying to read; postpone all writes. (Reader will resume writes.)
336 mIsWriteScheduled = false;
337 return;
340 // Process each pending change. We pop the index out of the change
341 // list, but leave the BlockChange in mBlockChanges until the change
342 // is written to file. This is so that any read which happens while
343 // we drop mDataMutex to write will refer to the data's source in
344 // memory, rather than the not-yet up to date data written to file.
345 // This also ensures we will insert a new index into mChangeIndexList
346 // when this happens.
348 // Hold a reference to the change, in case another change
349 // overwrites the mBlockChanges entry for this block while we drop
350 // mDataMutex to take mFileMutex.
351 int32_t blockIndex = mChangeIndexList.front();
352 RefPtr<BlockChange> change = mBlockChanges[blockIndex];
353 MOZ_ASSERT(change,
354 "Change index list should only contain entries for blocks "
355 "with changes");
357 MutexAutoUnlock unlock(mDataMutex);
358 MutexAutoLock lock(mFileMutex);
359 if (!mFD) {
360 // We may be here if mFD has been reset because we're closing, so we
361 // don't care anymore about writes.
362 return;
364 if (change->IsWrite()) {
365 WriteBlockToFile(blockIndex, change->mData.get());
366 } else if (change->IsMove()) {
367 MoveBlockInFile(change->mSourceBlockIndex, blockIndex);
370 mChangeIndexList.pop_front(); // MonitorAutoUnlock above
371 // If a new change has not been made to the block while we dropped
372 // mDataMutex, clear reference to the old change. Otherwise, the old
373 // reference has been cleared already.
374 if (mBlockChanges[blockIndex] == change) { // MonitorAutoUnlock above
375 mBlockChanges[blockIndex] = nullptr; // MonitorAutoUnlock above
379 mIsWriteScheduled = false;
382 nsresult FileBlockCache::Read(int64_t aOffset, uint8_t* aData, int32_t aLength,
383 int32_t* aBytes) {
384 MutexAutoLock mon(mDataMutex);
386 if (!mBackgroundET || (aOffset / BLOCK_SIZE) > INT32_MAX) {
387 return NS_ERROR_FAILURE;
390 mIsReading = true;
391 auto exitRead = MakeScopeExit([&] {
392 mDataMutex.AssertCurrentThreadOwns();
393 mIsReading = false;
394 if (!mChangeIndexList.empty()) {
395 // mReading has stopped or prevented pending writes, resume them.
396 EnsureWriteScheduled();
400 int32_t bytesToRead = aLength;
401 int64_t offset = aOffset;
402 uint8_t* dst = aData;
403 while (bytesToRead > 0) {
404 int32_t blockIndex = static_cast<int32_t>(offset / BLOCK_SIZE);
405 int32_t start = offset % BLOCK_SIZE;
406 int32_t amount = std::min(BLOCK_SIZE - start, bytesToRead);
408 // If the block is not yet written to file, we can just read from
409 // the memory buffer, otherwise we need to read from file.
410 int32_t bytesRead = 0;
411 MOZ_ASSERT(!mBlockChanges.IsEmpty());
412 MOZ_ASSERT(blockIndex >= 0 &&
413 static_cast<uint32_t>(blockIndex) < mBlockChanges.Length());
414 RefPtr<BlockChange> change = mBlockChanges.SafeElementAt(blockIndex);
415 if (change && change->IsWrite()) {
416 // Block isn't yet written to file. Read from memory buffer.
417 const uint8_t* blockData = change->mData.get();
418 memcpy(dst, blockData + start, amount);
419 bytesRead = amount;
420 } else {
421 if (change && change->IsMove()) {
422 // The target block is the destination of a not-yet-completed move
423 // action, so read from the move's source block from file. Note we
424 // *don't* follow a chain of moves here, as a move's source index
425 // is resolved when MoveBlock() is called, and the move's source's
426 // block could be have itself been subject to a move (or write)
427 // which happened *after* this move was recorded.
428 blockIndex = change->mSourceBlockIndex;
430 // Block has been written to file, either as the source block of a move,
431 // or as a stable (all changes made) block. Read the data directly
432 // from file.
433 nsresult res;
435 MutexAutoUnlock unlock(mDataMutex);
436 MutexAutoLock lock(mFileMutex);
437 if (!mFD) {
438 // Not initialized yet, or closed.
439 return NS_ERROR_FAILURE;
441 res = ReadFromFile(BlockIndexToOffset(blockIndex) + start, dst, amount,
442 bytesRead);
444 NS_ENSURE_SUCCESS(res, res);
446 dst += bytesRead;
447 offset += bytesRead;
448 bytesToRead -= bytesRead;
450 *aBytes = aLength - bytesToRead;
451 return NS_OK;
454 nsresult FileBlockCache::MoveBlock(int32_t aSourceBlockIndex,
455 int32_t aDestBlockIndex) {
456 MutexAutoLock mon(mDataMutex);
458 if (!mBackgroundET) {
459 return NS_ERROR_FAILURE;
462 mBlockChanges.EnsureLengthAtLeast(
463 std::max(aSourceBlockIndex, aDestBlockIndex) + 1);
465 // The source block's contents may be the destination of another pending
466 // move, which in turn can be the destination of another pending move,
467 // etc. Resolve the final source block, so that if one of the blocks in
468 // the chain of moves is overwritten, we don't lose the reference to the
469 // contents of the destination block.
470 int32_t sourceIndex = aSourceBlockIndex;
471 BlockChange* sourceBlock = nullptr;
472 while ((sourceBlock = mBlockChanges[sourceIndex]) && sourceBlock->IsMove()) {
473 sourceIndex = sourceBlock->mSourceBlockIndex;
476 if (mBlockChanges[aDestBlockIndex] == nullptr ||
477 !ContainerContains(mChangeIndexList, aDestBlockIndex)) {
478 // Only add another entry to the change index list if we don't already
479 // have one for this block. We won't have an entry when either there's
480 // no pending change for this block, or if there is a pending change for
481 // this block and we're in the process of writing it (we've popped the
482 // block's index out of mChangeIndexList in Run() but not finished writing
483 // the block to file yet.
484 mChangeIndexList.push_back(aDestBlockIndex);
487 // If the source block hasn't yet been written to file then the dest block
488 // simply contains that same write. Resolve this as a write instead.
489 if (sourceBlock && sourceBlock->IsWrite()) {
490 mBlockChanges[aDestBlockIndex] = new BlockChange(sourceBlock->mData.get());
491 } else {
492 mBlockChanges[aDestBlockIndex] = new BlockChange(sourceIndex);
495 EnsureWriteScheduled();
497 NS_ASSERTION(ContainerContains(mChangeIndexList, aDestBlockIndex),
498 "Should have scheduled block for change");
500 return NS_OK;
503 } // End namespace mozilla.
505 // avoid redefined macro in unified build
506 #undef LOG