tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

SourceBuffer.h (18912B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 /**
      7 * SourceBuffer is a single producer, multiple consumer data structure used for
      8 * storing image source (compressed) data.
      9 */
     10 
     11 #ifndef mozilla_image_sourcebuffer_h
     12 #define mozilla_image_sourcebuffer_h
     13 
     14 #include <algorithm>
     15 #include <utility>
     16 
     17 #include "mozilla/Maybe.h"
     18 #include "mozilla/MemoryReporting.h"
     19 #include "mozilla/Mutex.h"
     20 #include "mozilla/RefCounted.h"
     21 #include "mozilla/RefPtr.h"
     22 #include "nsTArray.h"
     23 
     24 class nsIInputStream;
     25 
     26 namespace mozilla {
     27 namespace image {
     28 
     29 class SourceBuffer;
     30 
     31 /**
     32 * IResumable is an interface for classes that can schedule themselves to resume
     33 * their work later. An implementation of IResumable generally should post a
     34 * runnable to some event target which continues the work of the task.
     35 */
     36 struct IResumable {
     37  MOZ_DECLARE_REFCOUNTED_TYPENAME(IResumable)
     38 
     39  // Subclasses may or may not be XPCOM classes, so we just require that they
     40  // implement AddRef and Release.
     41  NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
     42 
     43  virtual void Resume() = 0;
     44 
     45 protected:
     46  virtual ~IResumable() {}
     47 };
     48 
     49 /**
     50 * SourceBufferIterator is a class that allows consumers of image source data to
     51 * read the contents of a SourceBuffer sequentially.
     52 *
     53 * Consumers can advance through the SourceBuffer by calling
     54 * AdvanceOrScheduleResume() repeatedly. After every advance, they should call
     55 * check the return value, which will tell them the iterator's new state.
     56 *
     57 * If WAITING is returned, AdvanceOrScheduleResume() has arranged
     58 * to call the consumer's Resume() method later, so the consumer should save its
     59 * state if needed and stop running.
     60 *
     61 * If the iterator's new state is READY, then the consumer can call Data() and
     62 * Length() to read new data from the SourceBuffer.
     63 *
     64 * Finally, in the COMPLETE state the consumer can call CompletionStatus() to
     65 * get the status passed to SourceBuffer::Complete().
     66 */
     67 class SourceBufferIterator final {
     68 public:
     69  enum State {
     70    START,    // The iterator is at the beginning of the buffer.
     71    READY,    // The iterator is pointing to new data.
     72    WAITING,  // The iterator is blocked and the caller must yield.
     73    COMPLETE  // The iterator is pointing to the end of the buffer.
     74  };
     75 
     76  explicit SourceBufferIterator(SourceBuffer* aOwner, size_t aReadLimit)
     77      : mOwner(aOwner),
     78        mState(START),
     79        mChunkCount(0),
     80        mByteCount(0),
     81        mRemainderToRead(aReadLimit) {
     82    MOZ_ASSERT(aOwner);
     83    mData.mIterating.mChunk = 0;
     84    mData.mIterating.mData = nullptr;
     85    mData.mIterating.mOffset = 0;
     86    mData.mIterating.mAvailableLength = 0;
     87    mData.mIterating.mNextReadLength = 0;
     88  }
     89 
     90  SourceBufferIterator(SourceBufferIterator&& aOther)
     91      : mOwner(std::move(aOther.mOwner)),
     92        mState(aOther.mState),
     93        mData(aOther.mData),
     94        mChunkCount(aOther.mChunkCount),
     95        mByteCount(aOther.mByteCount),
     96        mRemainderToRead(aOther.mRemainderToRead) {}
     97 
     98  ~SourceBufferIterator();
     99 
    100  SourceBufferIterator& operator=(SourceBufferIterator&& aOther);
    101 
    102  /**
    103   * Returns true if there are no more than @aBytes remaining in the
    104   * SourceBuffer. If the SourceBuffer is not yet complete, returns false.
    105   */
    106  bool RemainingBytesIsNoMoreThan(size_t aBytes) const;
    107 
    108  /**
    109   * Advances the iterator through the SourceBuffer if possible. Advances no
    110   * more than @aRequestedBytes bytes. (Use SIZE_MAX to advance as much as
    111   * possible.)
    112   *
    113   * This is a wrapper around AdvanceOrScheduleResume() that makes it clearer at
    114   * the callsite when the no resuming is intended.
    115   *
    116   * @return State::READY if the iterator was successfully advanced.
    117   *         State::WAITING if the iterator could not be advanced because it's
    118   *           at the end of the underlying SourceBuffer, but the SourceBuffer
    119   *           may still receive additional data.
    120   *         State::COMPLETE if the iterator could not be advanced because it's
    121   *           at the end of the underlying SourceBuffer and the SourceBuffer is
    122   *           marked complete (i.e., it will never receive any additional
    123   *           data).
    124   */
    125  State Advance(size_t aRequestedBytes) {
    126    return AdvanceOrScheduleResume(aRequestedBytes, nullptr);
    127  }
    128 
    129  /**
    130   * Advances the iterator through the SourceBuffer if possible. Advances no
    131   * more than @aRequestedBytes bytes. (Use SIZE_MAX to advance as much as
    132   * possible.) If advancing is not possible and @aConsumer is not null,
    133   * arranges to call the @aConsumer's Resume() method when more data is
    134   * available.
    135   *
    136   * @return State::READY if the iterator was successfully advanced.
    137   *         State::WAITING if the iterator could not be advanced because it's
    138   *           at the end of the underlying SourceBuffer, but the SourceBuffer
    139   *           may still receive additional data. @aConsumer's Resume() method
    140   *           will be called when additional data is available.
    141   *         State::COMPLETE if the iterator could not be advanced because it's
    142   *           at the end of the underlying SourceBuffer and the SourceBuffer is
    143   *           marked complete (i.e., it will never receive any additional
    144   *           data).
    145   */
    146  State AdvanceOrScheduleResume(size_t aRequestedBytes, IResumable* aConsumer);
    147 
    148  /// If at the end, returns the status passed to SourceBuffer::Complete().
    149  nsresult CompletionStatus() const {
    150    MOZ_ASSERT(mState == COMPLETE,
    151               "Calling CompletionStatus() in the wrong state");
    152    return mState == COMPLETE ? mData.mAtEnd.mStatus : NS_OK;
    153  }
    154 
    155  /// If we're ready to read, returns a pointer to the new data.
    156  const char* Data() const {
    157    MOZ_ASSERT(mState == READY, "Calling Data() in the wrong state");
    158    return mState == READY ? mData.mIterating.mData + mData.mIterating.mOffset
    159                           : nullptr;
    160  }
    161 
    162  /// If we're ready to read, returns the length of the new data.
    163  size_t Length() const {
    164    MOZ_ASSERT(mState == READY, "Calling Length() in the wrong state");
    165    return mState == READY ? mData.mIterating.mNextReadLength : 0;
    166  }
    167 
    168  /// If we're ready to read, returns whether or not everything available thus
    169  /// far has been in the same contiguous buffer.
    170  bool IsContiguous() const {
    171    MOZ_ASSERT(mState == READY, "Calling IsContiguous() in the wrong state");
    172    return mState == READY ? mData.mIterating.mChunk == 0 : false;
    173  }
    174 
    175  /// @return a count of the chunks we've advanced through.
    176  uint32_t ChunkCount() const { return mChunkCount; }
    177 
    178  /// @return a count of the bytes in all chunks we've advanced through.
    179  size_t ByteCount() const { return mByteCount; }
    180 
    181  /// @return the source buffer which owns the iterator.
    182  SourceBuffer* Owner() const {
    183    MOZ_ASSERT(mOwner);
    184    return mOwner;
    185  }
    186 
    187  /// @return the current offset from the beginning of the buffer.
    188  size_t Position() const {
    189    return mByteCount - mData.mIterating.mAvailableLength;
    190  }
    191 
    192 private:
    193  friend class SourceBuffer;
    194 
    195  SourceBufferIterator(const SourceBufferIterator&) = delete;
    196  SourceBufferIterator& operator=(const SourceBufferIterator&) = delete;
    197 
    198  bool HasMore() const { return mState != COMPLETE; }
    199 
    200  State AdvanceFromLocalBuffer(size_t aRequestedBytes) {
    201    MOZ_ASSERT(mState == READY, "Advancing in the wrong state");
    202    MOZ_ASSERT(mData.mIterating.mAvailableLength > 0,
    203               "The local buffer shouldn't be empty");
    204    MOZ_ASSERT(mData.mIterating.mNextReadLength == 0,
    205               "Advancing without consuming previous data");
    206 
    207    mData.mIterating.mNextReadLength =
    208        std::min(mData.mIterating.mAvailableLength, aRequestedBytes);
    209 
    210    return READY;
    211  }
    212 
    213  State SetReady(uint32_t aChunk, const char* aData, size_t aOffset,
    214                 size_t aAvailableLength, size_t aRequestedBytes) {
    215    MOZ_ASSERT(mState != COMPLETE);
    216    mState = READY;
    217 
    218    // Prevent the iterator from reporting more data than it is allowed to read.
    219    if (aAvailableLength > mRemainderToRead) {
    220      aAvailableLength = mRemainderToRead;
    221    }
    222 
    223    // Update state.
    224    mData.mIterating.mChunk = aChunk;
    225    mData.mIterating.mData = aData;
    226    mData.mIterating.mOffset = aOffset;
    227    mData.mIterating.mAvailableLength = aAvailableLength;
    228 
    229    // Update metrics.
    230    mChunkCount++;
    231    mByteCount += aAvailableLength;
    232 
    233    // Attempt to advance by the requested number of bytes.
    234    return AdvanceFromLocalBuffer(aRequestedBytes);
    235  }
    236 
    237  State SetWaiting(bool aHasConsumer) {
    238    MOZ_ASSERT(mState != COMPLETE);
    239    // Without a consumer, we won't know when to wake up precisely. Caller
    240    // convention should mean that we don't try to advance unless we have
    241    // written new data, but that doesn't mean we got enough.
    242    MOZ_ASSERT(mState != WAITING || !aHasConsumer,
    243               "Did we get a spurious wakeup somehow?");
    244    return mState = WAITING;
    245  }
    246 
    247  State SetComplete(nsresult aStatus) {
    248    mData.mAtEnd.mStatus = aStatus;
    249    return mState = COMPLETE;
    250  }
    251 
    252  RefPtr<SourceBuffer> mOwner;
    253 
    254  State mState;
    255 
    256  /**
    257   * This union contains our iteration state if we're still iterating (for
    258   * states START, READY, and WAITING) and the status the SourceBuffer was
    259   * completed with if we're in state COMPLETE.
    260   */
    261  union {
    262    struct {
    263      uint32_t mChunk;    // Index of the chunk in SourceBuffer.
    264      const char* mData;  // Pointer to the start of the chunk.
    265      size_t mOffset;     // Current read position of the iterator relative to
    266                          // mData.
    267      size_t mAvailableLength;  // How many bytes remain unread in the chunk,
    268                                // relative to mOffset.
    269      size_t
    270          mNextReadLength;  // How many bytes the last iterator advance
    271                            // requested to be read, so that we know much
    272                            // to increase mOffset and reduce mAvailableLength
    273                            // by when the next advance is requested.
    274    } mIterating;  // Cached info of the chunk currently iterating over.
    275    struct {
    276      nsresult mStatus;  // Status code indicating if we read all the data.
    277    } mAtEnd;            // State info after iterator is complete.
    278  } mData;
    279 
    280  uint32_t mChunkCount;  // Count of chunks observed, including current chunk.
    281  size_t mByteCount;     // Count of readable bytes observed, including unread
    282                         // bytes from the current chunk.
    283  size_t mRemainderToRead;  // Count of bytes left to read if there is a maximum
    284                            // imposed by the caller. SIZE_MAX if unlimited.
    285 };
    286 
    287 /**
    288 * SourceBuffer is a parallel data structure used for storing image source
    289 * (compressed) data.
    290 *
    291 * SourceBuffer is a single producer, multiple consumer data structure. The
    292 * single producer calls Append() to append data to the buffer. In parallel,
    293 * multiple consumers can call Iterator(), which returns a SourceBufferIterator
    294 * that they can use to iterate through the buffer. The SourceBufferIterator
    295 * returns a series of pointers which remain stable for lifetime of the
    296 * SourceBuffer, and the data they point to is immutable, ensuring that the
    297 * producer never interferes with the consumers.
    298 *
    299 * In order to avoid blocking, SourceBuffer works with SourceBufferIterator to
    300 * keep a list of consumers which are waiting for new data, and to resume them
    301 * when the producer appends more. All consumers must implement the IResumable
    302 * interface to make this possible.
    303 */
    304 class SourceBuffer final {
    305 public:
    306  MOZ_DECLARE_REFCOUNTED_TYPENAME(image::SourceBuffer)
    307  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(image::SourceBuffer)
    308 
    309  SourceBuffer();
    310 
    311  //////////////////////////////////////////////////////////////////////////////
    312  // Producer methods.
    313  //////////////////////////////////////////////////////////////////////////////
    314 
    315  /**
    316   * If the producer knows how long the source data will be, it should call
    317   * ExpectLength, which enables SourceBuffer to preallocate its buffer.
    318   */
    319  nsresult ExpectLength(size_t aExpectedLength);
    320 
    321  /// Append the provided data to the buffer.
    322  nsresult Append(const char* aData, size_t aLength);
    323 
    324  /// Append the data available on the provided nsIInputStream to the buffer.
    325  nsresult AppendFromInputStream(nsIInputStream* aInputStream, uint32_t aCount);
    326 
    327  /// Take ownership of provided data and append it without copying.
    328  nsresult AdoptData(char* aData, size_t aLength,
    329                     void* (*aRealloc)(void*, size_t), void (*aFree)(void*));
    330 
    331  /**
    332   * Mark the buffer complete, with a status that will be available to
    333   * consumers. Further calls to Append() are forbidden after Complete().
    334   */
    335  void Complete(nsresult aStatus);
    336 
    337  /// Returns true if the buffer is complete.
    338  bool IsComplete();
    339 
    340  /// Memory reporting.
    341  size_t SizeOfIncludingThisWithComputedFallback(MallocSizeOf) const;
    342 
    343  //////////////////////////////////////////////////////////////////////////////
    344  // Consumer methods.
    345  //////////////////////////////////////////////////////////////////////////////
    346 
    347  /**
    348   * Returns an iterator to this SourceBuffer, which cannot read more than the
    349   * given length.
    350   */
    351  SourceBufferIterator Iterator(size_t aReadLength = SIZE_MAX);
    352 
    353  //////////////////////////////////////////////////////////////////////////////
    354  // Consumer methods.
    355  //////////////////////////////////////////////////////////////////////////////
    356 
    357  /**
    358   * The minimum chunk capacity we'll allocate, if we don't know the correct
    359   * capacity (which would happen because ExpectLength() wasn't called or gave
    360   * us the wrong value). This is only exposed for use by tests; if normal code
    361   * is using this, it's doing something wrong.
    362   */
    363  static const size_t MIN_CHUNK_CAPACITY = 4096;
    364 
    365  /**
    366   * The maximum chunk capacity we'll allocate. This was historically the
    367   * maximum we would preallocate based on the network size. We may adjust it
    368   * in the future based on the IMAGE_DECODE_CHUNKS telemetry to ensure most
    369   * images remain in a single chunk.
    370   */
    371  static const size_t MAX_CHUNK_CAPACITY = 20 * 1024 * 1024;
    372 
    373 private:
    374  friend class SourceBufferIterator;
    375 
    376  ~SourceBuffer();
    377 
    378  //////////////////////////////////////////////////////////////////////////////
    379  // Chunk type and chunk-related methods.
    380  //////////////////////////////////////////////////////////////////////////////
    381 
    382  class Chunk final {
    383   public:
    384    explicit Chunk(size_t aCapacity) : mCapacity(aCapacity), mLength(0) {
    385      MOZ_ASSERT(aCapacity > 0, "Creating zero-capacity chunk");
    386      mData = static_cast<char*>(malloc(mCapacity));
    387    }
    388 
    389    /// Create an "adopted" chunk taking ownership of the provided data.
    390    Chunk(char* aData, size_t aLength, void* (*aRealloc)(void*, size_t),
    391          void (*aFree)(void*))
    392        : mCapacity(aLength),
    393          mLength(aLength),
    394          mData(aData),
    395          mRealloc(aRealloc),
    396          mFree(aFree) {}
    397 
    398    ~Chunk() { mFree(mData); }
    399 
    400    Chunk(Chunk&& aOther)
    401        : mCapacity(aOther.mCapacity),
    402          mLength(aOther.mLength),
    403          mData(aOther.mData) {
    404      aOther.mCapacity = aOther.mLength = 0;
    405      aOther.mData = nullptr;
    406    }
    407 
    408    Chunk& operator=(Chunk&& aOther) {
    409      free(mData);
    410      mCapacity = aOther.mCapacity;
    411      mLength = aOther.mLength;
    412      mData = aOther.mData;
    413      aOther.mCapacity = aOther.mLength = 0;
    414      aOther.mData = nullptr;
    415      return *this;
    416    }
    417 
    418    bool AllocationFailed() const { return !mData; }
    419    size_t Capacity() const { return mCapacity; }
    420    size_t Length() const { return mLength; }
    421 
    422    char* Data() const {
    423      MOZ_ASSERT(mData, "Allocation failed but nobody checked for it");
    424      return mData;
    425    }
    426 
    427    void AddLength(size_t aAdditionalLength) {
    428      MOZ_ASSERT(mLength + aAdditionalLength <= mCapacity);
    429      mLength += aAdditionalLength;
    430    }
    431 
    432    bool SetCapacity(size_t aCapacity) {
    433      MOZ_ASSERT(mData, "Allocation failed but nobody checked for it");
    434      char* data = static_cast<char*>(mRealloc(mData, aCapacity));
    435      if (!data) {
    436        return false;
    437      }
    438 
    439      mData = data;
    440      mCapacity = aCapacity;
    441      return true;
    442    }
    443 
    444   private:
    445    Chunk(const Chunk&) = delete;
    446    Chunk& operator=(const Chunk&) = delete;
    447 
    448    size_t mCapacity;
    449    size_t mLength;
    450    char* mData;
    451    void* (*mRealloc)(void*, size_t) = realloc;
    452    void (*mFree)(void*) = free;
    453  };
    454 
    455  nsresult AppendChunk(Maybe<Chunk>&& aChunk) MOZ_REQUIRES(mMutex);
    456  Maybe<Chunk> CreateChunk(size_t aCapacity, size_t aExistingCapacity = 0,
    457                           bool aRoundUp = true);
    458  nsresult Compact() MOZ_REQUIRES(mMutex);
    459  static size_t RoundedUpCapacity(size_t aCapacity);
    460  size_t FibonacciCapacityWithMinimum(size_t aMinCapacity) MOZ_REQUIRES(mMutex);
    461 
    462  //////////////////////////////////////////////////////////////////////////////
    463  // Iterator / consumer methods.
    464  //////////////////////////////////////////////////////////////////////////////
    465 
    466  void AddWaitingConsumer(IResumable* aConsumer) MOZ_REQUIRES(mMutex);
    467  void ResumeWaitingConsumers() MOZ_REQUIRES(mMutex);
    468 
    469  typedef SourceBufferIterator::State State;
    470 
    471  State AdvanceIteratorOrScheduleResume(SourceBufferIterator& aIterator,
    472                                        size_t aRequestedBytes,
    473                                        IResumable* aConsumer);
    474  bool RemainingBytesIsNoMoreThan(const SourceBufferIterator& aIterator,
    475                                  size_t aBytes) const;
    476 
    477  void OnIteratorRelease();
    478 
    479  //////////////////////////////////////////////////////////////////////////////
    480  // Helper methods.
    481  //////////////////////////////////////////////////////////////////////////////
    482 
    483  nsresult HandleError(nsresult aError) MOZ_REQUIRES(mMutex);
    484  bool IsEmpty() MOZ_REQUIRES(mMutex);
    485  bool IsLastChunk(uint32_t aChunk) MOZ_REQUIRES(mMutex);
    486 
    487  //////////////////////////////////////////////////////////////////////////////
    488  // Member variables.
    489  //////////////////////////////////////////////////////////////////////////////
    490 
    491  /// All private members are protected by mMutex.
    492  mutable Mutex mMutex;
    493 
    494  /// The data in this SourceBuffer, stored as a series of Chunks.
    495  AutoTArray<Chunk, 1> mChunks MOZ_GUARDED_BY(mMutex);
    496 
    497  /// Consumers which are waiting to be notified when new data is available.
    498  nsTArray<RefPtr<IResumable>> mWaitingConsumers MOZ_GUARDED_BY(mMutex);
    499 
    500  /// If present, marks this SourceBuffer complete with the given final status.
    501  Maybe<nsresult> mStatus MOZ_GUARDED_BY(mMutex);
    502 
    503  /// Count of active consumers.
    504  uint32_t mConsumerCount MOZ_GUARDED_BY(mMutex);
    505 
    506  /// True if compacting has been performed.
    507  bool mCompacted MOZ_GUARDED_BY(mMutex);
    508 };
    509 
    510 }  // namespace image
    511 }  // namespace mozilla
    512 
    513 #endif  // mozilla_image_sourcebuffer_h