|
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 "FileBlockCache.h" |
|
8 #include "VideoUtils.h" |
|
9 #include "prio.h" |
|
10 #include <algorithm> |
|
11 |
|
12 namespace mozilla { |
|
13 |
|
14 nsresult FileBlockCache::Open(PRFileDesc* aFD) |
|
15 { |
|
16 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); |
|
17 NS_ENSURE_TRUE(aFD != nullptr, NS_ERROR_FAILURE); |
|
18 { |
|
19 MonitorAutoLock mon(mFileMonitor); |
|
20 mFD = aFD; |
|
21 } |
|
22 { |
|
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; |
|
29 } |
|
30 } |
|
31 |
|
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) |
|
39 { |
|
40 MOZ_COUNT_CTOR(FileBlockCache); |
|
41 } |
|
42 |
|
43 FileBlockCache::~FileBlockCache() |
|
44 { |
|
45 NS_ASSERTION(!mIsOpen, "Should Close() FileBlockCache before destroying"); |
|
46 { |
|
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."); |
|
55 } |
|
56 mFD = nullptr; |
|
57 } |
|
58 } |
|
59 MOZ_COUNT_DTOR(FileBlockCache); |
|
60 } |
|
61 |
|
62 |
|
63 void FileBlockCache::Close() |
|
64 { |
|
65 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); |
|
66 MonitorAutoLock mon(mDataMonitor); |
|
67 |
|
68 mIsOpen = false; |
|
69 |
|
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); |
|
80 } |
|
81 } |
|
82 |
|
83 nsresult FileBlockCache::WriteBlock(uint32_t aBlockIndex, const uint8_t* aData) |
|
84 { |
|
85 MonitorAutoLock mon(mDataMonitor); |
|
86 |
|
87 if (!mIsOpen) |
|
88 return NS_ERROR_FAILURE; |
|
89 |
|
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); |
|
94 |
|
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); |
|
103 } |
|
104 NS_ASSERTION(mChangeIndexList.Contains(aBlockIndex), "Must have entry for new block"); |
|
105 |
|
106 EnsureWriteScheduled(); |
|
107 |
|
108 return NS_OK; |
|
109 } |
|
110 |
|
111 void FileBlockCache::EnsureWriteScheduled() |
|
112 { |
|
113 mDataMonitor.AssertCurrentThreadOwns(); |
|
114 |
|
115 if (!mIsWriteScheduled) { |
|
116 mThread->Dispatch(this, NS_DISPATCH_NORMAL); |
|
117 mIsWriteScheduled = true; |
|
118 } |
|
119 } |
|
120 |
|
121 nsresult FileBlockCache::Seek(int64_t aOffset) |
|
122 { |
|
123 mFileMonitor.AssertCurrentThreadOwns(); |
|
124 |
|
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; |
|
130 } |
|
131 mFDCurrentPos = result; |
|
132 } |
|
133 return NS_OK; |
|
134 } |
|
135 |
|
136 nsresult FileBlockCache::ReadFromFile(int64_t aOffset, |
|
137 uint8_t* aDest, |
|
138 int32_t aBytesToRead, |
|
139 int32_t& aBytesRead) |
|
140 { |
|
141 mFileMonitor.AssertCurrentThreadOwns(); |
|
142 |
|
143 nsresult res = Seek(aOffset); |
|
144 if (NS_FAILED(res)) return res; |
|
145 |
|
146 aBytesRead = PR_Read(mFD, aDest, aBytesToRead); |
|
147 if (aBytesRead <= 0) |
|
148 return NS_ERROR_FAILURE; |
|
149 mFDCurrentPos += aBytesRead; |
|
150 |
|
151 return NS_OK; |
|
152 } |
|
153 |
|
154 nsresult FileBlockCache::WriteBlockToFile(int32_t aBlockIndex, |
|
155 const uint8_t* aBlockData) |
|
156 { |
|
157 mFileMonitor.AssertCurrentThreadOwns(); |
|
158 |
|
159 nsresult rv = Seek(BlockIndexToOffset(aBlockIndex)); |
|
160 if (NS_FAILED(rv)) return rv; |
|
161 |
|
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; |
|
166 } |
|
167 mFDCurrentPos += BLOCK_SIZE; |
|
168 |
|
169 return NS_OK; |
|
170 } |
|
171 |
|
172 nsresult FileBlockCache::MoveBlockInFile(int32_t aSourceBlockIndex, |
|
173 int32_t aDestBlockIndex) |
|
174 { |
|
175 mFileMonitor.AssertCurrentThreadOwns(); |
|
176 |
|
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; |
|
184 } |
|
185 return WriteBlockToFile(aDestBlockIndex, buf); |
|
186 } |
|
187 |
|
188 nsresult FileBlockCache::Run() |
|
189 { |
|
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."); |
|
194 |
|
195 while (!mChangeIndexList.IsEmpty()) { |
|
196 if (!mIsOpen) { |
|
197 // We've been closed, abort, discarding unwritten changes. |
|
198 mIsWriteScheduled = false; |
|
199 return NS_ERROR_FAILURE; |
|
200 } |
|
201 |
|
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. |
|
209 |
|
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"); |
|
217 { |
|
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); |
|
224 } |
|
225 } |
|
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; |
|
231 } |
|
232 } |
|
233 |
|
234 mIsWriteScheduled = false; |
|
235 |
|
236 return NS_OK; |
|
237 } |
|
238 |
|
239 nsresult FileBlockCache::Read(int64_t aOffset, |
|
240 uint8_t* aData, |
|
241 int32_t aLength, |
|
242 int32_t* aBytes) |
|
243 { |
|
244 MonitorAutoLock mon(mDataMonitor); |
|
245 |
|
246 if (!mFD || (aOffset / BLOCK_SIZE) > INT32_MAX) |
|
247 return NS_ERROR_FAILURE; |
|
248 |
|
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); |
|
256 |
|
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; |
|
275 } |
|
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; |
|
280 { |
|
281 MonitorAutoUnlock unlock(mDataMonitor); |
|
282 MonitorAutoLock lock(mFileMonitor); |
|
283 res = ReadFromFile(BlockIndexToOffset(blockIndex) + start, |
|
284 dst, |
|
285 amount, |
|
286 bytesRead); |
|
287 } |
|
288 NS_ENSURE_SUCCESS(res,res); |
|
289 } |
|
290 dst += bytesRead; |
|
291 offset += bytesRead; |
|
292 bytesToRead -= bytesRead; |
|
293 } |
|
294 *aBytes = aLength - bytesToRead; |
|
295 return NS_OK; |
|
296 } |
|
297 |
|
298 nsresult FileBlockCache::MoveBlock(int32_t aSourceBlockIndex, int32_t aDestBlockIndex) |
|
299 { |
|
300 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); |
|
301 MonitorAutoLock mon(mDataMonitor); |
|
302 |
|
303 if (!mIsOpen) |
|
304 return NS_ERROR_FAILURE; |
|
305 |
|
306 mBlockChanges.EnsureLengthAtLeast(std::max(aSourceBlockIndex, aDestBlockIndex) + 1); |
|
307 |
|
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; |
|
318 } |
|
319 |
|
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); |
|
329 } |
|
330 |
|
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); |
|
337 } |
|
338 |
|
339 EnsureWriteScheduled(); |
|
340 |
|
341 NS_ASSERTION(mChangeIndexList.Contains(aDestBlockIndex), |
|
342 "Should have scheduled block for change"); |
|
343 |
|
344 return NS_OK; |
|
345 } |
|
346 |
|
347 } // End namespace mozilla. |