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"
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
);
23 MonitorAutoLock
mon(mDataMonitor
);
24 nsresult res
= NS_NewThread(getter_AddRefs(mThread
),
26 MEDIA_THREAD_STACK_SIZE
);
27 mIsOpen
= NS_SUCCEEDED(res
);
32 FileBlockCache::FileBlockCache()
33 : mFileMonitor("MediaCache.Writer.IO.Monitor"),
36 mDataMonitor("MediaCache.Writer.Data.Monitor"),
37 mIsWriteScheduled(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
);
53 if (prrc
!= PR_SUCCESS
) {
54 NS_WARNING("PR_Close() failed.");
59 MOZ_COUNT_DTOR(FileBlockCache
);
63 void FileBlockCache::Close()
65 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
66 MonitorAutoLock
mon(mDataMonitor
);
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
);
79 NS_DispatchToMainThread(event
);
83 nsresult
FileBlockCache::WriteBlock(uint32_t aBlockIndex
, const uint8_t* aData
)
85 MonitorAutoLock
mon(mDataMonitor
);
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();
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
;
136 nsresult
FileBlockCache::ReadFromFile(int64_t aOffset
,
138 int32_t aBytesToRead
,
141 mFileMonitor
.AssertCurrentThreadOwns();
143 nsresult res
= Seek(aOffset
);
144 if (NS_FAILED(res
)) return res
;
146 aBytesRead
= PR_Read(mFD
, aDest
, aBytesToRead
);
148 return NS_ERROR_FAILURE
;
149 mFDCurrentPos
+= aBytesRead
;
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
;
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
),
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()) {
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;
239 nsresult
FileBlockCache::Read(int64_t aOffset
,
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
);
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
281 MonitorAutoUnlock
unlock(mDataMonitor
);
282 MonitorAutoLock
lock(mFileMonitor
);
283 res
= ReadFromFile(BlockIndexToOffset(blockIndex
) + start
,
288 NS_ENSURE_SUCCESS(res
,res
);
292 bytesToRead
-= bytesRead
;
294 *aBytes
= aLength
- bytesToRead
;
298 nsresult
FileBlockCache::MoveBlock(int32_t aSourceBlockIndex
, int32_t aDestBlockIndex
)
300 NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
301 MonitorAutoLock
mon(mDataMonitor
);
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());
336 mBlockChanges
[aDestBlockIndex
] = new BlockChange(sourceIndex
);
339 EnsureWriteScheduled();
341 NS_ASSERTION(mChangeIndexList
.Contains(aDestBlockIndex
),
342 "Should have scheduled block for change");
347 } // End namespace mozilla.