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