// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef MEDIA_BLINK_MULTIBUFFER_H_ #define MEDIA_BLINK_MULTIBUFFER_H_ #include #include #include #include #include #include #include #include #include #include "base/callback.h" #include "base/hash/hash.h" #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/single_thread_task_runner.h" #include "base/synchronization/lock.h" #include "build/build_config.h" #include "media/base/data_buffer.h" #include "media/blink/interval_map.h" #include "media/blink/lru.h" #include "media/blink/media_blink_export.h" namespace media { // Used to identify a block of data in the multibuffer. // Our blocks are 32kb (1 << 15), so our maximum cacheable file size // is 1 << (15 + 31) = 64Tb typedef int32_t MultiBufferBlockId; class MultiBuffer; // This type is used to identify a block in the LRU, which is shared between // multibuffers. typedef std::pair MultiBufferGlobalBlockId; } // namespace media namespace std { template <> struct hash { std::size_t operator()(const media::MultiBufferGlobalBlockId& key) const { return base::HashInts(reinterpret_cast(key.first), key.second); } }; } // namespace std namespace media { // Freeing a lot of blocks can be expensive, to keep thing // flowing smoothly we only free a maximum of |kMaxFreesPerAdd| // blocks when a new block is added to the cache. const int kMaxFreesPerAdd = 10; // There is a simple logic for creating, destroying and deferring // data providers. Every data provider has a look-ahead region and // a look-behind region. If there are readers in the look-ahead // region, we keep reading. If not, but there are readers in the // look-behind region, we defer. If there are no readers in either // region, we destroy the data provider. // When new readers are added, new data providers are created if // the new reader doesn't fall into the look-ahead region of // an existing data provider. // This is the size of the look-ahead region. const int kMaxWaitForWriterOffset = 5; // This is the size of the look-behind region. const int kMaxWaitForReaderOffset = 50; // MultiBuffers are multi-reader multi-writer cache/buffers with // prefetching and pinning. Data is stored internally in ref-counted // blocks of identical size. |block_size_shift| is log2 of the block // size. // // Users should inherit this class and implement CreateWriter(). // TODO(hubbe): Make the multibuffer respond to memory pressure. class MEDIA_BLINK_EXPORT MultiBuffer { public: // Interface for clients wishing to read data out of this cache. // Note: It might look tempting to replace this with a callback, // but we keep and compare pointers to Readers internally. class Reader { public: Reader() {} virtual ~Reader() {} // Notifies the reader that the range of available blocks has changed. // The reader must call MultiBuffer::Observe() to activate this callback. virtual void NotifyAvailableRange( const Interval& range) = 0; private: DISALLOW_COPY_AND_ASSIGN(Reader); }; // DataProvider is the interface that MultiBuffer // uses to get data into the cache. class DataProvider { public: virtual ~DataProvider() {} // Returns the block number that is to be returned // by the next Read() call. virtual MultiBufferBlockId Tell() const = 0; // Returns true if one (or more) blocks are // availble to read. virtual bool Available() const = 0; // Returns how many bytes are available, note that Available() may still // return false even if AvailableBytes() returns a value greater than // zero if less than a full block is available. virtual int64_t AvailableBytes() const = 0; // Returns the next block. Only valid if Available() // returns true. Last block might be of a smaller size // and after the last block we will get an end-of-stream // DataBuffer. virtual scoped_refptr Read() = 0; // Ask the data provider to stop giving us data. // It's ok if the effect is not immediate. virtual void SetDeferred(bool deferred) = 0; }; // Multibuffers use a global shared LRU to free memory. // This effectively means that recently used multibuffers can // borrow memory from less recently used ones. class MEDIA_BLINK_EXPORT GlobalLRU : public base::RefCounted { public: typedef MultiBufferGlobalBlockId GlobalBlockId; explicit GlobalLRU( const scoped_refptr& task_runner); // Free elements from cache if possible. // Don't free more than |max_to_free| blocks. void TryFree(int64_t max_to_free); // Free as much memory from the cache as possible. // Only used during critical memory pressure. void TryFreeAll(); // Like TryFree, but only frees blocks if the data // number of the blocks in the cache is too large. void Prune(int64_t max_to_free); // Returns true if there are prunable blocks. bool Pruneable() const; // Incremnt the amount of data used by all multibuffers. void IncrementDataSize(int64_t blocks); // Each multibuffer is allowed a certain amount of memory, // that memory is registered by calling this function. // The memory is actually shared by all multibuffers. // When the total amount of memory used by all multibuffers // is greater than what has been registered here, we use the // LRU to decide what blocks to free first. void IncrementMaxSize(int64_t blocks); // LRU operations. void Use(MultiBuffer* multibuffer, MultiBufferBlockId id); void Remove(MultiBuffer* multibuffer, MultiBufferBlockId id); void Insert(MultiBuffer* multibuffer, MultiBufferBlockId id); bool Contains(MultiBuffer* multibuffer, MultiBufferBlockId id); int64_t Size() const; private: friend class base::RefCounted; ~GlobalLRU(); // Schedule background pruning, if needed. void SchedulePrune(); // Perform background pruning. void PruneTask(); // Max number of blocks. int64_t max_size_; // Sum of all multibuffer::data_.size(). int64_t data_size_; // True if there is a call to the background pruning outstanding. bool background_pruning_pending_; // The LRU should contain all blocks which are not pinned from // all multibuffers. LRU lru_; // Where we run our tasks. scoped_refptr task_runner_; DISALLOW_COPY_AND_ASSIGN(GlobalLRU); }; MultiBuffer(int32_t block_size_shift, const scoped_refptr& global_lru); virtual ~MultiBuffer(); // Identifies a block in the cache. // Block numbers can be calculated from byte positions as: // block_num = byte_pos >> block_size_shift typedef MultiBufferBlockId BlockId; typedef std::unordered_map> DataMap; // Registers a reader at the given position. // If the cache does not already contain |pos|, it will activate // or create data providers to make sure that the block becomes // available soon. If |pos| is already in the cache, no action is // taken, it simply lets the cache know that this reader is likely // to read pos+1, pos+2.. soon. // // Registered readers will be notified when the available range // at their position changes. The available range at |pos| is a range // from A to B where: A <= |pos|, B >= |pos| and all blocks in [A..B) // are present in the cache. When this changes, we will call // NotifyAvailableRange() on the reader. void AddReader(const BlockId& pos, Reader* reader); // Unregister a reader at block |pos|. // Often followed by a call to AddReader(pos + 1, ...); // Idempotent. void RemoveReader(const BlockId& pos, Reader* reader); // Immediately remove writers at or before |pos| if nobody needs them. // Note that we can't really do this in StopWaitFor(), because it's very // likely that StopWaitFor() is immediately followed by a call to WaitFor(). // It is also a bad idea to wait for the writers to clean themselves up when // they try to provide unwanted data to the cache. Besides the obvoius // inefficiency, it will also cause the http_cache to bypass the disk/memory // cache if we have multiple simultaneous requests going against the same // url. void CleanupWriters(const BlockId& pos); // Returns true if block |pos| is available in the cache. bool Contains(const BlockId& pos) const; // Returns the next unavailable block at or after |pos|. BlockId FindNextUnavailable(const BlockId& pos) const; // Change the pin count for a range of data blocks. // Note that blocks do not have to be present in the // cache to be pinned. // Examples: // Pin block 3, 4 & 5: PinRange(3, 6, 1); // Unpin block 4 & 5: PinRange(4, 6, -1); void PinRange(const BlockId& from, const BlockId& to, int32_t how_much); // Calls PinRange for each range in |ranges|, convenience // function for applying multiple changes to the pinned ranges. void PinRanges(const IntervalMap& ranges); // Returns a continous (but possibly empty) list of blocks starting at // |from| up to, but not including |to|. This function is thread safe. void GetBlocksThreadsafe(const BlockId& from, const BlockId& to, std::vector>* output); // Increment max cache size by |size| (counted in blocks). void IncrementMaxSize(int32_t size); // Returns how many bytes have been received by the data providers at position // |block|, which have not yet been submitted to the multibuffer cache. // The returned number should be less than the size of one block. int64_t UncommittedBytesAt(const BlockId& block); // Caller takes ownership of 'provider', cache will // not call it anymore. std::unique_ptr RemoveProvider(DataProvider* provider); // Add a writer to this cache. Cache takes ownership, and may // destroy |provider| later. (Not during this call.) void AddProvider(std::unique_ptr provider); // Transfer all data from |other| to this. void MergeFrom(MultiBuffer* other); // Accessors. const DataMap& map() const { return data_; } int32_t block_size_shift() const { return block_size_shift_; } // Setters. void SetIsClientAudioElement(bool is_client_audio_element) { is_client_audio_element_ = is_client_audio_element; } // Callback which notifies us that a data provider has // some data for us. Also called when it might be appropriate // for a provider in a deferred state to wake up. void OnDataProviderEvent(DataProvider* provider); protected: // Create a new writer at |pos| and return it. // Users needs to implemement this method. virtual std::unique_ptr CreateWriter( const BlockId& pos, bool is_client_audio_element) = 0; virtual bool RangeSupported() const = 0; // Called when the cache becomes empty. Implementations can use this // as a signal for when we should free this object and any metadata // that goes with it. virtual void OnEmpty(); private: // For testing. friend class TestMultiBuffer; enum ProviderState { ProviderStateDead, ProviderStateDefer, ProviderStateLoad }; // Can be overriden for testing. virtual void Prune(size_t max_to_free); // Remove the given blocks from the multibuffer, called from // GlobalLRU::Prune(). void ReleaseBlocks(const std::vector& blocks); // Figure out what state a writer at |pos| should be in. ProviderState SuggestProviderState(const BlockId& pos) const; // Returns true if a writer at |pos| is colliding with // output of another writer. bool ProviderCollision(const BlockId& pos) const; // Call NotifyAvailableRange(new_range) on all readers waiting // for a block in |observer_range| void NotifyAvailableRange(const Interval& observer_range, const Interval& new_range); // Max number of blocks. int64_t max_size_; // log2 of block size. int32_t block_size_shift_; // Is the client an audio element? bool is_client_audio_element_ = false; // Stores the actual data. DataMap data_; // protects data_ // Note that because data_ is only modified on the a single thread, // we don't need to lock this if we're reading data from the same thread. // Instead, we only lock this when: // * modifying data_ // * reading data_ from another thread base::Lock data_lock_; // Keeps track of readers waiting for data. std::map> readers_; // Keeps track of writers by their position. // The writers are owned by this class. std::map> writer_index_; // Gloabally shared LRU, decides which block to free next. scoped_refptr lru_; // Keeps track of what blocks are pinned. If block p is pinned, // then pinned_[p] > 0. Pinned blocks cannot be freed and should not // be present in |lru_|. IntervalMap pinned_; // present_[block] should be 1 for all blocks that are present // and 0 for all blocks that are not. Used to quickly figure out // ranges of available/unavailable blocks without iterating. IntervalMap present_; DISALLOW_COPY_AND_ASSIGN(MultiBuffer); }; } // namespace media #endif // MEDIA_BLINK_MULTIBUFFER_H_