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/. */
7 #if !defined(StateWatching_h_)
8 # define StateWatching_h_
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"
22 * The state-watching machinery automates the process of responding to changes
23 * in various pieces of state.
25 * A standard programming pattern is as follows:
28 * NotifyStuffChanged();
31 * NotifyStuffChanged();
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).
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
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.
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.
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.
63 extern LazyLogModule gStateWatchingLog
;
65 # define WATCH_LOG(x, ...) \
66 MOZ_LOG(gStateWatchingLog, LogLevel::Debug, (x, ##__VA_ARGS__))
69 * AbstractWatcher is a superclass from which all watchers must inherit.
71 class AbstractWatcher
{
73 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractWatcher
)
74 AbstractWatcher() : mDestroyed(false) {}
75 bool IsDestroyed() { return mDestroyed
; }
76 virtual void Notify() = 0;
79 virtual ~AbstractWatcher() { MOZ_ASSERT(mDestroyed
); }
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.
88 * The functionality that this class provides is not threadsafe, and should only
89 * be used on the thread that owns that WatchTarget.
93 explicit WatchTarget(const char* aName
) : mName(aName
) {}
95 void AddWatcher(AbstractWatcher
* aWatcher
) {
96 MOZ_ASSERT(!mWatchers
.Contains(aWatcher
));
97 mWatchers
.AppendElement(aWatcher
);
100 void RemoveWatcher(AbstractWatcher
* aWatcher
) {
101 MOZ_ASSERT(mWatchers
.Contains(aWatcher
));
102 mWatchers
.RemoveElement(aWatcher
);
106 void NotifyWatchers() {
107 WATCH_LOG("%s[%p] notifying watchers\n", mName
, this);
109 for (size_t i
= 0; i
< mWatchers
.Length(); ++i
) {
110 mWatchers
[i
]->Notify();
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.
119 void PruneWatchers() {
120 mWatchers
.RemoveElementsBy(
121 [](const auto& watcher
) { return watcher
->IsDestroyed(); });
124 nsTArray
<RefPtr
<AbstractWatcher
>> mWatchers
;
131 * Watchable is a wrapper class that turns any primitive into a WatchTarget.
133 template <typename T
>
134 class Watchable
: public WatchTarget
{
136 Watchable(const T
& aInitialValue
, const char* aName
)
137 : WatchTarget(aName
), mValue(aInitialValue
) {}
139 const T
& Ref() const { return mValue
; }
140 operator const T
&() const { return Ref(); }
141 Watchable
& operator=(const T
& aNewValue
) {
142 if (aNewValue
!= mValue
) {
151 Watchable(const Watchable
& aOther
) = delete;
152 Watchable
& operator=(const Watchable
& aOther
) = delete;
157 // Manager class for state-watching. Declare one of these in any class for which
158 // you want to invoke method callbacks.
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|.
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.
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
>
177 typedef void (OwnerType::*CallbackMethod
)();
178 explicit WatchManager(OwnerType
* aOwner
, AbstractThread
* aOwnerThread
)
179 : mOwner(aOwner
), mOwnerThread(aOwnerThread
) {}
187 bool IsShutdown() const { return !mOwner
; }
189 // Shutdown needs to happen on mOwnerThread. If the WatchManager will be
190 // destroyed on a different thread, Shutdown() must be called manually.
192 MOZ_ASSERT(mOwnerThread
->IsCurrentThreadIn());
193 for (auto& watcher
: mWatchers
) {
200 void Watch(WatchTarget
& aTarget
, CallbackMethod aMethod
) {
201 MOZ_ASSERT(mOwnerThread
->IsCurrentThreadIn());
202 aTarget
.AddWatcher(&EnsureWatcher(aMethod
));
205 void Unwatch(WatchTarget
& aTarget
, CallbackMethod aMethod
) {
206 MOZ_ASSERT(mOwnerThread
->IsCurrentThreadIn());
207 PerCallbackWatcher
* watcher
= GetWatcher(aMethod
);
209 aTarget
.RemoveWatcher(watcher
);
212 void ManualNotify(CallbackMethod aMethod
) {
213 MOZ_ASSERT(mOwnerThread
->IsCurrentThreadIn());
214 PerCallbackWatcher
* watcher
= GetWatcher(aMethod
);
220 class PerCallbackWatcher
: public AbstractWatcher
{
222 PerCallbackWatcher(OwnerType
* aOwner
, AbstractThread
* aOwnerThread
,
223 CallbackMethod aMethod
)
225 mOwnerThread(aOwnerThread
),
226 mCallbackMethod(aMethod
) {}
229 MOZ_ASSERT(mOwnerThread
->IsCurrentThreadIn());
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.
243 mNotificationPending
= true;
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
))();
253 self
->mNotificationPending
= false;
257 bool CallbackMethodIs(CallbackMethod aMethod
) const {
258 return mCallbackMethod
== aMethod
;
262 ~PerCallbackWatcher() = default;
264 OwnerType
* mOwner
; // Never null.
265 bool mNotificationPending
= false;
266 RefPtr
<AbstractThread
> mOwnerThread
;
267 CallbackMethod mCallbackMethod
;
270 PerCallbackWatcher
* GetWatcher(CallbackMethod aMethod
) {
271 MOZ_ASSERT(mOwnerThread
->IsCurrentThreadIn());
272 for (auto& watcher
: mWatchers
) {
273 if (watcher
->CallbackMethodIs(aMethod
)) {
280 PerCallbackWatcher
& EnsureWatcher(CallbackMethod aMethod
) {
281 MOZ_ASSERT(mOwnerThread
->IsCurrentThreadIn());
282 PerCallbackWatcher
* watcher
= GetWatcher(aMethod
);
287 .AppendElement(MakeAndAddRef
<PerCallbackWatcher
>(
288 mOwner
, mOwnerThread
, aMethod
))
293 nsTArray
<RefPtr
<PerCallbackWatcher
>> mWatchers
;
295 RefPtr
<AbstractThread
> mOwnerThread
;
300 } // namespace mozilla