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