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 #if !defined(StateWatching_h_) 8 # define StateWatching_h_ 9 10 # include <cstddef> 11 # include <new> 12 # include <utility> 13 # include "mozilla/AbstractThread.h" 14 # include "mozilla/Assertions.h" 15 # include "mozilla/Logging.h" 16 # include "mozilla/RefPtr.h" 17 # include "nsISupports.h" 18 # include "nsTArray.h" 19 # include "nsThreadUtils.h" 20 21 /* 22 * The state-watching machinery automates the process of responding to changes 23 * in various pieces of state. 24 * 25 * A standard programming pattern is as follows: 26 * 27 * mFoo = ...; 28 * NotifyStuffChanged(); 29 * ... 30 * mBar = ...; 31 * NotifyStuffChanged(); 32 * 33 * This pattern is error-prone and difficult to audit because it requires the 34 * programmer to manually trigger the update routine. This can be especially 35 * problematic when the update routine depends on numerous pieces of state, and 36 * when that state is modified across a variety of helper methods. In these 37 * cases the responsibility for invoking the routine is often unclear, causing 38 * developers to scatter calls to it like pixie dust. This can result in 39 * duplicate invocations (which is wasteful) and missing invocations in corner- 40 * cases (which is a source of bugs). 41 * 42 * This file provides a set of primitives that automatically handle updates and 43 * allow the programmers to explicitly construct a graph of state dependencies. 44 * When used correctly, it eliminates the guess-work and wasted cycles described 45 * above. 46 * 47 * There are two basic pieces: 48 * (1) Objects that can be watched for updates. These inherit WatchTarget. 49 * (2) Objects that receive objects and trigger processing. These inherit 50 * AbstractWatcher. In the current machinery, these exist only internally 51 * within the WatchManager, though that could change. 52 * 53 * Note that none of this machinery is thread-safe - it must all happen on the 54 * same owning thread. To solve multi-threaded use-cases, use state mirroring 55 * and watch the mirrored value. 56 * 57 * Given that semantics may change and comments tend to go out of date, we 58 * deliberately don't provide usage examples here. Grep around to find them. 59 */ 60 61 namespace mozilla { 62 63 extern LazyLogModule gStateWatchingLog; 64 65 # define WATCH_LOG(x, ...) \ 66 MOZ_LOG(gStateWatchingLog, LogLevel::Debug, (x, ##__VA_ARGS__)) 67 68 /* 69 * AbstractWatcher is a superclass from which all watchers must inherit. 70 */ 71 class AbstractWatcher { 72 public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractWatcher)73 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractWatcher) 74 AbstractWatcher() : mDestroyed(false) {} IsDestroyed()75 bool IsDestroyed() { return mDestroyed; } 76 virtual void Notify() = 0; 77 78 protected: ~AbstractWatcher()79 virtual ~AbstractWatcher() { MOZ_ASSERT(mDestroyed); } 80 bool mDestroyed; 81 }; 82 83 /* 84 * WatchTarget is a superclass from which all watchable things must inherit. 85 * Unlike AbstractWatcher, it is a fully-implemented Mix-in, and the subclass 86 * needs only to invoke NotifyWatchers when something changes. 87 * 88 * The functionality that this class provides is not threadsafe, and should only 89 * be used on the thread that owns that WatchTarget. 90 */ 91 class WatchTarget { 92 public: WatchTarget(const char * aName)93 explicit WatchTarget(const char* aName) : mName(aName) {} 94 AddWatcher(AbstractWatcher * aWatcher)95 void AddWatcher(AbstractWatcher* aWatcher) { 96 MOZ_ASSERT(!mWatchers.Contains(aWatcher)); 97 mWatchers.AppendElement(aWatcher); 98 } 99 RemoveWatcher(AbstractWatcher * aWatcher)100 void RemoveWatcher(AbstractWatcher* aWatcher) { 101 MOZ_ASSERT(mWatchers.Contains(aWatcher)); 102 mWatchers.RemoveElement(aWatcher); 103 } 104 105 protected: NotifyWatchers()106 void NotifyWatchers() { 107 WATCH_LOG("%s[%p] notifying watchers\n", mName, this); 108 PruneWatchers(); 109 for (size_t i = 0; i < mWatchers.Length(); ++i) { 110 mWatchers[i]->Notify(); 111 } 112 } 113 114 private: 115 // We don't have Watchers explicitly unregister themselves when they die, 116 // because then they'd need back-references to all the WatchTargets they're 117 // subscribed to, and WatchTargets aren't reference-counted. So instead we 118 // just prune dead ones at appropriate times, which works just fine. PruneWatchers()119 void PruneWatchers() { 120 mWatchers.RemoveElementsBy( 121 [](const auto& watcher) { return watcher->IsDestroyed(); }); 122 } 123 124 nsTArray<RefPtr<AbstractWatcher>> mWatchers; 125 126 protected: 127 const char* mName; 128 }; 129 130 /* 131 * Watchable is a wrapper class that turns any primitive into a WatchTarget. 132 */ 133 template <typename T> 134 class Watchable : public WatchTarget { 135 public: Watchable(const T & aInitialValue,const char * aName)136 Watchable(const T& aInitialValue, const char* aName) 137 : WatchTarget(aName), mValue(aInitialValue) {} 138 Ref()139 const T& Ref() const { return mValue; } 140 operator const T&() const { return Ref(); } 141 Watchable& operator=(const T& aNewValue) { 142 if (aNewValue != mValue) { 143 mValue = aNewValue; 144 NotifyWatchers(); 145 } 146 147 return *this; 148 } 149 150 private: 151 Watchable(const Watchable& aOther) = delete; 152 Watchable& operator=(const Watchable& aOther) = delete; 153 154 T mValue; 155 }; 156 157 // Manager class for state-watching. Declare one of these in any class for which 158 // you want to invoke method callbacks. 159 // 160 // Internally, WatchManager maintains one AbstractWatcher per callback method. 161 // Consumers invoke Watch/Unwatch on a particular (WatchTarget, Callback) tuple. 162 // This causes an AbstractWatcher for |Callback| to be instantiated if it 163 // doesn't already exist, and registers it with |WatchTarget|. 164 // 165 // Using Direct Tasks on the TailDispatcher, WatchManager ensures that we fire 166 // watch callbacks no more than once per task, once all other operations for 167 // that task have been completed. 168 // 169 // WatchManager<OwnerType> is intended to be declared as a member of |OwnerType| 170 // objects. Given that, it and its owned objects can't hold permanent strong 171 // refs to the owner, since that would keep the owner alive indefinitely. 172 // Instead, it _only_ holds strong refs while waiting for Direct Tasks to fire. 173 // This ensures that everything is kept alive just long enough. 174 template <typename OwnerType> 175 class WatchManager { 176 public: 177 typedef void (OwnerType::*CallbackMethod)(); WatchManager(OwnerType * aOwner,AbstractThread * aOwnerThread)178 explicit WatchManager(OwnerType* aOwner, AbstractThread* aOwnerThread) 179 : mOwner(aOwner), mOwnerThread(aOwnerThread) {} 180 ~WatchManager()181 ~WatchManager() { 182 if (!IsShutdown()) { 183 Shutdown(); 184 } 185 } 186 IsShutdown()187 bool IsShutdown() const { return !mOwner; } 188 189 // Shutdown needs to happen on mOwnerThread. If the WatchManager will be 190 // destroyed on a different thread, Shutdown() must be called manually. Shutdown()191 void Shutdown() { 192 MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); 193 for (auto& watcher : mWatchers) { 194 watcher->Destroy(); 195 } 196 mWatchers.Clear(); 197 mOwner = nullptr; 198 } 199 Watch(WatchTarget & aTarget,CallbackMethod aMethod)200 void Watch(WatchTarget& aTarget, CallbackMethod aMethod) { 201 MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); 202 aTarget.AddWatcher(&EnsureWatcher(aMethod)); 203 } 204 Unwatch(WatchTarget & aTarget,CallbackMethod aMethod)205 void Unwatch(WatchTarget& aTarget, CallbackMethod aMethod) { 206 MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); 207 PerCallbackWatcher* watcher = GetWatcher(aMethod); 208 MOZ_ASSERT(watcher); 209 aTarget.RemoveWatcher(watcher); 210 } 211 ManualNotify(CallbackMethod aMethod)212 void ManualNotify(CallbackMethod aMethod) { 213 MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); 214 PerCallbackWatcher* watcher = GetWatcher(aMethod); 215 MOZ_ASSERT(watcher); 216 watcher->Notify(); 217 } 218 219 private: 220 class PerCallbackWatcher : public AbstractWatcher { 221 public: PerCallbackWatcher(OwnerType * aOwner,AbstractThread * aOwnerThread,CallbackMethod aMethod)222 PerCallbackWatcher(OwnerType* aOwner, AbstractThread* aOwnerThread, 223 CallbackMethod aMethod) 224 : mOwner(aOwner), 225 mOwnerThread(aOwnerThread), 226 mCallbackMethod(aMethod) {} 227 Destroy()228 void Destroy() { 229 MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); 230 mDestroyed = true; 231 mOwner = nullptr; 232 } 233 Notify()234 void Notify() override { 235 MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); 236 MOZ_DIAGNOSTIC_ASSERT(mOwner, 237 "mOwner is only null after destruction, " 238 "at which point we shouldn't be notified"); 239 if (mNotificationPending) { 240 // We've already got a notification job in the pipe. 241 return; 242 } 243 mNotificationPending = true; 244 245 // Queue up our notification jobs to run in a stable state. 246 AbstractThread::DispatchDirectTask( 247 NS_NewRunnableFunction("WatchManager::PerCallbackWatcher::Notify", 248 [self = RefPtr<PerCallbackWatcher>(this), 249 owner = RefPtr<OwnerType>(mOwner)]() { 250 if (!self->mDestroyed) { 251 ((*owner).*(self->mCallbackMethod))(); 252 } 253 self->mNotificationPending = false; 254 })); 255 } 256 CallbackMethodIs(CallbackMethod aMethod)257 bool CallbackMethodIs(CallbackMethod aMethod) const { 258 return mCallbackMethod == aMethod; 259 } 260 261 private: 262 ~PerCallbackWatcher() = default; 263 264 OwnerType* mOwner; // Never null. 265 bool mNotificationPending = false; 266 RefPtr<AbstractThread> mOwnerThread; 267 CallbackMethod mCallbackMethod; 268 }; 269 GetWatcher(CallbackMethod aMethod)270 PerCallbackWatcher* GetWatcher(CallbackMethod aMethod) { 271 MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); 272 for (auto& watcher : mWatchers) { 273 if (watcher->CallbackMethodIs(aMethod)) { 274 return watcher; 275 } 276 } 277 return nullptr; 278 } 279 EnsureWatcher(CallbackMethod aMethod)280 PerCallbackWatcher& EnsureWatcher(CallbackMethod aMethod) { 281 MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); 282 PerCallbackWatcher* watcher = GetWatcher(aMethod); 283 if (watcher) { 284 return *watcher; 285 } 286 watcher = mWatchers 287 .AppendElement(MakeAndAddRef<PerCallbackWatcher>( 288 mOwner, mOwnerThread, aMethod)) 289 ->get(); 290 return *watcher; 291 } 292 293 nsTArray<RefPtr<PerCallbackWatcher>> mWatchers; 294 OwnerType* mOwner; 295 RefPtr<AbstractThread> mOwnerThread; 296 }; 297 298 # undef WATCH_LOG 299 300 } // namespace mozilla 301 302 #endif 303