/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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/. */ #if !defined(StateWatching_h_) # define StateWatching_h_ # include # include # include # include "mozilla/AbstractThread.h" # include "mozilla/Assertions.h" # include "mozilla/Logging.h" # include "mozilla/RefPtr.h" # include "nsISupports.h" # include "nsTArray.h" # include "nsThreadUtils.h" /* * The state-watching machinery automates the process of responding to changes * in various pieces of state. * * A standard programming pattern is as follows: * * mFoo = ...; * NotifyStuffChanged(); * ... * mBar = ...; * NotifyStuffChanged(); * * This pattern is error-prone and difficult to audit because it requires the * programmer to manually trigger the update routine. This can be especially * problematic when the update routine depends on numerous pieces of state, and * when that state is modified across a variety of helper methods. In these * cases the responsibility for invoking the routine is often unclear, causing * developers to scatter calls to it like pixie dust. This can result in * duplicate invocations (which is wasteful) and missing invocations in corner- * cases (which is a source of bugs). * * This file provides a set of primitives that automatically handle updates and * allow the programmers to explicitly construct a graph of state dependencies. * When used correctly, it eliminates the guess-work and wasted cycles described * above. * * There are two basic pieces: * (1) Objects that can be watched for updates. These inherit WatchTarget. * (2) Objects that receive objects and trigger processing. These inherit * AbstractWatcher. In the current machinery, these exist only internally * within the WatchManager, though that could change. * * Note that none of this machinery is thread-safe - it must all happen on the * same owning thread. To solve multi-threaded use-cases, use state mirroring * and watch the mirrored value. * * Given that semantics may change and comments tend to go out of date, we * deliberately don't provide usage examples here. Grep around to find them. */ namespace mozilla { extern LazyLogModule gStateWatchingLog; # define WATCH_LOG(x, ...) \ MOZ_LOG(gStateWatchingLog, LogLevel::Debug, (x, ##__VA_ARGS__)) /* * AbstractWatcher is a superclass from which all watchers must inherit. */ class AbstractWatcher { public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractWatcher) AbstractWatcher() : mDestroyed(false) {} bool IsDestroyed() { return mDestroyed; } virtual void Notify() = 0; protected: virtual ~AbstractWatcher() { MOZ_ASSERT(mDestroyed); } bool mDestroyed; }; /* * WatchTarget is a superclass from which all watchable things must inherit. * Unlike AbstractWatcher, it is a fully-implemented Mix-in, and the subclass * needs only to invoke NotifyWatchers when something changes. * * The functionality that this class provides is not threadsafe, and should only * be used on the thread that owns that WatchTarget. */ class WatchTarget { public: explicit WatchTarget(const char* aName) : mName(aName) {} void AddWatcher(AbstractWatcher* aWatcher) { MOZ_ASSERT(!mWatchers.Contains(aWatcher)); mWatchers.AppendElement(aWatcher); } void RemoveWatcher(AbstractWatcher* aWatcher) { MOZ_ASSERT(mWatchers.Contains(aWatcher)); mWatchers.RemoveElement(aWatcher); } protected: void NotifyWatchers() { WATCH_LOG("%s[%p] notifying watchers\n", mName, this); PruneWatchers(); for (size_t i = 0; i < mWatchers.Length(); ++i) { mWatchers[i]->Notify(); } } private: // We don't have Watchers explicitly unregister themselves when they die, // because then they'd need back-references to all the WatchTargets they're // subscribed to, and WatchTargets aren't reference-counted. So instead we // just prune dead ones at appropriate times, which works just fine. void PruneWatchers() { mWatchers.RemoveElementsBy( [](const auto& watcher) { return watcher->IsDestroyed(); }); } nsTArray> mWatchers; protected: const char* mName; }; /* * Watchable is a wrapper class that turns any primitive into a WatchTarget. */ template class Watchable : public WatchTarget { public: Watchable(const T& aInitialValue, const char* aName) : WatchTarget(aName), mValue(aInitialValue) {} const T& Ref() const { return mValue; } operator const T&() const { return Ref(); } Watchable& operator=(const T& aNewValue) { if (aNewValue != mValue) { mValue = aNewValue; NotifyWatchers(); } return *this; } private: Watchable(const Watchable& aOther) = delete; Watchable& operator=(const Watchable& aOther) = delete; T mValue; }; // Manager class for state-watching. Declare one of these in any class for which // you want to invoke method callbacks. // // Internally, WatchManager maintains one AbstractWatcher per callback method. // Consumers invoke Watch/Unwatch on a particular (WatchTarget, Callback) tuple. // This causes an AbstractWatcher for |Callback| to be instantiated if it // doesn't already exist, and registers it with |WatchTarget|. // // Using Direct Tasks on the TailDispatcher, WatchManager ensures that we fire // watch callbacks no more than once per task, once all other operations for // that task have been completed. // // WatchManager is intended to be declared as a member of |OwnerType| // objects. Given that, it and its owned objects can't hold permanent strong // refs to the owner, since that would keep the owner alive indefinitely. // Instead, it _only_ holds strong refs while waiting for Direct Tasks to fire. // This ensures that everything is kept alive just long enough. template class WatchManager { public: typedef void (OwnerType::*CallbackMethod)(); explicit WatchManager(OwnerType* aOwner, AbstractThread* aOwnerThread) : mOwner(aOwner), mOwnerThread(aOwnerThread) {} ~WatchManager() { if (!IsShutdown()) { Shutdown(); } } bool IsShutdown() const { return !mOwner; } // Shutdown needs to happen on mOwnerThread. If the WatchManager will be // destroyed on a different thread, Shutdown() must be called manually. void Shutdown() { MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); for (auto& watcher : mWatchers) { watcher->Destroy(); } mWatchers.Clear(); mOwner = nullptr; } void Watch(WatchTarget& aTarget, CallbackMethod aMethod) { MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); aTarget.AddWatcher(&EnsureWatcher(aMethod)); } void Unwatch(WatchTarget& aTarget, CallbackMethod aMethod) { MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); PerCallbackWatcher* watcher = GetWatcher(aMethod); MOZ_ASSERT(watcher); aTarget.RemoveWatcher(watcher); } void ManualNotify(CallbackMethod aMethod) { MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); PerCallbackWatcher* watcher = GetWatcher(aMethod); MOZ_ASSERT(watcher); watcher->Notify(); } private: class PerCallbackWatcher : public AbstractWatcher { public: PerCallbackWatcher(OwnerType* aOwner, AbstractThread* aOwnerThread, CallbackMethod aMethod) : mOwner(aOwner), mOwnerThread(aOwnerThread), mCallbackMethod(aMethod) {} void Destroy() { MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); mDestroyed = true; mOwner = nullptr; } void Notify() override { MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); MOZ_DIAGNOSTIC_ASSERT(mOwner, "mOwner is only null after destruction, " "at which point we shouldn't be notified"); if (mNotificationPending) { // We've already got a notification job in the pipe. return; } mNotificationPending = true; // Queue up our notification jobs to run in a stable state. AbstractThread::DispatchDirectTask( NS_NewRunnableFunction("WatchManager::PerCallbackWatcher::Notify", [self = RefPtr(this), owner = RefPtr(mOwner)]() { if (!self->mDestroyed) { ((*owner).*(self->mCallbackMethod))(); } self->mNotificationPending = false; })); } bool CallbackMethodIs(CallbackMethod aMethod) const { return mCallbackMethod == aMethod; } private: ~PerCallbackWatcher() = default; OwnerType* mOwner; // Never null. bool mNotificationPending = false; RefPtr mOwnerThread; CallbackMethod mCallbackMethod; }; PerCallbackWatcher* GetWatcher(CallbackMethod aMethod) { MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); for (auto& watcher : mWatchers) { if (watcher->CallbackMethodIs(aMethod)) { return watcher; } } return nullptr; } PerCallbackWatcher& EnsureWatcher(CallbackMethod aMethod) { MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); PerCallbackWatcher* watcher = GetWatcher(aMethod); if (watcher) { return *watcher; } watcher = mWatchers .AppendElement(MakeAndAddRef( mOwner, mOwnerThread, aMethod)) ->get(); return *watcher; } nsTArray> mWatchers; OwnerType* mOwner; RefPtr mOwnerThread; }; # undef WATCH_LOG } // namespace mozilla #endif