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 "mozilla/SharedThreadPool.h"
8 #include "FileBlockCache.h"
9 #include "VideoUtils.h"
10 #include "prio.h"
11 #include <algorithm>
12
13 namespace mozilla {
14
Open(PRFileDesc * aFD)15 nsresult FileBlockCache::Open(PRFileDesc* aFD)
16 {
17 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
18 NS_ENSURE_TRUE(aFD != nullptr, NS_ERROR_FAILURE);
19 {
20 MonitorAutoLock mon(mFileMonitor);
21 mFD = aFD;
22 }
23 {
24 MonitorAutoLock mon(mDataMonitor);
25 nsresult res = NS_NewThread(getter_AddRefs(mThread),
26 nullptr,
27 SharedThreadPool::kStackSize);
28 mIsOpen = NS_SUCCEEDED(res);
29 return res;
30 }
31 }
32
FileBlockCache()33 FileBlockCache::FileBlockCache()
34 : mFileMonitor("MediaCache.Writer.IO.Monitor"),
35 mFD(nullptr),
36 mFDCurrentPos(0),
37 mDataMonitor("MediaCache.Writer.Data.Monitor"),
38 mIsWriteScheduled(false),
39 mIsOpen(false)
40 {
41 MOZ_COUNT_CTOR(FileBlockCache);
42 }
43
~FileBlockCache()44 FileBlockCache::~FileBlockCache()
45 {
46 NS_ASSERTION(!mIsOpen, "Should Close() FileBlockCache before destroying");
47 {
48 // Note, mThread will be shutdown by the time this runs, so we won't
49 // block while taking mFileMonitor.
50 MonitorAutoLock mon(mFileMonitor);
51 if (mFD) {
52 PRStatus prrc;
53 prrc = PR_Close(mFD);
54 if (prrc != PR_SUCCESS) {
55 NS_WARNING("PR_Close() failed.");
56 }
57 mFD = nullptr;
58 }
59 }
60 MOZ_COUNT_DTOR(FileBlockCache);
61 }
62
63
Close()64 void FileBlockCache::Close()
65 {
66 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
67 MonitorAutoLock mon(mDataMonitor);
68
69 mIsOpen = false;
70
71 if (mThread) {
72 // We must shut down the thread in another runnable. This is called
73 // while we're shutting down the media cache, and nsIThread::Shutdown()
74 // can cause events to run before it completes, which could end up
75 // opening more streams, while the media cache is shutting down and
76 // releasing memory etc! Also note we close mFD in the destructor so
77 // as to not disturb any IO that's currently running.
78 nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
79 if (mainThread) {
80 nsCOMPtr<nsIRunnable> event = new ShutdownThreadEvent(mThread);
81 mainThread->Dispatch(event.forget(), NS_DISPATCH_NORMAL);
82 } else {
83 // we're on Mainthread already, *and* the event queues are already
84 // shut down, so no events should occur - certainly not creations of
85 // new streams.
86 mThread->Shutdown();
87 }
88 }
89 }
90
91 template<typename Container, typename Value>
92 bool
ContainerContains(const Container & aContainer,const Value & value)93 ContainerContains(const Container& aContainer, const Value& value)
94 {
95 return std::find(aContainer.begin(), aContainer.end(), value)
96 != aContainer.end();
97 }
98
WriteBlock(uint32_t aBlockIndex,const uint8_t * aData)99 nsresult FileBlockCache::WriteBlock(uint32_t aBlockIndex, const uint8_t* aData)
100 {
101 MonitorAutoLock mon(mDataMonitor);
102
103 if (!mIsOpen)
104 return NS_ERROR_FAILURE;
105
106 // Check if we've already got a pending write scheduled for this block.
107 mBlockChanges.EnsureLengthAtLeast(aBlockIndex + 1);
108 bool blockAlreadyHadPendingChange = mBlockChanges[aBlockIndex] != nullptr;
109 mBlockChanges[aBlockIndex] = new BlockChange(aData);
110
111 if (!blockAlreadyHadPendingChange || !ContainerContains(mChangeIndexList, aBlockIndex)) {
112 // We either didn't already have a pending change for this block, or we
113 // did but we didn't have an entry for it in mChangeIndexList (we're in the process
114 // of writing it and have removed the block's index out of mChangeIndexList
115 // in Run() but not finished writing the block to file yet). Add the blocks
116 // index to the end of mChangeIndexList to ensure the block is written as
117 // as soon as possible.
118 mChangeIndexList.push_back(aBlockIndex);
119 }
120 NS_ASSERTION(ContainerContains(mChangeIndexList, aBlockIndex), "Must have entry for new block");
121
122 EnsureWriteScheduled();
123
124 return NS_OK;
125 }
126
EnsureWriteScheduled()127 void FileBlockCache::EnsureWriteScheduled()
128 {
129 mDataMonitor.AssertCurrentThreadOwns();
130
131 if (!mIsWriteScheduled) {
132 mThread->Dispatch(this, NS_DISPATCH_NORMAL);
133 mIsWriteScheduled = true;
134 }
135 }
136
Seek(int64_t aOffset)137 nsresult FileBlockCache::Seek(int64_t aOffset)
138 {
139 mFileMonitor.AssertCurrentThreadOwns();
140
141 if (mFDCurrentPos != aOffset) {
142 int64_t result = PR_Seek64(mFD, aOffset, PR_SEEK_SET);
143 if (result != aOffset) {
144 NS_WARNING("Failed to seek media cache file");
145 return NS_ERROR_FAILURE;
146 }
147 mFDCurrentPos = result;
148 }
149 return NS_OK;
150 }
151
ReadFromFile(int64_t aOffset,uint8_t * aDest,int32_t aBytesToRead,int32_t & aBytesRead)152 nsresult FileBlockCache::ReadFromFile(int64_t aOffset,
153 uint8_t* aDest,
154 int32_t aBytesToRead,
155 int32_t& aBytesRead)
156 {
157 mFileMonitor.AssertCurrentThreadOwns();
158
159 nsresult res = Seek(aOffset);
160 if (NS_FAILED(res)) return res;
161
162 aBytesRead = PR_Read(mFD, aDest, aBytesToRead);
163 if (aBytesRead <= 0)
164 return NS_ERROR_FAILURE;
165 mFDCurrentPos += aBytesRead;
166
167 return NS_OK;
168 }
169
WriteBlockToFile(int32_t aBlockIndex,const uint8_t * aBlockData)170 nsresult FileBlockCache::WriteBlockToFile(int32_t aBlockIndex,
171 const uint8_t* aBlockData)
172 {
173 mFileMonitor.AssertCurrentThreadOwns();
174
175 nsresult rv = Seek(BlockIndexToOffset(aBlockIndex));
176 if (NS_FAILED(rv)) return rv;
177
178 int32_t amount = PR_Write(mFD, aBlockData, BLOCK_SIZE);
179 if (amount < BLOCK_SIZE) {
180 NS_WARNING("Failed to write media cache block!");
181 return NS_ERROR_FAILURE;
182 }
183 mFDCurrentPos += BLOCK_SIZE;
184
185 return NS_OK;
186 }
187
MoveBlockInFile(int32_t aSourceBlockIndex,int32_t aDestBlockIndex)188 nsresult FileBlockCache::MoveBlockInFile(int32_t aSourceBlockIndex,
189 int32_t aDestBlockIndex)
190 {
191 mFileMonitor.AssertCurrentThreadOwns();
192
193 uint8_t buf[BLOCK_SIZE];
194 int32_t bytesRead = 0;
195 if (NS_FAILED(ReadFromFile(BlockIndexToOffset(aSourceBlockIndex),
196 buf,
197 BLOCK_SIZE,
198 bytesRead))) {
199 return NS_ERROR_FAILURE;
200 }
201 return WriteBlockToFile(aDestBlockIndex, buf);
202 }
203
Run()204 nsresult FileBlockCache::Run()
205 {
206 MonitorAutoLock mon(mDataMonitor);
207 NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
208 NS_ASSERTION(!mChangeIndexList.empty(), "Only dispatch when there's work to do");
209 NS_ASSERTION(mIsWriteScheduled, "Should report write running or scheduled.");
210
211 while (!mChangeIndexList.empty()) {
212 if (!mIsOpen) {
213 // We've been closed, abort, discarding unwritten changes.
214 mIsWriteScheduled = false;
215 return NS_ERROR_FAILURE;
216 }
217
218 // Process each pending change. We pop the index out of the change
219 // list, but leave the BlockChange in mBlockChanges until the change
220 // is written to file. This is so that any read which happens while
221 // we drop mDataMonitor to write will refer to the data's source in
222 // memory, rather than the not-yet up to date data written to file.
223 // This also ensures we will insert a new index into mChangeIndexList
224 // when this happens.
225
226 // Hold a reference to the change, in case another change
227 // overwrites the mBlockChanges entry for this block while we drop
228 // mDataMonitor to take mFileMonitor.
229 int32_t blockIndex = mChangeIndexList.front();
230 mChangeIndexList.pop_front();
231 RefPtr<BlockChange> change = mBlockChanges[blockIndex];
232 MOZ_ASSERT(change,
233 "Change index list should only contain entries for blocks "
234 "with changes");
235 {
236 MonitorAutoUnlock unlock(mDataMonitor);
237 MonitorAutoLock lock(mFileMonitor);
238 if (change->IsWrite()) {
239 WriteBlockToFile(blockIndex, change->mData.get());
240 } else if (change->IsMove()) {
241 MoveBlockInFile(change->mSourceBlockIndex, blockIndex);
242 }
243 }
244 // If a new change has not been made to the block while we dropped
245 // mDataMonitor, clear reference to the old change. Otherwise, the old
246 // reference has been cleared already.
247 if (mBlockChanges[blockIndex] == change) {
248 mBlockChanges[blockIndex] = nullptr;
249 }
250 }
251
252 mIsWriteScheduled = false;
253
254 return NS_OK;
255 }
256
Read(int64_t aOffset,uint8_t * aData,int32_t aLength,int32_t * aBytes)257 nsresult FileBlockCache::Read(int64_t aOffset,
258 uint8_t* aData,
259 int32_t aLength,
260 int32_t* aBytes)
261 {
262 MonitorAutoLock mon(mDataMonitor);
263
264 if (!mFD || (aOffset / BLOCK_SIZE) > INT32_MAX)
265 return NS_ERROR_FAILURE;
266
267 int32_t bytesToRead = aLength;
268 int64_t offset = aOffset;
269 uint8_t* dst = aData;
270 while (bytesToRead > 0) {
271 int32_t blockIndex = static_cast<int32_t>(offset / BLOCK_SIZE);
272 int32_t start = offset % BLOCK_SIZE;
273 int32_t amount = std::min(BLOCK_SIZE - start, bytesToRead);
274
275 // If the block is not yet written to file, we can just read from
276 // the memory buffer, otherwise we need to read from file.
277 int32_t bytesRead = 0;
278 RefPtr<BlockChange> change = mBlockChanges[blockIndex];
279 if (change && change->IsWrite()) {
280 // Block isn't yet written to file. Read from memory buffer.
281 const uint8_t* blockData = change->mData.get();
282 memcpy(dst, blockData + start, amount);
283 bytesRead = amount;
284 } else {
285 if (change && change->IsMove()) {
286 // The target block is the destination of a not-yet-completed move
287 // action, so read from the move's source block from file. Note we
288 // *don't* follow a chain of moves here, as a move's source index
289 // is resolved when MoveBlock() is called, and the move's source's
290 // block could be have itself been subject to a move (or write)
291 // which happened *after* this move was recorded.
292 blockIndex = mBlockChanges[blockIndex]->mSourceBlockIndex;
293 }
294 // Block has been written to file, either as the source block of a move,
295 // or as a stable (all changes made) block. Read the data directly
296 // from file.
297 nsresult res;
298 {
299 MonitorAutoUnlock unlock(mDataMonitor);
300 MonitorAutoLock lock(mFileMonitor);
301 res = ReadFromFile(BlockIndexToOffset(blockIndex) + start,
302 dst,
303 amount,
304 bytesRead);
305 }
306 NS_ENSURE_SUCCESS(res,res);
307 }
308 dst += bytesRead;
309 offset += bytesRead;
310 bytesToRead -= bytesRead;
311 }
312 *aBytes = aLength - bytesToRead;
313 return NS_OK;
314 }
315
MoveBlock(int32_t aSourceBlockIndex,int32_t aDestBlockIndex)316 nsresult FileBlockCache::MoveBlock(int32_t aSourceBlockIndex, int32_t aDestBlockIndex)
317 {
318 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
319 MonitorAutoLock mon(mDataMonitor);
320
321 if (!mIsOpen)
322 return NS_ERROR_FAILURE;
323
324 mBlockChanges.EnsureLengthAtLeast(std::max(aSourceBlockIndex, aDestBlockIndex) + 1);
325
326 // The source block's contents may be the destination of another pending
327 // move, which in turn can be the destination of another pending move,
328 // etc. Resolve the final source block, so that if one of the blocks in
329 // the chain of moves is overwritten, we don't lose the reference to the
330 // contents of the destination block.
331 int32_t sourceIndex = aSourceBlockIndex;
332 BlockChange* sourceBlock = nullptr;
333 while ((sourceBlock = mBlockChanges[sourceIndex]) &&
334 sourceBlock->IsMove()) {
335 sourceIndex = sourceBlock->mSourceBlockIndex;
336 }
337
338 if (mBlockChanges[aDestBlockIndex] == nullptr ||
339 !ContainerContains(mChangeIndexList, aDestBlockIndex)) {
340 // Only add another entry to the change index list if we don't already
341 // have one for this block. We won't have an entry when either there's
342 // no pending change for this block, or if there is a pending change for
343 // this block and we're in the process of writing it (we've popped the
344 // block's index out of mChangeIndexList in Run() but not finished writing
345 // the block to file yet.
346 mChangeIndexList.push_back(aDestBlockIndex);
347 }
348
349 // If the source block hasn't yet been written to file then the dest block
350 // simply contains that same write. Resolve this as a write instead.
351 if (sourceBlock && sourceBlock->IsWrite()) {
352 mBlockChanges[aDestBlockIndex] = new BlockChange(sourceBlock->mData.get());
353 } else {
354 mBlockChanges[aDestBlockIndex] = new BlockChange(sourceIndex);
355 }
356
357 EnsureWriteScheduled();
358
359 NS_ASSERTION(ContainerContains(mChangeIndexList, aDestBlockIndex),
360 "Should have scheduled block for change");
361
362 return NS_OK;
363 }
364
365 } // End namespace mozilla.
366