/* 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 mozilla_EventTargetAndLockCapability_h
#define mozilla_EventTargetAndLockCapability_h

#include "MainThreadUtils.h"
#include "mozilla/EventTargetCapability.h"
#include "mozilla/ThreadSafety.h"

// This header contains helper types for combining a Lock and a thread
// capability allow using both independently, as well as combined together.
//
// This is useful for reflecting the "Single-Writer Mutex" pattern for the clang
// thread-safety analysis, allowing compile-time validation of the correct use
// of the combined capability.
//
// See https://firefox-source-docs.mozilla.org/xpcom/thread-safety.html for
// additional high-level documentation on the thread-safety analysis.

namespace mozilla {

// A thread-safety capability used to combine a Lock with the main thread
// capability to allow using each independently, as well as combined together.
//
// The MainThreadAndLockCapability grants shared (read-only) access if either on
// the main thread, or the inner mutex is held, and only allows exclusive
// (mutable) access if both the lock and the thread capability are held.
//
// This is used to implement the "Single-Writer Mutex" pattern, where a mutex is
// used to guard off-thread access to a value only mutated on a single thread,
// while not requiring the mutex for reads on that thread.
//
// There are no `AutoLock` helper types, instead the lock needs to be acquired
// using the normal locking mechanism (e.g. `MutexAutoLock lock(cap.Lock())`),
// followed by the relevant call to `NoteLockHeld` or `NoteExclusiveAccess`.
template <typename LockT>
class MOZ_CAPABILITY("combo (main thread + mutex)")
    MainThreadAndLockCapability {
 public:
  explicit MainThreadAndLockCapability(const char* aName) : mLock(aName) {}

  // Get access to the internal Lock. This can be used both to have values
  // normally guarded by this lock, as well as to acquire the lock as-needed.
  LockT& Lock() MOZ_RETURN_CAPABILITY(mLock) { return mLock; }

  // Note that we're on the main thread, and thus have shared (read-only) access
  // to values guarded by the MainThreadAndLockCapability for the thread-safety
  // analysis.
  void NoteOnMainThread() const MOZ_REQUIRES(sMainThreadCapability)
      MOZ_ASSERT_SHARED_CAPABILITY(this) {}

  // Note that we're holding the lock, and thus have shared (read-only) access
  // to values guarded by the MainThreadAndLockCapability for the thread-safety
  // analysis.
  void NoteLockHeld() const MOZ_REQUIRES(mLock)
      MOZ_ASSERT_SHARED_CAPABILITY(this) {}

  // Note that we're holding the lock while on the main thread, and thus have
  // exclusive (mutable) access to values guarded by the
  // MainThreadAndLockCapability for the thread-safety analysis.
  void NoteExclusiveAccess() const MOZ_REQUIRES(sMainThreadCapability, mLock)
      MOZ_ASSERT_CAPABILITY(this) {}

  // If you have previously called one of the above `Note` methods in the
  // current function scope, then have acquired `Lock()` and now want to
  // `NoteExclusiveAccess()`, this method can be called to clear the thread
  // safety analysis's understanding that the MainThreadAndLockCapability is
  // currently held.
  void ClearCurrentAccess() const
      MOZ_RELEASE_GENERIC(this) MOZ_NO_THREAD_SAFETY_ANALYSIS {}

 private:
  LockT mLock;
};

// Similar to MainThreadAndLockCapability, this is a thread-safety capability
// used to combine a Lock with an EventTarget capability to allow using each
// independently, as well as combined together.
//
// The EventTargetAndLockCapability grants shared (read-only) access if either
// on the event target, or the inner lock is held, and only allows exclusive
// (mutable) access if both the lock and the event target capability are held.
//
// This is used to implement the "Single-Writer Mutex" pattern, where a mutex is
// used to guard off-thread access to a value only mutated on a single thread,
// while not requiring the mutex for reads on that thread.
//
// There are no `AutoLock` helper types, instead the lock needs to be acquired
// using the normal locking mechanism (e.g. `MutexAutoLock lock(cap.Lock())`),
// followed by the relevant call to `NoteLockHeld` or `NoteExclusiveAccess`.
template <typename TargetT, typename LockT>
class MOZ_CAPABILITY("combo (event target + mutex)")
    EventTargetAndLockCapability {
 public:
  EventTargetAndLockCapability(const char* aName, TargetT* aTarget)
      : mLock(aName), mTarget(aTarget) {}

  // Get access to the internal Lock. This can be used both to have values
  // normally guarded by this lock, as well as to acquire the lock as-needed.
  LockT& Lock() MOZ_RETURN_CAPABILITY(mLock) { return mLock; }

  // Get access to the internal EventTargetCapability. This can be used both to
  // have values normally guarded by this capability, as well as to assert the
  // capability as needed.
  const mozilla::EventTargetCapability<TargetT>& Target() const
      MOZ_RETURN_CAPABILITY(mTarget) {
    return mTarget;
  }

  // Note that we're on the event target, and thus have shared (read-only)
  // access to values guarded by the EventTargetAndLockCapability for the
  // thread-safety analysis.
  void NoteOnTarget() const MOZ_REQUIRES(mTarget)
      MOZ_ASSERT_SHARED_CAPABILITY(this) {}

  // Note that we're holding the lock, and thus have shared (read-only) access
  // to values guarded by the EventTargetAndLockCapability for the thread-safety
  // analysis.
  void NoteLockHeld() const MOZ_REQUIRES(mLock)
      MOZ_ASSERT_SHARED_CAPABILITY(this) {}

  // Note that we're holding the lock while on the event target, and thus have
  // exclusive (mutable) access to values guarded by the
  // EventTargetAndLockCapability for the thread-safety analysis.
  void NoteExclusiveAccess() const MOZ_REQUIRES(mTarget, mLock)
      MOZ_ASSERT_CAPABILITY(this) {}

  // If you have previously called one of the above `Note` methods in the
  // current function scope, then have acquired `Mutex()` and now want to
  // `NoteExclusiveAccess()`, this method can be called to clear the thread
  // safety analysis's understanding that the EventTargetAndLockCapability is
  // currently held.
  void ClearCurrentAccess() const
      MOZ_RELEASE_GENERIC(this) MOZ_NO_THREAD_SAFETY_ANALYSIS {}

 private:
  LockT mLock;
  mozilla::EventTargetCapability<TargetT> mTarget;
};

}  // namespace mozilla

#endif  // mozilla_EventTargetAndLockCapability_h
