Backed out 35 changesets (bug 941158, bug 972518, bug 959520, bug 986063, bug 948895...
[gecko.git] / content / media / FileBlockCache.cpp
blobfd5f1bfed368a5271431c229bebb2cc12f4b6e14
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 "VideoUtils.h"
9 #include "prio.h"
10 #include <algorithm>
12 namespace mozilla {
14 nsresult FileBlockCache::Open(PRFileDesc* aFD)
16 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
17 NS_ENSURE_TRUE(aFD != nullptr, NS_ERROR_FAILURE);
19 MonitorAutoLock mon(mFileMonitor);
20 mFD = aFD;
23 MonitorAutoLock mon(mDataMonitor);
24 nsresult res = NS_NewThread(getter_AddRefs(mThread),
25 nullptr,
26 MEDIA_THREAD_STACK_SIZE);
27 mIsOpen = NS_SUCCEEDED(res);
28 return res;
32 FileBlockCache::FileBlockCache()
33 : mFileMonitor("MediaCache.Writer.IO.Monitor"),
34 mFD(nullptr),
35 mFDCurrentPos(0),
36 mDataMonitor("MediaCache.Writer.Data.Monitor"),
37 mIsWriteScheduled(false),
38 mIsOpen(false)
40 MOZ_COUNT_CTOR(FileBlockCache);
43 FileBlockCache::~FileBlockCache()
45 NS_ASSERTION(!mIsOpen, "Should Close() FileBlockCache before destroying");
47 // Note, mThread will be shutdown by the time this runs, so we won't
48 // block while taking mFileMonitor.
49 MonitorAutoLock mon(mFileMonitor);
50 if (mFD) {
51 PRStatus prrc;
52 prrc = PR_Close(mFD);
53 if (prrc != PR_SUCCESS) {
54 NS_WARNING("PR_Close() failed.");
56 mFD = nullptr;
59 MOZ_COUNT_DTOR(FileBlockCache);
63 void FileBlockCache::Close()
65 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
66 MonitorAutoLock mon(mDataMonitor);
68 mIsOpen = false;
70 if (mThread) {
71 // We must shut down the thread in another runnable. This is called
72 // while we're shutting down the media cache, and nsIThread::Shutdown()
73 // can cause events to run before it completes, which could end up
74 // opening more streams, while the media cache is shutting down and
75 // releasing memory etc! Also note we close mFD in the destructor so
76 // as to not disturb any IO that's currently running.
77 nsCOMPtr<nsIRunnable> event = new ShutdownThreadEvent(mThread);
78 mThread = nullptr;
79 NS_DispatchToMainThread(event);
83 nsresult FileBlockCache::WriteBlock(uint32_t aBlockIndex, const uint8_t* aData)
85 MonitorAutoLock mon(mDataMonitor);
87 if (!mIsOpen)
88 return NS_ERROR_FAILURE;
90 // Check if we've already got a pending write scheduled for this block.
91 mBlockChanges.EnsureLengthAtLeast(aBlockIndex + 1);
92 bool blockAlreadyHadPendingChange = mBlockChanges[aBlockIndex] != nullptr;
93 mBlockChanges[aBlockIndex] = new BlockChange(aData);
95 if (!blockAlreadyHadPendingChange || !mChangeIndexList.Contains(aBlockIndex)) {
96 // We either didn't already have a pending change for this block, or we
97 // did but we didn't have an entry for it in mChangeIndexList (we're in the process
98 // of writing it and have removed the block's index out of mChangeIndexList
99 // in Run() but not finished writing the block to file yet). Add the blocks
100 // index to the end of mChangeIndexList to ensure the block is written as
101 // as soon as possible.
102 mChangeIndexList.PushBack(aBlockIndex);
104 NS_ASSERTION(mChangeIndexList.Contains(aBlockIndex), "Must have entry for new block");
106 EnsureWriteScheduled();
108 return NS_OK;
111 void FileBlockCache::EnsureWriteScheduled()
113 mDataMonitor.AssertCurrentThreadOwns();
115 if (!mIsWriteScheduled) {
116 mThread->Dispatch(this, NS_DISPATCH_NORMAL);
117 mIsWriteScheduled = true;
121 nsresult FileBlockCache::Seek(int64_t aOffset)
123 mFileMonitor.AssertCurrentThreadOwns();
125 if (mFDCurrentPos != aOffset) {
126 int64_t result = PR_Seek64(mFD, aOffset, PR_SEEK_SET);
127 if (result != aOffset) {
128 NS_WARNING("Failed to seek media cache file");
129 return NS_ERROR_FAILURE;
131 mFDCurrentPos = result;
133 return NS_OK;
136 nsresult FileBlockCache::ReadFromFile(int64_t aOffset,
137 uint8_t* aDest,
138 int32_t aBytesToRead,
139 int32_t& aBytesRead)
141 mFileMonitor.AssertCurrentThreadOwns();
143 nsresult res = Seek(aOffset);
144 if (NS_FAILED(res)) return res;
146 aBytesRead = PR_Read(mFD, aDest, aBytesToRead);
147 if (aBytesRead <= 0)
148 return NS_ERROR_FAILURE;
149 mFDCurrentPos += aBytesRead;
151 return NS_OK;
154 nsresult FileBlockCache::WriteBlockToFile(int32_t aBlockIndex,
155 const uint8_t* aBlockData)
157 mFileMonitor.AssertCurrentThreadOwns();
159 nsresult rv = Seek(BlockIndexToOffset(aBlockIndex));
160 if (NS_FAILED(rv)) return rv;
162 int32_t amount = PR_Write(mFD, aBlockData, BLOCK_SIZE);
163 if (amount < BLOCK_SIZE) {
164 NS_WARNING("Failed to write media cache block!");
165 return NS_ERROR_FAILURE;
167 mFDCurrentPos += BLOCK_SIZE;
169 return NS_OK;
172 nsresult FileBlockCache::MoveBlockInFile(int32_t aSourceBlockIndex,
173 int32_t aDestBlockIndex)
175 mFileMonitor.AssertCurrentThreadOwns();
177 uint8_t buf[BLOCK_SIZE];
178 int32_t bytesRead = 0;
179 if (NS_FAILED(ReadFromFile(BlockIndexToOffset(aSourceBlockIndex),
180 buf,
181 BLOCK_SIZE,
182 bytesRead))) {
183 return NS_ERROR_FAILURE;
185 return WriteBlockToFile(aDestBlockIndex, buf);
188 nsresult FileBlockCache::Run()
190 MonitorAutoLock mon(mDataMonitor);
191 NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
192 NS_ASSERTION(!mChangeIndexList.IsEmpty(), "Only dispatch when there's work to do");
193 NS_ASSERTION(mIsWriteScheduled, "Should report write running or scheduled.");
195 while (!mChangeIndexList.IsEmpty()) {
196 if (!mIsOpen) {
197 // We've been closed, abort, discarding unwritten changes.
198 mIsWriteScheduled = false;
199 return NS_ERROR_FAILURE;
202 // Process each pending change. We pop the index out of the change
203 // list, but leave the BlockChange in mBlockChanges until the change
204 // is written to file. This is so that any read which happens while
205 // we drop mDataMonitor to write will refer to the data's source in
206 // memory, rather than the not-yet up to date data written to file.
207 // This also ensures we will insert a new index into mChangeIndexList
208 // when this happens.
210 // Hold a reference to the change, in case another change
211 // overwrites the mBlockChanges entry for this block while we drop
212 // mDataMonitor to take mFileMonitor.
213 int32_t blockIndex = mChangeIndexList.PopFront();
214 nsRefPtr<BlockChange> change = mBlockChanges[blockIndex];
215 NS_ABORT_IF_FALSE(change,
216 "Change index list should only contain entries for blocks with changes");
218 MonitorAutoUnlock unlock(mDataMonitor);
219 MonitorAutoLock lock(mFileMonitor);
220 if (change->IsWrite()) {
221 WriteBlockToFile(blockIndex, change->mData.get());
222 } else if (change->IsMove()) {
223 MoveBlockInFile(change->mSourceBlockIndex, blockIndex);
226 // If a new change has not been made to the block while we dropped
227 // mDataMonitor, clear reference to the old change. Otherwise, the old
228 // reference has been cleared already.
229 if (mBlockChanges[blockIndex] == change) {
230 mBlockChanges[blockIndex] = nullptr;
234 mIsWriteScheduled = false;
236 return NS_OK;
239 nsresult FileBlockCache::Read(int64_t aOffset,
240 uint8_t* aData,
241 int32_t aLength,
242 int32_t* aBytes)
244 MonitorAutoLock mon(mDataMonitor);
246 if (!mFD || (aOffset / BLOCK_SIZE) > INT32_MAX)
247 return NS_ERROR_FAILURE;
249 int32_t bytesToRead = aLength;
250 int64_t offset = aOffset;
251 uint8_t* dst = aData;
252 while (bytesToRead > 0) {
253 int32_t blockIndex = static_cast<int32_t>(offset / BLOCK_SIZE);
254 int32_t start = offset % BLOCK_SIZE;
255 int32_t amount = std::min(BLOCK_SIZE - start, bytesToRead);
257 // If the block is not yet written to file, we can just read from
258 // the memory buffer, otherwise we need to read from file.
259 int32_t bytesRead = 0;
260 nsRefPtr<BlockChange> change = mBlockChanges[blockIndex];
261 if (change && change->IsWrite()) {
262 // Block isn't yet written to file. Read from memory buffer.
263 const uint8_t* blockData = change->mData.get();
264 memcpy(dst, blockData + start, amount);
265 bytesRead = amount;
266 } else {
267 if (change && change->IsMove()) {
268 // The target block is the destination of a not-yet-completed move
269 // action, so read from the move's source block from file. Note we
270 // *don't* follow a chain of moves here, as a move's source index
271 // is resolved when MoveBlock() is called, and the move's source's
272 // block could be have itself been subject to a move (or write)
273 // which happened *after* this move was recorded.
274 blockIndex = mBlockChanges[blockIndex]->mSourceBlockIndex;
276 // Block has been written to file, either as the source block of a move,
277 // or as a stable (all changes made) block. Read the data directly
278 // from file.
279 nsresult res;
281 MonitorAutoUnlock unlock(mDataMonitor);
282 MonitorAutoLock lock(mFileMonitor);
283 res = ReadFromFile(BlockIndexToOffset(blockIndex) + start,
284 dst,
285 amount,
286 bytesRead);
288 NS_ENSURE_SUCCESS(res,res);
290 dst += bytesRead;
291 offset += bytesRead;
292 bytesToRead -= bytesRead;
294 *aBytes = aLength - bytesToRead;
295 return NS_OK;
298 nsresult FileBlockCache::MoveBlock(int32_t aSourceBlockIndex, int32_t aDestBlockIndex)
300 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
301 MonitorAutoLock mon(mDataMonitor);
303 if (!mIsOpen)
304 return NS_ERROR_FAILURE;
306 mBlockChanges.EnsureLengthAtLeast(std::max(aSourceBlockIndex, aDestBlockIndex) + 1);
308 // The source block's contents may be the destination of another pending
309 // move, which in turn can be the destination of another pending move,
310 // etc. Resolve the final source block, so that if one of the blocks in
311 // the chain of moves is overwritten, we don't lose the reference to the
312 // contents of the destination block.
313 int32_t sourceIndex = aSourceBlockIndex;
314 BlockChange* sourceBlock = nullptr;
315 while ((sourceBlock = mBlockChanges[sourceIndex]) &&
316 sourceBlock->IsMove()) {
317 sourceIndex = sourceBlock->mSourceBlockIndex;
320 if (mBlockChanges[aDestBlockIndex] == nullptr ||
321 !mChangeIndexList.Contains(aDestBlockIndex)) {
322 // Only add another entry to the change index list if we don't already
323 // have one for this block. We won't have an entry when either there's
324 // no pending change for this block, or if there is a pending change for
325 // this block and we're in the process of writing it (we've popped the
326 // block's index out of mChangeIndexList in Run() but not finished writing
327 // the block to file yet.
328 mChangeIndexList.PushBack(aDestBlockIndex);
331 // If the source block hasn't yet been written to file then the dest block
332 // simply contains that same write. Resolve this as a write instead.
333 if (sourceBlock && sourceBlock->IsWrite()) {
334 mBlockChanges[aDestBlockIndex] = new BlockChange(sourceBlock->mData.get());
335 } else {
336 mBlockChanges[aDestBlockIndex] = new BlockChange(sourceIndex);
339 EnsureWriteScheduled();
341 NS_ASSERTION(mChangeIndexList.Contains(aDestBlockIndex),
342 "Should have scheduled block for change");
344 return NS_OK;
347 } // End namespace mozilla.