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 #ifndef XPCOM_THREADS_STATEWATCHING_H_
8 #define XPCOM_THREADS_STATEWATCHING_H_
13 #include "mozilla/AbstractThread.h"
14 #include "mozilla/Assertions.h"
15 #include "mozilla/Logging.h"
16 #include "mozilla/RefPtr.h"
17 #include "nsCycleCollectionNoteChild.h"
18 #include "nsISupports.h"
20 #include "nsThreadUtils.h"
23 * The state-watching machinery automates the process of responding to changes
24 * in various pieces of state.
26 * A standard programming pattern is as follows:
29 * NotifyStuffChanged();
32 * NotifyStuffChanged();
34 * This pattern is error-prone and difficult to audit because it requires the
35 * programmer to manually trigger the update routine. This can be especially
36 * problematic when the update routine depends on numerous pieces of state, and
37 * when that state is modified across a variety of helper methods. In these
38 * cases the responsibility for invoking the routine is often unclear, causing
39 * developers to scatter calls to it like pixie dust. This can result in
40 * duplicate invocations (which is wasteful) and missing invocations in corner-
41 * cases (which is a source of bugs).
43 * This file provides a set of primitives that automatically handle updates and
44 * allow the programmers to explicitly construct a graph of state dependencies.
45 * When used correctly, it eliminates the guess-work and wasted cycles described
48 * There are two basic pieces:
49 * (1) Objects that can be watched for updates. These inherit WatchTarget.
50 * (2) Objects that receive objects and trigger processing. These inherit
51 * AbstractWatcher. In the current machinery, these exist only internally
52 * within the WatchManager, though that could change.
54 * Note that none of this machinery is thread-safe - it must all happen on the
55 * same owning thread. To solve multi-threaded use-cases, use state mirroring
56 * and watch the mirrored value.
58 * Given that semantics may change and comments tend to go out of date, we
59 * deliberately don't provide usage examples here. Grep around to find them.
64 extern LazyLogModule gStateWatchingLog
;
66 #define WATCH_LOG(x, ...) \
67 MOZ_LOG(gStateWatchingLog, LogLevel::Debug, (x, ##__VA_ARGS__))
70 * AbstractWatcher is a superclass from which all watchers must inherit.
72 class AbstractWatcher
{
74 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractWatcher
)
75 AbstractWatcher() : mDestroyed(false) {}
76 bool IsDestroyed() { return mDestroyed
; }
77 virtual void Notify() = 0;
80 virtual ~AbstractWatcher() { MOZ_ASSERT(mDestroyed
); }
85 * WatchTarget is a superclass from which all watchable things must inherit.
86 * Unlike AbstractWatcher, it is a fully-implemented Mix-in, and the subclass
87 * needs only to invoke NotifyWatchers when something changes.
89 * The functionality that this class provides is not threadsafe, and should only
90 * be used on the thread that owns that WatchTarget.
94 explicit WatchTarget(const char* aName
) : mName(aName
) {}
96 void AddWatcher(AbstractWatcher
* aWatcher
) {
97 MOZ_ASSERT(!mWatchers
.Contains(aWatcher
));
98 mWatchers
.AppendElement(aWatcher
);
101 void RemoveWatcher(AbstractWatcher
* aWatcher
) {
102 MOZ_ASSERT(mWatchers
.Contains(aWatcher
));
103 mWatchers
.RemoveElement(aWatcher
);
107 void NotifyWatchers() {
108 WATCH_LOG("%s[%p] notifying watchers\n", mName
, this);
110 for (size_t i
= 0; i
< mWatchers
.Length(); ++i
) {
111 mWatchers
[i
]->Notify();
116 // We don't have Watchers explicitly unregister themselves when they die,
117 // because then they'd need back-references to all the WatchTargets they're
118 // subscribed to, and WatchTargets aren't reference-counted. So instead we
119 // just prune dead ones at appropriate times, which works just fine.
120 void PruneWatchers() {
121 mWatchers
.RemoveElementsBy(
122 [](const auto& watcher
) { return watcher
->IsDestroyed(); });
125 nsTArray
<RefPtr
<AbstractWatcher
>> mWatchers
;
132 * Watchable is a wrapper class that turns any primitive into a WatchTarget.
134 template <typename T
>
135 class Watchable
: public WatchTarget
{
137 Watchable(const T
& aInitialValue
, const char* aName
)
138 : WatchTarget(aName
), mValue(aInitialValue
) {}
140 const T
& Ref() const { return mValue
; }
141 operator const T
&() const { return Ref(); }
142 template <typename U
>
143 Watchable
& operator=(U
&& aNewValue
) {
144 if (aNewValue
!= mValue
) {
145 mValue
= std::forward
<U
>(aNewValue
);
153 Watchable(const Watchable
& aOther
) = delete;
154 Watchable
& operator=(const Watchable
& aOther
) = delete;
159 template <typename T
>
160 inline void ImplCycleCollectionUnlink(Watchable
<T
>& aField
) {
161 ImplCycleCollectionUnlink
<T
>(aField
);
164 template <typename T
>
165 inline void ImplCycleCollectionTraverse(
166 nsCycleCollectionTraversalCallback
& aCallback
, const Watchable
<T
>& aField
,
167 const char* aName
, uint32_t aFlags
= 0) {
168 ImplCycleCollectionTraverse(aCallback
, aField
.Ref(), aName
, aFlags
);
171 // Manager class for state-watching. Declare one of these in any class for which
172 // you want to invoke method callbacks.
174 // Internally, WatchManager maintains one AbstractWatcher per callback method.
175 // Consumers invoke Watch/Unwatch on a particular (WatchTarget, Callback) tuple.
176 // This causes an AbstractWatcher for |Callback| to be instantiated if it
177 // doesn't already exist, and registers it with |WatchTarget|.
179 // Using Direct Tasks on the TailDispatcher, WatchManager ensures that we fire
180 // watch callbacks no more than once per task, once all other operations for
181 // that task have been completed.
183 // WatchManager<OwnerType> is intended to be declared as a member of |OwnerType|
184 // objects. Given that, it and its owned objects can't hold permanent strong
185 // refs to the owner, since that would keep the owner alive indefinitely.
186 // Instead, it _only_ holds strong refs while waiting for Direct Tasks to fire.
187 // This ensures that everything is kept alive just long enough.
188 template <typename OwnerType
>
191 typedef void (OwnerType::*CallbackMethod
)();
192 explicit WatchManager(OwnerType
* aOwner
, AbstractThread
* aOwnerThread
)
193 : mOwner(aOwner
), mOwnerThread(aOwnerThread
) {}
201 bool IsShutdown() const { return !mOwner
; }
203 // Shutdown needs to happen on mOwnerThread. If the WatchManager will be
204 // destroyed on a different thread, Shutdown() must be called manually.
206 MOZ_ASSERT(mOwnerThread
->IsCurrentThreadIn());
207 for (auto& watcher
: mWatchers
) {
214 void Watch(WatchTarget
& aTarget
, CallbackMethod aMethod
) {
215 MOZ_ASSERT(mOwnerThread
->IsCurrentThreadIn());
216 aTarget
.AddWatcher(&EnsureWatcher(aMethod
));
219 void Unwatch(WatchTarget
& aTarget
, CallbackMethod aMethod
) {
220 MOZ_ASSERT(mOwnerThread
->IsCurrentThreadIn());
221 PerCallbackWatcher
* watcher
= GetWatcher(aMethod
);
223 aTarget
.RemoveWatcher(watcher
);
226 void ManualNotify(CallbackMethod aMethod
) {
227 MOZ_ASSERT(mOwnerThread
->IsCurrentThreadIn());
228 PerCallbackWatcher
* watcher
= GetWatcher(aMethod
);
234 class PerCallbackWatcher
: public AbstractWatcher
{
236 PerCallbackWatcher(OwnerType
* aOwner
, AbstractThread
* aOwnerThread
,
237 CallbackMethod aMethod
)
239 mOwnerThread(aOwnerThread
),
240 mCallbackMethod(aMethod
) {}
243 MOZ_ASSERT(mOwnerThread
->IsCurrentThreadIn());
248 void Notify() override
{
249 MOZ_ASSERT(mOwnerThread
->IsCurrentThreadIn());
250 MOZ_DIAGNOSTIC_ASSERT(mOwner
,
251 "mOwner is only null after destruction, "
252 "at which point we shouldn't be notified");
253 if (mNotificationPending
) {
254 // We've already got a notification job in the pipe.
257 mNotificationPending
= true;
259 // Queue up our notification jobs to run in a stable state.
260 AbstractThread::DispatchDirectTask(
261 NS_NewRunnableFunction("WatchManager::PerCallbackWatcher::Notify",
262 [self
= RefPtr
<PerCallbackWatcher
>(this),
263 owner
= RefPtr
<OwnerType
>(mOwner
)]() {
264 if (!self
->mDestroyed
) {
265 ((*owner
).*(self
->mCallbackMethod
))();
267 self
->mNotificationPending
= false;
271 bool CallbackMethodIs(CallbackMethod aMethod
) const {
272 return mCallbackMethod
== aMethod
;
276 ~PerCallbackWatcher() = default;
278 OwnerType
* mOwner
; // Never null.
279 bool mNotificationPending
= false;
280 RefPtr
<AbstractThread
> mOwnerThread
;
281 CallbackMethod mCallbackMethod
;
284 PerCallbackWatcher
* GetWatcher(CallbackMethod aMethod
) {
285 MOZ_ASSERT(mOwnerThread
->IsCurrentThreadIn());
286 for (auto& watcher
: mWatchers
) {
287 if (watcher
->CallbackMethodIs(aMethod
)) {
294 PerCallbackWatcher
& EnsureWatcher(CallbackMethod aMethod
) {
295 MOZ_ASSERT(mOwnerThread
->IsCurrentThreadIn());
296 PerCallbackWatcher
* watcher
= GetWatcher(aMethod
);
301 .AppendElement(MakeAndAddRef
<PerCallbackWatcher
>(
302 mOwner
, mOwnerThread
, aMethod
))
307 nsTArray
<RefPtr
<PerCallbackWatcher
>> mWatchers
;
309 RefPtr
<AbstractThread
> mOwnerThread
;
314 } // namespace mozilla