summaryrefslogtreecommitdiffstats
path: root/xpcom/threads/StateWatching.h
blob: 3da0c63bfeefc039121ebdcc2556f9075e79f132 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
/* -*- 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 <cstddef>
#  include <new>
#  include <utility>
#  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<RefPtr<AbstractWatcher>> mWatchers;

 protected:
  const char* mName;
};

/*
 * Watchable is a wrapper class that turns any primitive into a WatchTarget.
 */
template <typename T>
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<OwnerType> 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 <typename OwnerType>
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<PerCallbackWatcher>(this),
                                  owner = RefPtr<OwnerType>(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<AbstractThread> 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<PerCallbackWatcher>(
                      mOwner, mOwnerThread, aMethod))
                  ->get();
    return *watcher;
  }

  nsTArray<RefPtr<PerCallbackWatcher>> mWatchers;
  OwnerType* mOwner;
  RefPtr<AbstractThread> mOwnerThread;
};

#  undef WATCH_LOG

}  // namespace mozilla

#endif