Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* vim:set ts=2 sw=2 sts=2 et cindent: */ |
michael@0 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | #ifndef MediaCache_h_ |
michael@0 | 8 | #define MediaCache_h_ |
michael@0 | 9 | |
michael@0 | 10 | #include "nsTArray.h" |
michael@0 | 11 | #include "nsCOMPtr.h" |
michael@0 | 12 | #include "nsHashKeys.h" |
michael@0 | 13 | #include "nsTHashtable.h" |
michael@0 | 14 | |
michael@0 | 15 | class nsIPrincipal; |
michael@0 | 16 | |
michael@0 | 17 | namespace mozilla { |
michael@0 | 18 | // defined in MediaResource.h |
michael@0 | 19 | class ChannelMediaResource; |
michael@0 | 20 | class MediaByteRange; |
michael@0 | 21 | class MediaResource; |
michael@0 | 22 | class ReentrantMonitorAutoEnter; |
michael@0 | 23 | |
michael@0 | 24 | /** |
michael@0 | 25 | * Media applications want fast, "on demand" random access to media data, |
michael@0 | 26 | * for pausing, seeking, etc. But we are primarily interested |
michael@0 | 27 | * in transporting media data using HTTP over the Internet, which has |
michael@0 | 28 | * high latency to open a connection, requires a new connection for every |
michael@0 | 29 | * seek, may not even support seeking on some connections (especially |
michael@0 | 30 | * live streams), and uses a push model --- data comes from the server |
michael@0 | 31 | * and you don't have much control over the rate. Also, transferring data |
michael@0 | 32 | * over the Internet can be slow and/or unpredictable, so we want to read |
michael@0 | 33 | * ahead to buffer and cache as much data as possible. |
michael@0 | 34 | * |
michael@0 | 35 | * The job of the media cache is to resolve this impedance mismatch. |
michael@0 | 36 | * The media cache reads data from Necko channels into file-backed storage, |
michael@0 | 37 | * and offers a random-access file-like API to the stream data |
michael@0 | 38 | * (MediaCacheStream). Along the way it solves several problems: |
michael@0 | 39 | * -- The cache intelligently reads ahead to prefetch data that may be |
michael@0 | 40 | * needed in the future |
michael@0 | 41 | * -- The size of the cache is bounded so that we don't fill up |
michael@0 | 42 | * storage with read-ahead data |
michael@0 | 43 | * -- Cache replacement is managed globally so that the most valuable |
michael@0 | 44 | * data (across all streams) is retained |
michael@0 | 45 | * -- The cache can suspend Necko channels temporarily when their data is |
michael@0 | 46 | * not wanted (yet) |
michael@0 | 47 | * -- The cache translates file-like seek requests to HTTP seeks, |
michael@0 | 48 | * including optimizations like not triggering a new seek if it would |
michael@0 | 49 | * be faster to just keep reading until we reach the seek point. The |
michael@0 | 50 | * "seek to EOF" idiom to determine file size is also handled efficiently |
michael@0 | 51 | * (seeking to EOF and then seeking back to the previous offset does not |
michael@0 | 52 | * trigger any Necko activity) |
michael@0 | 53 | * -- The cache also handles the case where the server does not support |
michael@0 | 54 | * seeking |
michael@0 | 55 | * -- Necko can only send data to the main thread, but MediaCacheStream |
michael@0 | 56 | * can distribute data to any thread |
michael@0 | 57 | * -- The cache exposes APIs so clients can detect what data is |
michael@0 | 58 | * currently held |
michael@0 | 59 | * |
michael@0 | 60 | * Note that although HTTP is the most important transport and we only |
michael@0 | 61 | * support transport-level seeking via HTTP byte-ranges, the media cache |
michael@0 | 62 | * works with any kind of Necko channels and provides random access to |
michael@0 | 63 | * cached data even for, e.g., FTP streams. |
michael@0 | 64 | * |
michael@0 | 65 | * The media cache is not persistent. It does not currently allow |
michael@0 | 66 | * data from one load to be used by other loads, either within the same |
michael@0 | 67 | * browser session or across browser sessions. The media cache file |
michael@0 | 68 | * is marked "delete on close" so it will automatically disappear in the |
michael@0 | 69 | * event of a browser crash or shutdown. |
michael@0 | 70 | * |
michael@0 | 71 | * The media cache is block-based. Streams are divided into blocks of a |
michael@0 | 72 | * fixed size (currently 4K) and we cache blocks. A single cache contains |
michael@0 | 73 | * blocks for all streams. |
michael@0 | 74 | * |
michael@0 | 75 | * The cache size is controlled by the media.cache_size preference |
michael@0 | 76 | * (which is in KB). The default size is 500MB. |
michael@0 | 77 | * |
michael@0 | 78 | * The replacement policy predicts a "time of next use" for each block |
michael@0 | 79 | * in the cache. When we need to free a block, the block with the latest |
michael@0 | 80 | * "time of next use" will be evicted. Blocks are divided into |
michael@0 | 81 | * different classes, each class having its own predictor: |
michael@0 | 82 | * FREE_BLOCK: these blocks are effectively infinitely far in the future; |
michael@0 | 83 | * a free block will always be chosen for replacement before other classes |
michael@0 | 84 | * of blocks. |
michael@0 | 85 | * METADATA_BLOCK: these are blocks that contain data that has been read |
michael@0 | 86 | * by the decoder in "metadata mode", e.g. while the decoder is searching |
michael@0 | 87 | * the stream during a seek operation. These blocks are managed with an |
michael@0 | 88 | * LRU policy; the "time of next use" is predicted to be as far in the |
michael@0 | 89 | * future as the last use was in the past. |
michael@0 | 90 | * PLAYED_BLOCK: these are blocks that have not been read in "metadata |
michael@0 | 91 | * mode", and contain data behind the current decoder read point. (They |
michael@0 | 92 | * may not actually have been read by the decoder, if the decoder seeked |
michael@0 | 93 | * forward.) These blocks are managed with an LRU policy except that we add |
michael@0 | 94 | * REPLAY_DELAY seconds of penalty to their predicted "time of next use", |
michael@0 | 95 | * to reflect the uncertainty about whether replay will actually happen |
michael@0 | 96 | * or not. |
michael@0 | 97 | * READAHEAD_BLOCK: these are blocks that have not been read in |
michael@0 | 98 | * "metadata mode" and that are entirely ahead of the current decoder |
michael@0 | 99 | * read point. (They may actually have been read by the decoder in the |
michael@0 | 100 | * past if the decoder has since seeked backward.) We predict the |
michael@0 | 101 | * time of next use for these blocks by assuming steady playback and |
michael@0 | 102 | * dividing the number of bytes between the block and the current decoder |
michael@0 | 103 | * read point by the decoder's estimate of its playback rate in bytes |
michael@0 | 104 | * per second. This ensures that the blocks farthest ahead are considered |
michael@0 | 105 | * least valuable. |
michael@0 | 106 | * For efficient prediction of the "latest time of next use", we maintain |
michael@0 | 107 | * linked lists of blocks in each class, ordering blocks by time of |
michael@0 | 108 | * next use. READAHEAD_BLOCKS have one linked list per stream, since their |
michael@0 | 109 | * time of next use depends on stream parameters, but the other lists |
michael@0 | 110 | * are global. |
michael@0 | 111 | * |
michael@0 | 112 | * A block containing a current decoder read point can contain data |
michael@0 | 113 | * both behind and ahead of the read point. It will be classified as a |
michael@0 | 114 | * PLAYED_BLOCK but we will give it special treatment so it is never |
michael@0 | 115 | * evicted --- it actually contains the highest-priority readahead data |
michael@0 | 116 | * as well as played data. |
michael@0 | 117 | * |
michael@0 | 118 | * "Time of next use" estimates are also used for flow control. When |
michael@0 | 119 | * reading ahead we can predict the time of next use for the data that |
michael@0 | 120 | * will be read. If the predicted time of next use is later then the |
michael@0 | 121 | * prediction for all currently cached blocks, and the cache is full, then |
michael@0 | 122 | * we should suspend reading from the Necko channel. |
michael@0 | 123 | * |
michael@0 | 124 | * Unfortunately suspending the Necko channel can't immediately stop the |
michael@0 | 125 | * flow of data from the server. First our desire to suspend has to be |
michael@0 | 126 | * transmitted to the server (in practice, Necko stops reading from the |
michael@0 | 127 | * socket, which causes the kernel to shrink its advertised TCP receive |
michael@0 | 128 | * window size to zero). Then the server can stop sending the data, but |
michael@0 | 129 | * we will receive data roughly corresponding to the product of the link |
michael@0 | 130 | * bandwidth multiplied by the round-trip latency. We deal with this by |
michael@0 | 131 | * letting the cache overflow temporarily and then trimming it back by |
michael@0 | 132 | * moving overflowing blocks back into the body of the cache, replacing |
michael@0 | 133 | * less valuable blocks as they become available. We try to avoid simply |
michael@0 | 134 | * discarding overflowing readahead data. |
michael@0 | 135 | * |
michael@0 | 136 | * All changes to the actual contents of the cache happen on the main |
michael@0 | 137 | * thread, since that's where Necko's notifications happen. |
michael@0 | 138 | * |
michael@0 | 139 | * The media cache maintains at most one Necko channel for each stream. |
michael@0 | 140 | * (In the future it might be advantageous to relax this, e.g. so that a |
michael@0 | 141 | * seek to near the end of the file can happen without disturbing |
michael@0 | 142 | * the loading of data from the beginning of the file.) The Necko channel |
michael@0 | 143 | * is managed through ChannelMediaResource; MediaCache does not |
michael@0 | 144 | * depend on Necko directly. |
michael@0 | 145 | * |
michael@0 | 146 | * Every time something changes that might affect whether we want to |
michael@0 | 147 | * read from a Necko channel, or whether we want to seek on the Necko |
michael@0 | 148 | * channel --- such as data arriving or data being consumed by the |
michael@0 | 149 | * decoder --- we asynchronously trigger MediaCache::Update on the main |
michael@0 | 150 | * thread. That method implements most cache policy. It evaluates for |
michael@0 | 151 | * each stream whether we want to suspend or resume the stream and what |
michael@0 | 152 | * offset we should seek to, if any. It is also responsible for trimming |
michael@0 | 153 | * back the cache size to its desired limit by moving overflowing blocks |
michael@0 | 154 | * into the main part of the cache. |
michael@0 | 155 | * |
michael@0 | 156 | * Streams can be opened in non-seekable mode. In non-seekable mode, |
michael@0 | 157 | * the cache will only call ChannelMediaResource::CacheClientSeek with |
michael@0 | 158 | * a 0 offset. The cache tries hard not to discard readahead data |
michael@0 | 159 | * for non-seekable streams, since that could trigger a potentially |
michael@0 | 160 | * disastrous re-read of the entire stream. It's up to cache clients |
michael@0 | 161 | * to try to avoid requesting seeks on such streams. |
michael@0 | 162 | * |
michael@0 | 163 | * MediaCache has a single internal monitor for all synchronization. |
michael@0 | 164 | * This is treated as the lowest level monitor in the media code. So, |
michael@0 | 165 | * we must not acquire any MediaDecoder locks or MediaResource locks |
michael@0 | 166 | * while holding the MediaCache lock. But it's OK to hold those locks |
michael@0 | 167 | * and then get the MediaCache lock. |
michael@0 | 168 | * |
michael@0 | 169 | * MediaCache associates a principal with each stream. CacheClientSeek |
michael@0 | 170 | * can trigger new HTTP requests; due to redirects to other domains, |
michael@0 | 171 | * each HTTP load can return data with a different principal. This |
michael@0 | 172 | * principal must be passed to NotifyDataReceived, and MediaCache |
michael@0 | 173 | * will detect when different principals are associated with data in the |
michael@0 | 174 | * same stream, and replace them with a null principal. |
michael@0 | 175 | */ |
michael@0 | 176 | class MediaCache; |
michael@0 | 177 | |
michael@0 | 178 | /** |
michael@0 | 179 | * If the cache fails to initialize then Init will fail, so nonstatic |
michael@0 | 180 | * methods of this class can assume gMediaCache is non-null. |
michael@0 | 181 | * |
michael@0 | 182 | * This class can be directly embedded as a value. |
michael@0 | 183 | */ |
michael@0 | 184 | class MediaCacheStream { |
michael@0 | 185 | public: |
michael@0 | 186 | enum { |
michael@0 | 187 | // This needs to be a power of two |
michael@0 | 188 | BLOCK_SIZE = 32768 |
michael@0 | 189 | }; |
michael@0 | 190 | enum ReadMode { |
michael@0 | 191 | MODE_METADATA, |
michael@0 | 192 | MODE_PLAYBACK |
michael@0 | 193 | }; |
michael@0 | 194 | |
michael@0 | 195 | // aClient provides the underlying transport that cache will use to read |
michael@0 | 196 | // data for this stream. |
michael@0 | 197 | MediaCacheStream(ChannelMediaResource* aClient); |
michael@0 | 198 | ~MediaCacheStream(); |
michael@0 | 199 | |
michael@0 | 200 | // Set up this stream with the cache. Can fail on OOM. One |
michael@0 | 201 | // of InitAsClone or Init must be called before any other method on |
michael@0 | 202 | // this class. Does nothing if already initialized. |
michael@0 | 203 | nsresult Init(); |
michael@0 | 204 | |
michael@0 | 205 | // Set up this stream with the cache, assuming it's for the same data |
michael@0 | 206 | // as the aOriginal stream. Can fail on OOM. Exactly one |
michael@0 | 207 | // of InitAsClone or Init must be called before any other method on |
michael@0 | 208 | // this class. Does nothing if already initialized. |
michael@0 | 209 | nsresult InitAsClone(MediaCacheStream* aOriginal); |
michael@0 | 210 | |
michael@0 | 211 | // These are called on the main thread. |
michael@0 | 212 | // Tell us whether the stream is seekable or not. Non-seekable streams |
michael@0 | 213 | // will always pass 0 for aOffset to CacheClientSeek. This should only |
michael@0 | 214 | // be called while the stream is at channel offset 0. Seekability can |
michael@0 | 215 | // change during the lifetime of the MediaCacheStream --- every time |
michael@0 | 216 | // we do an HTTP load the seekability may be different (and sometimes |
michael@0 | 217 | // is, in practice, due to the effects of caching proxies). |
michael@0 | 218 | void SetTransportSeekable(bool aIsTransportSeekable); |
michael@0 | 219 | // This must be called (and return) before the ChannelMediaResource |
michael@0 | 220 | // used to create this MediaCacheStream is deleted. |
michael@0 | 221 | void Close(); |
michael@0 | 222 | // This returns true when the stream has been closed |
michael@0 | 223 | bool IsClosed() const { return mClosed; } |
michael@0 | 224 | // Returns true when this stream is can be shared by a new resource load |
michael@0 | 225 | bool IsAvailableForSharing() const |
michael@0 | 226 | { |
michael@0 | 227 | return !mClosed && |
michael@0 | 228 | (!mDidNotifyDataEnded || NS_SUCCEEDED(mNotifyDataEndedStatus)); |
michael@0 | 229 | } |
michael@0 | 230 | // Get the principal for this stream. Anything accessing the contents of |
michael@0 | 231 | // this stream must have a principal that subsumes this principal. |
michael@0 | 232 | nsIPrincipal* GetCurrentPrincipal() { return mPrincipal; } |
michael@0 | 233 | // Ensure a global media cache update has run with this stream present. |
michael@0 | 234 | // This ensures the cache has had a chance to suspend or unsuspend this stream. |
michael@0 | 235 | // Called only on main thread. This can change the state of streams, fire |
michael@0 | 236 | // notifications, etc. |
michael@0 | 237 | void EnsureCacheUpdate(); |
michael@0 | 238 | |
michael@0 | 239 | // These callbacks are called on the main thread by the client |
michael@0 | 240 | // when data has been received via the channel. |
michael@0 | 241 | // Tells the cache what the server said the data length is going to be. |
michael@0 | 242 | // The actual data length may be greater (we receive more data than |
michael@0 | 243 | // specified) or smaller (the stream ends before we reach the given |
michael@0 | 244 | // length), because servers can lie. The server's reported data length |
michael@0 | 245 | // *and* the actual data length can even vary over time because a |
michael@0 | 246 | // misbehaving server may feed us a different stream after each seek |
michael@0 | 247 | // operation. So this is really just a hint. The cache may however |
michael@0 | 248 | // stop reading (suspend the channel) when it thinks we've read all the |
michael@0 | 249 | // data available based on an incorrect reported length. Seeks relative |
michael@0 | 250 | // EOF also depend on the reported length if we haven't managed to |
michael@0 | 251 | // read the whole stream yet. |
michael@0 | 252 | void NotifyDataLength(int64_t aLength); |
michael@0 | 253 | // Notifies the cache that a load has begun. We pass the offset |
michael@0 | 254 | // because in some cases the offset might not be what the cache |
michael@0 | 255 | // requested. In particular we might unexpectedly start providing |
michael@0 | 256 | // data at offset 0. This need not be called if the offset is the |
michael@0 | 257 | // offset that the cache requested in |
michael@0 | 258 | // ChannelMediaResource::CacheClientSeek. This can be called at any |
michael@0 | 259 | // time by the client, not just after a CacheClientSeek. |
michael@0 | 260 | void NotifyDataStarted(int64_t aOffset); |
michael@0 | 261 | // Notifies the cache that data has been received. The stream already |
michael@0 | 262 | // knows the offset because data is received in sequence and |
michael@0 | 263 | // the starting offset is known via NotifyDataStarted or because |
michael@0 | 264 | // the cache requested the offset in |
michael@0 | 265 | // ChannelMediaResource::CacheClientSeek, or because it defaulted to 0. |
michael@0 | 266 | // We pass in the principal that was used to load this data. |
michael@0 | 267 | void NotifyDataReceived(int64_t aSize, const char* aData, |
michael@0 | 268 | nsIPrincipal* aPrincipal); |
michael@0 | 269 | // Notifies the cache that the current bytes should be written to disk. |
michael@0 | 270 | // Called on the main thread. |
michael@0 | 271 | void FlushPartialBlock(); |
michael@0 | 272 | // Notifies the cache that the channel has closed with the given status. |
michael@0 | 273 | void NotifyDataEnded(nsresult aStatus); |
michael@0 | 274 | |
michael@0 | 275 | // These methods can be called on any thread. |
michael@0 | 276 | // Cached blocks associated with this stream will not be evicted |
michael@0 | 277 | // while the stream is pinned. |
michael@0 | 278 | void Pin(); |
michael@0 | 279 | void Unpin(); |
michael@0 | 280 | // See comments above for NotifyDataLength about how the length |
michael@0 | 281 | // can vary over time. Returns -1 if no length is known. Returns the |
michael@0 | 282 | // reported length if we haven't got any better information. If |
michael@0 | 283 | // the stream ended normally we return the length we actually got. |
michael@0 | 284 | // If we've successfully read data beyond the originally reported length, |
michael@0 | 285 | // we return the end of the data we've read. |
michael@0 | 286 | int64_t GetLength(); |
michael@0 | 287 | // Returns the unique resource ID. Call only on the main thread or while |
michael@0 | 288 | // holding the media cache lock. |
michael@0 | 289 | int64_t GetResourceID() { return mResourceID; } |
michael@0 | 290 | // Returns the end of the bytes starting at the given offset |
michael@0 | 291 | // which are in cache. |
michael@0 | 292 | int64_t GetCachedDataEnd(int64_t aOffset); |
michael@0 | 293 | // Returns the offset of the first byte of cached data at or after aOffset, |
michael@0 | 294 | // or -1 if there is no such cached data. |
michael@0 | 295 | int64_t GetNextCachedData(int64_t aOffset); |
michael@0 | 296 | // Fills aRanges with the ByteRanges representing the data which is currently |
michael@0 | 297 | // cached. Locks the media cache while running, to prevent any ranges |
michael@0 | 298 | // growing. The stream should be pinned while this runs and while its results |
michael@0 | 299 | // are used, to ensure no data is evicted. |
michael@0 | 300 | nsresult GetCachedRanges(nsTArray<MediaByteRange>& aRanges); |
michael@0 | 301 | |
michael@0 | 302 | // Reads from buffered data only. Will fail if not all data to be read is |
michael@0 | 303 | // in the cache. Will not mark blocks as read. Can be called from the main |
michael@0 | 304 | // thread. It's the caller's responsibility to wrap the call in a pin/unpin, |
michael@0 | 305 | // and also to check that the range they want is cached before calling this. |
michael@0 | 306 | nsresult ReadFromCache(char* aBuffer, |
michael@0 | 307 | int64_t aOffset, |
michael@0 | 308 | int64_t aCount); |
michael@0 | 309 | |
michael@0 | 310 | // IsDataCachedToEndOfStream returns true if all the data from |
michael@0 | 311 | // aOffset to the end of the stream (the server-reported end, if the |
michael@0 | 312 | // real end is not known) is in cache. If we know nothing about the |
michael@0 | 313 | // end of the stream, this returns false. |
michael@0 | 314 | bool IsDataCachedToEndOfStream(int64_t aOffset); |
michael@0 | 315 | // The mode is initially MODE_PLAYBACK. |
michael@0 | 316 | void SetReadMode(ReadMode aMode); |
michael@0 | 317 | // This is the client's estimate of the playback rate assuming |
michael@0 | 318 | // the media plays continuously. The cache can't guess this itself |
michael@0 | 319 | // because it doesn't know when the decoder was paused, buffering, etc. |
michael@0 | 320 | // Do not pass zero. |
michael@0 | 321 | void SetPlaybackRate(uint32_t aBytesPerSecond); |
michael@0 | 322 | // Returns the last set value of SetTransportSeekable. |
michael@0 | 323 | bool IsTransportSeekable(); |
michael@0 | 324 | |
michael@0 | 325 | // Returns true when all streams for this resource are suspended or their |
michael@0 | 326 | // channel has ended. |
michael@0 | 327 | bool AreAllStreamsForResourceSuspended(); |
michael@0 | 328 | |
michael@0 | 329 | // These methods must be called on a different thread from the main |
michael@0 | 330 | // thread. They should always be called on the same thread for a given |
michael@0 | 331 | // stream. |
michael@0 | 332 | // This can fail when aWhence is NS_SEEK_END and no stream length |
michael@0 | 333 | // is known. |
michael@0 | 334 | nsresult Seek(int32_t aWhence, int64_t aOffset); |
michael@0 | 335 | int64_t Tell(); |
michael@0 | 336 | // *aBytes gets the number of bytes that were actually read. This can |
michael@0 | 337 | // be less than aCount. If the first byte of data is not in the cache, |
michael@0 | 338 | // this will block until the data is available or the stream is |
michael@0 | 339 | // closed, otherwise it won't block. |
michael@0 | 340 | nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes); |
michael@0 | 341 | // Seeks to aOffset in the stream then performs a Read operation. See |
michael@0 | 342 | // 'Read' for argument and return details. |
michael@0 | 343 | nsresult ReadAt(int64_t aOffset, char* aBuffer, |
michael@0 | 344 | uint32_t aCount, uint32_t* aBytes); |
michael@0 | 345 | |
michael@0 | 346 | size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const; |
michael@0 | 347 | |
michael@0 | 348 | private: |
michael@0 | 349 | friend class MediaCache; |
michael@0 | 350 | |
michael@0 | 351 | /** |
michael@0 | 352 | * A doubly-linked list of blocks. Add/Remove/Get methods are all |
michael@0 | 353 | * constant time. We declare this here so that a stream can contain a |
michael@0 | 354 | * BlockList of its read-ahead blocks. Blocks are referred to by index |
michael@0 | 355 | * into the MediaCache::mIndex array. |
michael@0 | 356 | * |
michael@0 | 357 | * Blocks can belong to more than one list at the same time, because |
michael@0 | 358 | * the next/prev pointers are not stored in the block. |
michael@0 | 359 | */ |
michael@0 | 360 | class BlockList { |
michael@0 | 361 | public: |
michael@0 | 362 | BlockList() : mFirstBlock(-1), mCount(0) {} |
michael@0 | 363 | ~BlockList() { |
michael@0 | 364 | NS_ASSERTION(mFirstBlock == -1 && mCount == 0, |
michael@0 | 365 | "Destroying non-empty block list"); |
michael@0 | 366 | } |
michael@0 | 367 | void AddFirstBlock(int32_t aBlock); |
michael@0 | 368 | void AddAfter(int32_t aBlock, int32_t aBefore); |
michael@0 | 369 | void RemoveBlock(int32_t aBlock); |
michael@0 | 370 | // Returns the first block in the list, or -1 if empty |
michael@0 | 371 | int32_t GetFirstBlock() const { return mFirstBlock; } |
michael@0 | 372 | // Returns the last block in the list, or -1 if empty |
michael@0 | 373 | int32_t GetLastBlock() const; |
michael@0 | 374 | // Returns the next block in the list after aBlock or -1 if |
michael@0 | 375 | // aBlock is the last block |
michael@0 | 376 | int32_t GetNextBlock(int32_t aBlock) const; |
michael@0 | 377 | // Returns the previous block in the list before aBlock or -1 if |
michael@0 | 378 | // aBlock is the first block |
michael@0 | 379 | int32_t GetPrevBlock(int32_t aBlock) const; |
michael@0 | 380 | bool IsEmpty() const { return mFirstBlock < 0; } |
michael@0 | 381 | int32_t GetCount() const { return mCount; } |
michael@0 | 382 | // The contents of aBlockIndex1 and aBlockIndex2 have been swapped |
michael@0 | 383 | void NotifyBlockSwapped(int32_t aBlockIndex1, int32_t aBlockIndex2); |
michael@0 | 384 | #ifdef DEBUG |
michael@0 | 385 | // Verify linked-list invariants |
michael@0 | 386 | void Verify(); |
michael@0 | 387 | #else |
michael@0 | 388 | void Verify() {} |
michael@0 | 389 | #endif |
michael@0 | 390 | |
michael@0 | 391 | size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; |
michael@0 | 392 | |
michael@0 | 393 | private: |
michael@0 | 394 | struct Entry : public nsUint32HashKey { |
michael@0 | 395 | Entry(KeyTypePointer aKey) : nsUint32HashKey(aKey) { } |
michael@0 | 396 | Entry(const Entry& toCopy) : nsUint32HashKey(&toCopy.GetKey()), |
michael@0 | 397 | mNextBlock(toCopy.mNextBlock), mPrevBlock(toCopy.mPrevBlock) {} |
michael@0 | 398 | |
michael@0 | 399 | int32_t mNextBlock; |
michael@0 | 400 | int32_t mPrevBlock; |
michael@0 | 401 | }; |
michael@0 | 402 | nsTHashtable<Entry> mEntries; |
michael@0 | 403 | |
michael@0 | 404 | // The index of the first block in the list, or -1 if the list is empty. |
michael@0 | 405 | int32_t mFirstBlock; |
michael@0 | 406 | // The number of blocks in the list. |
michael@0 | 407 | int32_t mCount; |
michael@0 | 408 | }; |
michael@0 | 409 | |
michael@0 | 410 | // Returns the end of the bytes starting at the given offset |
michael@0 | 411 | // which are in cache. |
michael@0 | 412 | // This method assumes that the cache monitor is held and can be called on |
michael@0 | 413 | // any thread. |
michael@0 | 414 | int64_t GetCachedDataEndInternal(int64_t aOffset); |
michael@0 | 415 | // Returns the offset of the first byte of cached data at or after aOffset, |
michael@0 | 416 | // or -1 if there is no such cached data. |
michael@0 | 417 | // This method assumes that the cache monitor is held and can be called on |
michael@0 | 418 | // any thread. |
michael@0 | 419 | int64_t GetNextCachedDataInternal(int64_t aOffset); |
michael@0 | 420 | // Writes |mPartialBlock| to disk. |
michael@0 | 421 | // Used by |NotifyDataEnded| and |FlushPartialBlock|. |
michael@0 | 422 | // If |aNotifyAll| is true, this function will wake up readers who may be |
michael@0 | 423 | // waiting on the media cache monitor. Called on the main thread only. |
michael@0 | 424 | void FlushPartialBlockInternal(bool aNotify); |
michael@0 | 425 | // A helper function to do the work of closing the stream. Assumes |
michael@0 | 426 | // that the cache monitor is held. Main thread only. |
michael@0 | 427 | // aReentrantMonitor is the nsAutoReentrantMonitor wrapper holding the cache monitor. |
michael@0 | 428 | // This is used to NotifyAll to wake up threads that might be |
michael@0 | 429 | // blocked on reading from this stream. |
michael@0 | 430 | void CloseInternal(ReentrantMonitorAutoEnter& aReentrantMonitor); |
michael@0 | 431 | // Update mPrincipal given that data has been received from aPrincipal |
michael@0 | 432 | bool UpdatePrincipal(nsIPrincipal* aPrincipal); |
michael@0 | 433 | |
michael@0 | 434 | // These fields are main-thread-only. |
michael@0 | 435 | ChannelMediaResource* mClient; |
michael@0 | 436 | nsCOMPtr<nsIPrincipal> mPrincipal; |
michael@0 | 437 | // Set to true when Init or InitAsClone has been called |
michael@0 | 438 | bool mInitialized; |
michael@0 | 439 | // Set to true when MediaCache::Update() has finished while this stream |
michael@0 | 440 | // was present. |
michael@0 | 441 | bool mHasHadUpdate; |
michael@0 | 442 | // Set to true when the stream has been closed either explicitly or |
michael@0 | 443 | // due to an internal cache error |
michael@0 | 444 | bool mClosed; |
michael@0 | 445 | // True if CacheClientNotifyDataEnded has been called for this stream. |
michael@0 | 446 | bool mDidNotifyDataEnded; |
michael@0 | 447 | |
michael@0 | 448 | // The following fields must be written holding the cache's monitor and |
michael@0 | 449 | // only on the main thread, thus can be read either on the main thread |
michael@0 | 450 | // or while holding the cache's monitor. |
michael@0 | 451 | |
michael@0 | 452 | // This is a unique ID representing the resource we're loading. |
michael@0 | 453 | // All streams with the same mResourceID are loading the same |
michael@0 | 454 | // underlying resource and should share data. |
michael@0 | 455 | int64_t mResourceID; |
michael@0 | 456 | // The last reported seekability state for the underlying channel |
michael@0 | 457 | bool mIsTransportSeekable; |
michael@0 | 458 | // True if the cache has suspended our channel because the cache is |
michael@0 | 459 | // full and the priority of the data that would be received is lower |
michael@0 | 460 | // than the priority of the data already in the cache |
michael@0 | 461 | bool mCacheSuspended; |
michael@0 | 462 | // True if the channel ended and we haven't seeked it again. |
michael@0 | 463 | bool mChannelEnded; |
michael@0 | 464 | // The offset where the next data from the channel will arrive |
michael@0 | 465 | int64_t mChannelOffset; |
michael@0 | 466 | // The reported or discovered length of the data, or -1 if nothing is |
michael@0 | 467 | // known |
michael@0 | 468 | int64_t mStreamLength; |
michael@0 | 469 | |
michael@0 | 470 | // The following fields are protected by the cache's monitor can can be written |
michael@0 | 471 | // by any thread. |
michael@0 | 472 | |
michael@0 | 473 | // The offset where the reader is positioned in the stream |
michael@0 | 474 | int64_t mStreamOffset; |
michael@0 | 475 | // For each block in the stream data, maps to the cache entry for the |
michael@0 | 476 | // block, or -1 if the block is not cached. |
michael@0 | 477 | nsTArray<int32_t> mBlocks; |
michael@0 | 478 | // The list of read-ahead blocks, ordered by stream offset; the first |
michael@0 | 479 | // block is the earliest in the stream (so the last block will be the |
michael@0 | 480 | // least valuable). |
michael@0 | 481 | BlockList mReadaheadBlocks; |
michael@0 | 482 | // The list of metadata blocks; the first block is the most recently used |
michael@0 | 483 | BlockList mMetadataBlocks; |
michael@0 | 484 | // The list of played-back blocks; the first block is the most recently used |
michael@0 | 485 | BlockList mPlayedBlocks; |
michael@0 | 486 | // The last reported estimate of the decoder's playback rate |
michael@0 | 487 | uint32_t mPlaybackBytesPerSecond; |
michael@0 | 488 | // The number of times this stream has been Pinned without a |
michael@0 | 489 | // corresponding Unpin |
michael@0 | 490 | uint32_t mPinCount; |
michael@0 | 491 | // The status used when we did CacheClientNotifyDataEnded. Only valid |
michael@0 | 492 | // when mDidNotifyDataEnded is true. |
michael@0 | 493 | nsresult mNotifyDataEndedStatus; |
michael@0 | 494 | // The last reported read mode |
michael@0 | 495 | ReadMode mCurrentMode; |
michael@0 | 496 | // True if some data in mPartialBlockBuffer has been read as metadata |
michael@0 | 497 | bool mMetadataInPartialBlockBuffer; |
michael@0 | 498 | |
michael@0 | 499 | // The following field is protected by the cache's monitor but are |
michael@0 | 500 | // only written on the main thread. |
michael@0 | 501 | |
michael@0 | 502 | // Data received for the block containing mChannelOffset. Data needs |
michael@0 | 503 | // to wait here so we can write back a complete block. The first |
michael@0 | 504 | // mChannelOffset%BLOCK_SIZE bytes have been filled in with good data, |
michael@0 | 505 | // the rest are garbage. |
michael@0 | 506 | // Use int64_t so that the data is well-aligned. |
michael@0 | 507 | // Heap allocate this buffer since the exact power-of-2 will cause allocation |
michael@0 | 508 | // slop when combined with the rest of the object members. |
michael@0 | 509 | nsAutoArrayPtr<int64_t> mPartialBlockBuffer; |
michael@0 | 510 | }; |
michael@0 | 511 | |
michael@0 | 512 | } // namespace mozilla |
michael@0 | 513 | |
michael@0 | 514 | #endif |