Line data Source code
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 : // If re-initializing, just discard pending writes if any.
66 : nsresult Init() override;
67 :
68 : // Maximum number of blocks allowed in this block cache.
69 : // Calculated from "media.cache_size" pref.
70 : int32_t GetMaxBlocks() const override;
71 :
72 : // Can be called on any thread. This defers to a non-main thread.
73 : nsresult WriteBlock(uint32_t aBlockIndex,
74 : Span<const uint8_t> aData1,
75 : Span<const uint8_t> aData2) override;
76 :
77 : // Synchronously reads data from file. May read from file or memory
78 : // depending on whether written blocks have been flushed to file yet.
79 : // Not recommended to be called from the main thread, as can cause jank.
80 : nsresult Read(int64_t aOffset,
81 : uint8_t* aData,
82 : int32_t aLength,
83 : int32_t* aBytes) override;
84 :
85 : // Moves a block asynchronously. Can be called on any thread.
86 : // This defers file I/O to a non-main thread.
87 : nsresult MoveBlock(int32_t aSourceBlockIndex,
88 : int32_t aDestBlockIndex) override;
89 :
90 : // Represents a change yet to be made to a block in the file. The change
91 : // is either a write (and the data to be written is stored in this struct)
92 : // or a move (and the index of the source block is stored instead).
93 : struct BlockChange final {
94 :
95 0 : NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BlockChange)
96 :
97 : // This block is waiting in memory to be written.
98 : // Stores a copy of the block, so we can write it asynchronously.
99 0 : explicit BlockChange(const uint8_t* aData)
100 0 : : mSourceBlockIndex(-1)
101 : {
102 0 : mData = MakeUnique<uint8_t[]>(BLOCK_SIZE);
103 0 : memcpy(mData.get(), aData, BLOCK_SIZE);
104 0 : }
105 :
106 0 : BlockChange(Span<const uint8_t> aData1, Span<const uint8_t> aData2)
107 0 : : mSourceBlockIndex(-1)
108 : {
109 0 : MOZ_ASSERT(aData1.Length() + aData2.Length() == BLOCK_SIZE);
110 0 : mData = MakeUnique<uint8_t[]>(BLOCK_SIZE);
111 0 : memcpy(mData.get(), aData1.Elements(), aData1.Length());
112 0 : memcpy(mData.get() + aData1.Length(), aData2.Elements(), aData2.Length());
113 0 : }
114 :
115 : // This block's contents are located in another file
116 : // block, i.e. this block has been moved.
117 0 : explicit BlockChange(int32_t aSourceBlockIndex)
118 0 : : mSourceBlockIndex(aSourceBlockIndex) {}
119 :
120 : UniquePtr<uint8_t[]> mData;
121 : const int32_t mSourceBlockIndex;
122 :
123 0 : bool IsMove() const {
124 0 : return mSourceBlockIndex != -1;
125 : }
126 0 : bool IsWrite() const {
127 0 : return mSourceBlockIndex == -1 &&
128 0 : mData.get() != nullptr;
129 : }
130 :
131 : private:
132 : // Private destructor, to discourage deletion outside of Release():
133 0 : ~BlockChange()
134 0 : {
135 0 : }
136 : };
137 :
138 : private:
139 0 : int64_t BlockIndexToOffset(int32_t aBlockIndex) {
140 0 : return static_cast<int64_t>(aBlockIndex) * BLOCK_SIZE;
141 : }
142 :
143 : void SetCacheFile(PRFileDesc* aFD);
144 :
145 : // Close file in thread and terminate thread.
146 : void Close();
147 :
148 : // Performs block writes and block moves on its own thread.
149 : void PerformBlockIOs();
150 :
151 : // Mutex which controls access to mFD and mFDCurrentPos. Don't hold
152 : // mDataMutex while holding mFileMutex! mFileMutex must be owned
153 : // while accessing any of the following data fields or methods.
154 : Mutex mFileMutex;
155 : // Moves a block already committed to file.
156 : nsresult MoveBlockInFile(int32_t aSourceBlockIndex,
157 : int32_t aDestBlockIndex);
158 : // Seeks file pointer.
159 : nsresult Seek(int64_t aOffset);
160 : // Reads data from file offset.
161 : nsresult ReadFromFile(int64_t aOffset,
162 : uint8_t* aDest,
163 : int32_t aBytesToRead,
164 : int32_t& aBytesRead);
165 : nsresult WriteBlockToFile(int32_t aBlockIndex, const uint8_t* aBlockData);
166 : // File descriptor we're writing to. This is created externally, but
167 : // shutdown by us.
168 : PRFileDesc* mFD;
169 : // The current file offset in the file.
170 : int64_t mFDCurrentPos;
171 :
172 : // Mutex which controls access to all data in this class, except mFD
173 : // and mFDCurrentPos. Don't hold mDataMutex while holding mFileMutex!
174 : // mDataMutex must be owned while accessing any of the following data
175 : // fields or methods.
176 : Mutex mDataMutex;
177 : // Ensures we either are running the event to preform IO, or an event
178 : // has been dispatched to preform the IO.
179 : // mDataMutex must be owned while calling this.
180 : void EnsureWriteScheduled();
181 :
182 : // Array of block changes to made. If mBlockChanges[offset/BLOCK_SIZE] == nullptr,
183 : // then the block has no pending changes to be written, but if
184 : // mBlockChanges[offset/BLOCK_SIZE] != nullptr, then either there's a block
185 : // cached in memory waiting to be written, or this block is the target of a
186 : // block move.
187 : nsTArray< RefPtr<BlockChange> > mBlockChanges;
188 : // Thread upon which block writes and block moves are performed. This is
189 : // created upon open, and shutdown (asynchronously) upon close (on the
190 : // main thread).
191 : nsCOMPtr<nsIThread> mThread;
192 : // Queue of pending block indexes that need to be written or moved.
193 : std::deque<int32_t> mChangeIndexList;
194 : // True if we've dispatched an event to commit all pending block changes
195 : // to file on mThread.
196 : bool mIsWriteScheduled;
197 : // True when a read is happening. Pending writes may be postponed, to give
198 : // higher priority to reads (which may be blocking the caller).
199 : bool mIsReading;
200 : // True if we've got a temporary file descriptor. Note: we don't use mFD
201 : // directly as that's synchronized via mFileMutex and we need to make
202 : // decisions about whether we can write while holding mDataMutex.
203 : bool mInitialized = false;
204 : };
205 :
206 : } // End namespace mozilla.
207 :
208 : #endif /* FILE_BLOCK_CACHE_H_ */
|