|
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 #ifndef FILE_BLOCK_CACHE_H_ |
|
8 #define FILE_BLOCK_CACHE_H_ |
|
9 |
|
10 #include "mozilla/Attributes.h" |
|
11 #include "mozilla/Monitor.h" |
|
12 #include "nsTArray.h" |
|
13 #include "MediaCache.h" |
|
14 #include "nsDeque.h" |
|
15 #include "nsThreadUtils.h" |
|
16 |
|
17 struct PRFileDesc; |
|
18 |
|
19 namespace mozilla { |
|
20 |
|
21 // Manages file I/O for the media cache. Data comes in over the network |
|
22 // via callbacks on the main thread, however we don't want to write the |
|
23 // incoming data to the media cache on the main thread, as this could block |
|
24 // causing UI jank. |
|
25 // |
|
26 // So FileBlockCache provides an abstraction for a temporary file accessible |
|
27 // as an array of blocks, which supports a block move operation, and |
|
28 // allows synchronous reading and writing from any thread, with writes being |
|
29 // buffered so as not to block. |
|
30 // |
|
31 // Writes and cache block moves (which require reading) are deferred to |
|
32 // their own non-main thread. This object also ensures that data which has |
|
33 // been scheduled to be written, but hasn't actually *been* written, is read |
|
34 // as if it had, i.e. pending writes are cached in readable memory until |
|
35 // they're flushed to file. |
|
36 // |
|
37 // To improve efficiency, writes can only be done at block granularity, |
|
38 // whereas reads can be done with byte granularity. |
|
39 // |
|
40 // Note it's also recommended not to read from the media cache from the main |
|
41 // thread to prevent jank. |
|
42 // |
|
43 // When WriteBlock() or MoveBlock() are called, data about how to complete |
|
44 // the block change is added to mBlockChanges, indexed by block index, and |
|
45 // the block index is appended to the mChangeIndexList. This enables |
|
46 // us to quickly tell if a block has been changed, and ensures we can perform |
|
47 // the changes in the correct order. An event is dispatched to perform the |
|
48 // changes listed in mBlockChanges to file. Read() checks mBlockChanges and |
|
49 // determines the current data to return, reading from file or from |
|
50 // mBlockChanges as necessary. |
|
51 class FileBlockCache : public nsRunnable { |
|
52 public: |
|
53 enum { |
|
54 BLOCK_SIZE = MediaCacheStream::BLOCK_SIZE |
|
55 }; |
|
56 |
|
57 FileBlockCache(); |
|
58 |
|
59 ~FileBlockCache(); |
|
60 |
|
61 // Assumes ownership of aFD. |
|
62 nsresult Open(PRFileDesc* aFD); |
|
63 |
|
64 // Closes writer, shuts down thread. |
|
65 void Close(); |
|
66 |
|
67 // Can be called on any thread. This defers to a non-main thread. |
|
68 nsresult WriteBlock(uint32_t aBlockIndex, const uint8_t* aData); |
|
69 |
|
70 // Performs block writes and block moves on its own thread. |
|
71 NS_IMETHOD Run() MOZ_OVERRIDE; |
|
72 |
|
73 // Synchronously reads data from file. May read from file or memory |
|
74 // depending on whether written blocks have been flushed to file yet. |
|
75 // Not recommended to be called from the main thread, as can cause jank. |
|
76 nsresult Read(int64_t aOffset, |
|
77 uint8_t* aData, |
|
78 int32_t aLength, |
|
79 int32_t* aBytes); |
|
80 |
|
81 // Moves a block asynchronously. Can be called on any thread. |
|
82 // This defers file I/O to a non-main thread. |
|
83 nsresult MoveBlock(int32_t aSourceBlockIndex, int32_t aDestBlockIndex); |
|
84 |
|
85 // Represents a change yet to be made to a block in the file. The change |
|
86 // is either a write (and the data to be written is stored in this struct) |
|
87 // or a move (and the index of the source block is stored instead). |
|
88 struct BlockChange MOZ_FINAL { |
|
89 |
|
90 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BlockChange) |
|
91 |
|
92 // This block is waiting in memory to be written. |
|
93 // Stores a copy of the block, so we can write it asynchronously. |
|
94 BlockChange(const uint8_t* aData) |
|
95 : mSourceBlockIndex(-1) |
|
96 { |
|
97 mData = new uint8_t[BLOCK_SIZE]; |
|
98 memcpy(mData.get(), aData, BLOCK_SIZE); |
|
99 } |
|
100 |
|
101 // This block's contents are located in another file |
|
102 // block, i.e. this block has been moved. |
|
103 BlockChange(int32_t aSourceBlockIndex) |
|
104 : mSourceBlockIndex(aSourceBlockIndex) {} |
|
105 |
|
106 nsAutoArrayPtr<uint8_t> mData; |
|
107 const int32_t mSourceBlockIndex; |
|
108 |
|
109 bool IsMove() const { |
|
110 return mSourceBlockIndex != -1; |
|
111 } |
|
112 bool IsWrite() const { |
|
113 return mSourceBlockIndex == -1 && |
|
114 mData.get() != nullptr; |
|
115 } |
|
116 |
|
117 private: |
|
118 // Private destructor, to discourage deletion outside of Release(): |
|
119 ~BlockChange() |
|
120 { |
|
121 } |
|
122 }; |
|
123 |
|
124 class Int32Queue : private nsDeque { |
|
125 public: |
|
126 int32_t PopFront() { |
|
127 int32_t front = ObjectAt(0); |
|
128 nsDeque::PopFront(); |
|
129 return front; |
|
130 } |
|
131 |
|
132 void PushBack(int32_t aValue) { |
|
133 nsDeque::Push(reinterpret_cast<void*>(aValue)); |
|
134 } |
|
135 |
|
136 bool Contains(int32_t aValue) { |
|
137 for (int32_t i = 0; i < GetSize(); ++i) { |
|
138 if (ObjectAt(i) == aValue) { |
|
139 return true; |
|
140 } |
|
141 } |
|
142 return false; |
|
143 } |
|
144 |
|
145 bool IsEmpty() { |
|
146 return nsDeque::GetSize() == 0; |
|
147 } |
|
148 |
|
149 private: |
|
150 int32_t ObjectAt(int32_t aIndex) { |
|
151 void* v = nsDeque::ObjectAt(aIndex); |
|
152 return reinterpret_cast<uintptr_t>(v); |
|
153 } |
|
154 }; |
|
155 |
|
156 private: |
|
157 int64_t BlockIndexToOffset(int32_t aBlockIndex) { |
|
158 return static_cast<int64_t>(aBlockIndex) * BLOCK_SIZE; |
|
159 } |
|
160 |
|
161 // Monitor which controls access to mFD and mFDCurrentPos. Don't hold |
|
162 // mDataMonitor while holding mFileMonitor! mFileMonitor must be owned |
|
163 // while accessing any of the following data fields or methods. |
|
164 Monitor mFileMonitor; |
|
165 // Moves a block already committed to file. |
|
166 nsresult MoveBlockInFile(int32_t aSourceBlockIndex, |
|
167 int32_t aDestBlockIndex); |
|
168 // Seeks file pointer. |
|
169 nsresult Seek(int64_t aOffset); |
|
170 // Reads data from file offset. |
|
171 nsresult ReadFromFile(int64_t aOffset, |
|
172 uint8_t* aDest, |
|
173 int32_t aBytesToRead, |
|
174 int32_t& aBytesRead); |
|
175 nsresult WriteBlockToFile(int32_t aBlockIndex, const uint8_t* aBlockData); |
|
176 // File descriptor we're writing to. This is created externally, but |
|
177 // shutdown by us. |
|
178 PRFileDesc* mFD; |
|
179 // The current file offset in the file. |
|
180 int64_t mFDCurrentPos; |
|
181 |
|
182 // Monitor which controls access to all data in this class, except mFD |
|
183 // and mFDCurrentPos. Don't hold mDataMonitor while holding mFileMonitor! |
|
184 // mDataMonitor must be owned while accessing any of the following data |
|
185 // fields or methods. |
|
186 Monitor mDataMonitor; |
|
187 // Ensures we either are running the event to preform IO, or an event |
|
188 // has been dispatched to preform the IO. |
|
189 // mDataMonitor must be owned while calling this. |
|
190 void EnsureWriteScheduled(); |
|
191 // Array of block changes to made. If mBlockChanges[offset/BLOCK_SIZE] == nullptr, |
|
192 // then the block has no pending changes to be written, but if |
|
193 // mBlockChanges[offset/BLOCK_SIZE] != nullptr, then either there's a block |
|
194 // cached in memory waiting to be written, or this block is the target of a |
|
195 // block move. |
|
196 nsTArray< nsRefPtr<BlockChange> > mBlockChanges; |
|
197 // Thread upon which block writes and block moves are performed. This is |
|
198 // created upon open, and shutdown (asynchronously) upon close (on the |
|
199 // main thread). |
|
200 nsCOMPtr<nsIThread> mThread; |
|
201 // Queue of pending block indexes that need to be written or moved. |
|
202 //nsAutoTArray<int32_t, 8> mChangeIndexList; |
|
203 Int32Queue mChangeIndexList; |
|
204 // True if we've dispatched an event to commit all pending block changes |
|
205 // to file on mThread. |
|
206 bool mIsWriteScheduled; |
|
207 // True if the writer is ready to write data to file. |
|
208 bool mIsOpen; |
|
209 }; |
|
210 |
|
211 } // End namespace mozilla. |
|
212 |
|
213 #endif /* FILE_BLOCK_CACHE_H_ */ |