tor-browser

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

ExclusiveData.h (11828B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      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
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #ifndef threading_ExclusiveData_h
      8 #define threading_ExclusiveData_h
      9 
     10 #include "mozilla/OperatorNewExtensions.h"
     11 
     12 #include <utility>
     13 
     14 #include "threading/ConditionVariable.h"
     15 #include "threading/Mutex.h"
     16 
     17 namespace js {
     18 
     19 /**
     20 * [SMDOC] ExclusiveData API
     21 *
     22 * A mutual exclusion lock class.
     23 *
     24 * `ExclusiveData` provides an RAII guard to automatically lock and unlock when
     25 * accessing the protected inner value.
     26 *
     27 * Unlike the STL's `std::mutex`, the protected value is internal to this
     28 * class. This is a huge win: one no longer has to rely on documentation to
     29 * explain the relationship between a lock and its protected data, and the type
     30 * system can enforce[0] it.
     31 *
     32 * For example, suppose we have a counter class:
     33 *
     34 *     class Counter
     35 *     {
     36 *         int32_t i;
     37 *
     38 *       public:
     39 *         void inc(int32_t n) { i += n; }
     40 *     };
     41 *
     42 * If we share a counter across threads with `std::mutex`, we rely solely on
     43 * comments to document the relationship between the lock and its data, like
     44 * this:
     45 *
     46 *     class SharedCounter
     47 *     {
     48 *         // Remember to acquire `counter_lock` when accessing `counter`,
     49 *         // pretty please!
     50 *         Counter counter;
     51 *         std::mutex counter_lock;
     52 *
     53 *       public:
     54 *         void inc(size_t n) {
     55 *             // Whoops, forgot to acquire the lock! Off to the races!
     56 *             counter.inc(n);
     57 *         }
     58 *     };
     59 *
     60 * In contrast, `ExclusiveData` wraps the protected value, enabling the type
     61 * system to enforce that we acquire the lock before accessing the value:
     62 *
     63 *     class SharedCounter
     64 *     {
     65 *         ExclusiveData<Counter> counter;
     66 *
     67 *       public:
     68 *         void inc(size_t n) {
     69 *             auto guard = counter.lock();
     70 *             guard->inc(n);
     71 *         }
     72 *     };
     73 *
     74 * The API design is based on Rust's `std::sync::Mutex<T>` type.
     75 *
     76 * [0]: Of course, we don't have a borrow checker in C++, so the type system
     77 *      cannot guarantee that you don't stash references received from
     78 *      `ExclusiveData<T>::Guard` somewhere such that the reference outlives the
     79 *      guard's lifetime and therefore becomes invalid. To help avoid this last
     80 *      foot-gun, prefer using the guard directly! Do not store raw references
     81 *      to the protected value in other structures!
     82 */
     83 template <typename T>
     84 class ExclusiveData {
     85 protected:
     86  mutable Mutex lock_ MOZ_UNANNOTATED;
     87  mutable T value_;
     88 
     89  ExclusiveData(const ExclusiveData&) = delete;
     90  ExclusiveData& operator=(const ExclusiveData&) = delete;
     91 
     92  void acquire() const { lock_.lock(); }
     93  void release() const { lock_.unlock(); }
     94 
     95 public:
     96  /**
     97   * Create a new `ExclusiveData`, with perfect forwarding of the protected
     98   * value.
     99   */
    100  template <typename U>
    101  explicit ExclusiveData(const MutexId& id, U&& u)
    102      : lock_(id), value_(std::forward<U>(u)) {}
    103 
    104  /**
    105   * Create a new `ExclusiveData`, constructing the protected value in place.
    106   */
    107  template <typename... Args>
    108  explicit ExclusiveData(const MutexId& id, Args&&... args)
    109      : lock_(id), value_(std::forward<Args>(args)...) {}
    110 
    111  ExclusiveData& operator=(ExclusiveData&& rhs) {
    112    this->~ExclusiveData();
    113    new (mozilla::KnownNotNull, this) ExclusiveData(std::move(rhs));
    114    return *this;
    115  }
    116 
    117  /**
    118   * An RAII class that provides exclusive access to a `ExclusiveData<T>`'s
    119   * protected inner `T` value.
    120   *
    121   * Note that this is intentionally marked MOZ_STACK_CLASS instead of
    122   * MOZ_RAII_CLASS, as the latter disallows moves and returning by value, but
    123   * Guard utilizes both.
    124   */
    125  class MOZ_STACK_CLASS Guard {
    126   protected:
    127    const ExclusiveData* parent_;
    128    explicit Guard(std::nullptr_t) : parent_(nullptr) {}
    129 
    130   private:
    131    Guard(const Guard&) = delete;
    132    Guard& operator=(const Guard&) = delete;
    133 
    134   public:
    135    explicit Guard(const ExclusiveData& parent) : parent_(&parent) {
    136      parent_->acquire();
    137    }
    138 
    139    Guard(Guard&& rhs) : parent_(rhs.parent_) {
    140      MOZ_ASSERT(&rhs != this, "self-move disallowed!");
    141      rhs.parent_ = nullptr;
    142    }
    143 
    144    Guard& operator=(Guard&& rhs) {
    145      this->~Guard();
    146      new (this) Guard(std::move(rhs));
    147      return *this;
    148    }
    149 
    150    T& get() const {
    151      MOZ_ASSERT(parent_);
    152      return parent_->value_;
    153    }
    154 
    155    operator T&() const { return get(); }
    156    T* operator->() const { return &get(); }
    157 
    158    const ExclusiveData<T>* parent() const {
    159      MOZ_ASSERT(parent_);
    160      return parent_;
    161    }
    162 
    163    ~Guard() {
    164      if (parent_) {
    165        parent_->release();
    166      }
    167    }
    168  };
    169 
    170  /**
    171   * NullableGuard are similar to Guard, except that one the access to the
    172   * ExclusiveData might not always be granted. This is useful when contextual
    173   * information is enough to prevent useless use of Mutex.
    174   *
    175   * The NullableGuard can be manipulated as follows:
    176   *
    177   *     if (NullableGuard guard = data.mightAccess()) {
    178   *         // NullableGuard is acquired.
    179   *         guard->...
    180   *     }
    181   *     // NullableGuard was either not acquired or released.
    182   *
    183   * Where mightAccess returns either a NullableGuard from `noAccess()` or a
    184   * Guard from `lock()`.
    185   */
    186  class MOZ_STACK_CLASS NullableGuard : public Guard {
    187   public:
    188    explicit NullableGuard(std::nullptr_t) : Guard((std::nullptr_t) nullptr) {}
    189    explicit NullableGuard(const ExclusiveData& parent) : Guard(parent) {}
    190    explicit NullableGuard(Guard&& rhs) : Guard(std::move(rhs)) {}
    191 
    192    NullableGuard& operator=(Guard&& rhs) {
    193      this->~NullableGuard();
    194      new (this) NullableGuard(std::move(rhs));
    195      return *this;
    196    }
    197 
    198    /**
    199     * Returns whether this NullableGuard has access to the exclusive data.
    200     */
    201    bool hasAccess() const { return this->parent_; }
    202    explicit operator bool() const { return hasAccess(); }
    203  };
    204 
    205  /**
    206   * Access the protected inner `T` value for exclusive reading and writing.
    207   */
    208  Guard lock() const { return Guard(*this); }
    209 
    210  /**
    211   * Provide a no-access guard, which coerces to false when tested. This value
    212   * can be returned if the guard access is conditioned on external factors.
    213   *
    214   * See NullableGuard.
    215   */
    216  NullableGuard noAccess() const {
    217    return NullableGuard((std::nullptr_t) nullptr);
    218  }
    219 };
    220 
    221 template <class T>
    222 class ExclusiveWaitableData : public ExclusiveData<T> {
    223  using Base = ExclusiveData<T>;
    224 
    225  mutable ConditionVariable condVar_;
    226 
    227 public:
    228  template <typename U>
    229  explicit ExclusiveWaitableData(const MutexId& id, U&& u)
    230      : Base(id, std::forward<U>(u)) {}
    231 
    232  template <typename... Args>
    233  explicit ExclusiveWaitableData(const MutexId& id, Args&&... args)
    234      : Base(id, std::forward<Args>(args)...) {}
    235 
    236  class MOZ_STACK_CLASS Guard : public ExclusiveData<T>::Guard {
    237    using Base = typename ExclusiveData<T>::Guard;
    238 
    239   public:
    240    explicit Guard(const ExclusiveWaitableData& parent) : Base(parent) {}
    241 
    242    Guard(Guard&& guard) : Base(std::move(guard)) {}
    243 
    244    Guard& operator=(Guard&& rhs) { return Base::operator=(std::move(rhs)); }
    245 
    246    void wait() {
    247      auto* parent = static_cast<const ExclusiveWaitableData*>(this->parent());
    248      parent->condVar_.wait(parent->lock_);
    249    }
    250 
    251    void notify_one() {
    252      auto* parent = static_cast<const ExclusiveWaitableData*>(this->parent());
    253      parent->condVar_.notify_one();
    254    }
    255 
    256    void notify_all() {
    257      auto* parent = static_cast<const ExclusiveWaitableData*>(this->parent());
    258      parent->condVar_.notify_all();
    259    }
    260  };
    261 
    262  Guard lock() const { return Guard(*this); }
    263 };
    264 
    265 /**
    266 * Multiple-readers / single-writer variant of ExclusiveData.
    267 *
    268 * Readers call readLock() to obtain a stack-only RAII reader lock, which will
    269 * allow other readers to read concurrently but block writers; the yielded value
    270 * is const.  Writers call writeLock() to obtain a ditto writer lock, which
    271 * yields exclusive access to non-const data.
    272 *
    273 * See ExclusiveData and its implementation for more documentation.
    274 */
    275 template <typename T>
    276 class RWExclusiveData {
    277  mutable Mutex lock_ MOZ_UNANNOTATED;
    278  mutable ConditionVariable cond_;
    279  mutable T value_;
    280  mutable int readers_;
    281 
    282  // We maintain a count of active readers.  Writers may enter the critical
    283  // section only when the reader count is zero, so the reader that decrements
    284  // the count to zero must wake up any waiting writers.
    285  //
    286  // There can be multiple writers waiting, so a writer leaving the critical
    287  // section must also wake up any other waiting writers.
    288 
    289  void acquireReaderLock() const {
    290    lock_.lock();
    291    readers_++;
    292    lock_.unlock();
    293  }
    294 
    295  void releaseReaderLock() const {
    296    lock_.lock();
    297    MOZ_ASSERT(readers_ > 0);
    298    if (--readers_ == 0) {
    299      cond_.notify_all();
    300    }
    301    lock_.unlock();
    302  }
    303 
    304  void acquireWriterLock() const {
    305    lock_.lock();
    306    while (readers_ > 0) {
    307      cond_.wait(lock_);
    308    }
    309  }
    310 
    311  void releaseWriterLock() const {
    312    cond_.notify_all();
    313    lock_.unlock();
    314  }
    315 
    316 public:
    317  RWExclusiveData(const RWExclusiveData&) = delete;
    318  RWExclusiveData& operator=(const RWExclusiveData&) = delete;
    319 
    320  /**
    321   * Create a new `RWExclusiveData`, constructing the protected value in place.
    322   */
    323  template <typename... Args>
    324  explicit RWExclusiveData(const MutexId& id, Args&&... args)
    325      : lock_(id), value_(std::forward<Args>(args)...), readers_(0) {}
    326 
    327  class MOZ_STACK_CLASS ReadGuard {
    328    const RWExclusiveData* parent_;
    329    explicit ReadGuard(std::nullptr_t) : parent_(nullptr) {}
    330 
    331   public:
    332    ReadGuard(const ReadGuard&) = delete;
    333    ReadGuard& operator=(const ReadGuard&) = delete;
    334 
    335    explicit ReadGuard(const RWExclusiveData& parent) : parent_(&parent) {
    336      parent_->acquireReaderLock();
    337    }
    338 
    339    ReadGuard(ReadGuard&& rhs) : parent_(rhs.parent_) {
    340      MOZ_ASSERT(&rhs != this, "self-move disallowed!");
    341      rhs.parent_ = nullptr;
    342    }
    343 
    344    ReadGuard& operator=(ReadGuard&& rhs) {
    345      this->~ReadGuard();
    346      new (this) ReadGuard(std::move(rhs));
    347      return *this;
    348    }
    349 
    350    const T& get() const {
    351      MOZ_ASSERT(parent_);
    352      return parent_->value_;
    353    }
    354 
    355    operator const T&() const { return get(); }
    356    const T* operator->() const { return &get(); }
    357 
    358    const RWExclusiveData<T>* parent() const {
    359      MOZ_ASSERT(parent_);
    360      return parent_;
    361    }
    362 
    363    ~ReadGuard() {
    364      if (parent_) {
    365        parent_->releaseReaderLock();
    366      }
    367    }
    368  };
    369 
    370  class MOZ_STACK_CLASS WriteGuard {
    371    const RWExclusiveData* parent_;
    372    explicit WriteGuard(std::nullptr_t) : parent_(nullptr) {}
    373 
    374   public:
    375    WriteGuard(const WriteGuard&) = delete;
    376    WriteGuard& operator=(const WriteGuard&) = delete;
    377 
    378    explicit WriteGuard(const RWExclusiveData& parent) : parent_(&parent) {
    379      parent_->acquireWriterLock();
    380    }
    381 
    382    WriteGuard(WriteGuard&& rhs) : parent_(rhs.parent_) {
    383      MOZ_ASSERT(&rhs != this, "self-move disallowed!");
    384      rhs.parent_ = nullptr;
    385    }
    386 
    387    WriteGuard& operator=(WriteGuard&& rhs) {
    388      this->~WriteGuard();
    389      new (this) WriteGuard(std::move(rhs));
    390      return *this;
    391    }
    392 
    393    T& get() const {
    394      MOZ_ASSERT(parent_);
    395      return parent_->value_;
    396    }
    397 
    398    operator T&() const { return get(); }
    399    T* operator->() const { return &get(); }
    400 
    401    const RWExclusiveData<T>* parent() const {
    402      MOZ_ASSERT(parent_);
    403      return parent_;
    404    }
    405 
    406    ~WriteGuard() {
    407      if (parent_) {
    408        parent_->releaseWriterLock();
    409      }
    410    }
    411  };
    412 
    413  ReadGuard readLock() const { return ReadGuard(*this); }
    414  WriteGuard writeLock() const { return WriteGuard(*this); }
    415 };
    416 
    417 }  // namespace js
    418 
    419 #endif  // threading_ExclusiveData_h