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