Coverage Report

Created: 2018-09-25 14:53

/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_ */