summaryrefslogtreecommitdiffstats
path: root/dom/media/FileBlockCache.h
blob: 3a1daf0794946d831615dfd0f2c667cb2ba5cafd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef FILE_BLOCK_CACHE_H_
#define FILE_BLOCK_CACHE_H_

#include "mozilla/Attributes.h"
#include "mozilla/MozPromise.h"
#include "mozilla/Mutex.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/AbstractThread.h"
#include "nsTArray.h"
#include "MediaBlockCacheBase.h"
#include "nsDeque.h"
#include "nsThreadUtils.h"
#include <deque>

struct PRFileDesc;

namespace mozilla {

// Manages file I/O for the media cache. Data comes in over the network
// via callbacks on the main thread, however we don't want to write the
// incoming data to the media cache on the main thread, as this could block
// causing UI jank.
//
// So FileBlockCache provides an abstraction for a temporary file accessible
// as an array of blocks, which supports a block move operation, and
// allows synchronous reading and writing from any thread, with writes being
// buffered so as not to block.
//
// Writes and cache block moves (which require reading) are deferred to
// their own non-main thread. This object also ensures that data which has
// been scheduled to be written, but hasn't actually *been* written, is read
// as if it had, i.e. pending writes are cached in readable memory until
// they're flushed to file.
//
// To improve efficiency, writes can only be done at block granularity,
// whereas reads can be done with byte granularity.
//
// Note it's also recommended not to read from the media cache from the main
// thread to prevent jank.
//
// When WriteBlock() or MoveBlock() are called, data about how to complete
// the block change is added to mBlockChanges, indexed by block index, and
// the block index is appended to the mChangeIndexList. This enables
// us to quickly tell if a block has been changed, and ensures we can perform
// the changes in the correct order. An event is dispatched to perform the
// changes listed in mBlockChanges to file. Read() checks mBlockChanges and
// determines the current data to return, reading from file or from
// mBlockChanges as necessary.
class FileBlockCache : public MediaBlockCacheBase {
 public:
  FileBlockCache();

 protected:
  virtual ~FileBlockCache();

 public:
  // Launch thread and open temporary file.
  nsresult Init() override;

  // Will discard pending changes if any.
  void Flush() override;

  // Maximum number of blocks allowed in this block cache.
  // Calculated from "media.cache_size" pref.
  size_t GetMaxBlocks(size_t aCacheSizeInKB) const override;

  // Can be called on any thread. This defers to a non-main thread.
  nsresult WriteBlock(uint32_t aBlockIndex, Span<const uint8_t> aData1,
                      Span<const uint8_t> aData2) override;

  // Synchronously reads data from file. May read from file or memory
  // depending on whether written blocks have been flushed to file yet.
  // Not recommended to be called from the main thread, as can cause jank.
  nsresult Read(int64_t aOffset, uint8_t* aData, int32_t aLength,
                int32_t* aBytes) override;

  // Moves a block asynchronously. Can be called on any thread.
  // This defers file I/O to a non-main thread.
  nsresult MoveBlock(int32_t aSourceBlockIndex,
                     int32_t aDestBlockIndex) override;

  // Represents a change yet to be made to a block in the file. The change
  // is either a write (and the data to be written is stored in this struct)
  // or a move (and the index of the source block is stored instead).
  struct BlockChange final {
    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BlockChange)

    // This block is waiting in memory to be written.
    // Stores a copy of the block, so we can write it asynchronously.
    explicit BlockChange(const uint8_t* aData) : mSourceBlockIndex(-1) {
      mData = MakeUnique<uint8_t[]>(BLOCK_SIZE);
      memcpy(mData.get(), aData, BLOCK_SIZE);
    }

    BlockChange(Span<const uint8_t> aData1, Span<const uint8_t> aData2)
        : mSourceBlockIndex(-1) {
      MOZ_ASSERT(aData1.Length() + aData2.Length() == BLOCK_SIZE);
      mData = MakeUnique<uint8_t[]>(BLOCK_SIZE);
      memcpy(mData.get(), aData1.Elements(), aData1.Length());
      memcpy(mData.get() + aData1.Length(), aData2.Elements(), aData2.Length());
    }

    // This block's contents are located in another file
    // block, i.e. this block has been moved.
    explicit BlockChange(int32_t aSourceBlockIndex)
        : mSourceBlockIndex(aSourceBlockIndex) {}

    UniquePtr<uint8_t[]> mData;
    const int32_t mSourceBlockIndex;

    bool IsMove() const { return mSourceBlockIndex != -1; }
    bool IsWrite() const {
      return mSourceBlockIndex == -1 && mData.get() != nullptr;
    }

   private:
    // Private destructor, to discourage deletion outside of Release():
    ~BlockChange() = default;
  };

 private:
  int64_t BlockIndexToOffset(int32_t aBlockIndex) {
    return static_cast<int64_t>(aBlockIndex) * BLOCK_SIZE;
  }

  void SetCacheFile(PRFileDesc* aFD);

  // Close file in thread and terminate thread.
  void Close();

  // Performs block writes and block moves on its own thread.
  void PerformBlockIOs();

  // Mutex which controls access to mFD and mFDCurrentPos. Don't hold
  // mDataMutex while holding mFileMutex! mFileMutex must be owned
  // while accessing any of the following data fields or methods.
  Mutex mFileMutex;
  // Moves a block already committed to file.
  nsresult MoveBlockInFile(int32_t aSourceBlockIndex, int32_t aDestBlockIndex);
  // Seeks file pointer.
  nsresult Seek(int64_t aOffset);
  // Reads data from file offset.
  nsresult ReadFromFile(int64_t aOffset, uint8_t* aDest, int32_t aBytesToRead,
                        int32_t& aBytesRead);
  nsresult WriteBlockToFile(int32_t aBlockIndex, const uint8_t* aBlockData);
  // File descriptor we're writing to. This is created externally, but
  // shutdown by us.
  PRFileDesc* mFD MOZ_PT_GUARDED_BY(mFileMutex);
  // The current file offset in the file.
  int64_t mFDCurrentPos MOZ_GUARDED_BY(mFileMutex);

  // Mutex which controls access to all data in this class, except mFD
  // and mFDCurrentPos. Don't hold mDataMutex while holding mFileMutex!
  // mDataMutex must be owned while accessing any of the following data
  // fields or methods.
  Mutex mDataMutex;
  // Ensures we either are running the event to preform IO, or an event
  // has been dispatched to preform the IO.
  // mDataMutex must be owned while calling this.
  void EnsureWriteScheduled();

  // Array of block changes to made. If mBlockChanges[offset/BLOCK_SIZE] ==
  // nullptr, then the block has no pending changes to be written, but if
  // mBlockChanges[offset/BLOCK_SIZE] != nullptr, then either there's a block
  // cached in memory waiting to be written, or this block is the target of a
  // block move.
  nsTArray<RefPtr<BlockChange> > mBlockChanges MOZ_GUARDED_BY(mDataMutex);
  // Event target upon which block writes and block moves are performed. This is
  // created upon open, and dropped on close.
  nsCOMPtr<nsISerialEventTarget> mBackgroundET MOZ_GUARDED_BY(mDataMutex);
  // Queue of pending block indexes that need to be written or moved.
  std::deque<int32_t> mChangeIndexList MOZ_GUARDED_BY(mDataMutex);
  // True if we've dispatched an event to commit all pending block changes
  // to file on mBackgroundET.
  bool mIsWriteScheduled MOZ_GUARDED_BY(mDataMutex);
  // True when a read is happening. Pending writes may be postponed, to give
  // higher priority to reads (which may be blocking the caller).
  bool mIsReading MOZ_GUARDED_BY(mDataMutex);
  // True if we've got a temporary file descriptor. Note: we don't use mFD
  // directly as that's synchronized via mFileMutex and we need to make
  // decisions about whether we can write while holding mDataMutex.
  bool mInitialized MOZ_GUARDED_BY(mDataMutex) = false;
};

}  // End namespace mozilla.

#endif /* FILE_BLOCK_CACHE_H_ */