Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/xpcom/threads/nsProxyRelease.h
Line
Count
Source (jump to first uncovered line)
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
#ifndef nsProxyRelease_h__
8
#define nsProxyRelease_h__
9
10
#include "nsIEventTarget.h"
11
#include "nsIThread.h"
12
#include "nsCOMPtr.h"
13
#include "nsAutoPtr.h"
14
#include "MainThreadUtils.h"
15
#include "nsPrintfCString.h"
16
#include "nsThreadUtils.h"
17
#include "mozilla/Likely.h"
18
#include "mozilla/Move.h"
19
#include "mozilla/SystemGroup.h"
20
#include "mozilla/TypeTraits.h"
21
#include "mozilla/Unused.h"
22
23
#ifdef XPCOM_GLUE_AVOID_NSPR
24
#error NS_ProxyRelease implementation depends on NSPR.
25
#endif
26
27
namespace detail {
28
29
template<typename T>
30
class ProxyReleaseEvent : public mozilla::CancelableRunnable
31
{
32
public:
33
  ProxyReleaseEvent(const char* aName, already_AddRefed<T> aDoomed)
34
0
  : CancelableRunnable(aName), mDoomed(aDoomed.take()) {}
35
36
  NS_IMETHOD Run() override
37
0
  {
38
0
    NS_IF_RELEASE(mDoomed);
39
0
    return NS_OK;
40
0
  }
41
42
  nsresult Cancel() override
43
0
  {
44
0
    return Run();
45
0
  }
46
47
#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
48
  NS_IMETHOD GetName(nsACString& aName) override
49
0
  {
50
0
    if (mName) {
51
0
      aName.Append(nsPrintfCString("ProxyReleaseEvent for %s", mName));
52
0
    } else {
53
0
      aName.AssignLiteral("ProxyReleaseEvent");
54
0
    }
55
0
    return NS_OK;
56
0
  }
57
#endif
58
59
private:
60
  T* MOZ_OWNING_REF mDoomed;
61
};
62
63
template<typename T>
64
void
65
ProxyRelease(const char* aName, nsIEventTarget* aTarget,
66
             already_AddRefed<T> aDoomed, bool aAlwaysProxy)
67
8.65k
{
68
8.65k
  // Auto-managing release of the pointer.
69
8.65k
  RefPtr<T> doomed = aDoomed;
70
8.65k
  nsresult rv;
71
8.65k
72
8.65k
  if (!doomed || !aTarget) {
73
8.65k
    return;
74
8.65k
  }
75
0
76
0
  if (!aAlwaysProxy) {
77
0
    bool onCurrentThread = false;
78
0
    rv = aTarget->IsOnCurrentThread(&onCurrentThread);
79
0
    if (NS_SUCCEEDED(rv) && onCurrentThread) {
80
0
      return;
81
0
    }
82
0
  }
83
0
84
0
  nsCOMPtr<nsIRunnable> ev = new ProxyReleaseEvent<T>(aName, doomed.forget());
85
0
86
0
  rv = aTarget->Dispatch(ev, NS_DISPATCH_NORMAL);
87
0
  if (NS_FAILED(rv)) {
88
0
    NS_WARNING("failed to post proxy release event, leaking!");
89
0
    // It is better to leak the aDoomed object than risk crashing as
90
0
    // a result of deleting it on the wrong thread.
91
0
  }
92
0
}
93
94
template<bool nsISupportsBased>
95
struct ProxyReleaseChooser
96
{
97
  template<typename T>
98
  static void ProxyRelease(const char* aName,
99
                           nsIEventTarget* aTarget,
100
                           already_AddRefed<T> aDoomed,
101
                           bool aAlwaysProxy)
102
  {
103
    ::detail::ProxyRelease(aName, aTarget, std::move(aDoomed), aAlwaysProxy);
104
  }
105
};
106
107
template<>
108
struct ProxyReleaseChooser<true>
109
{
110
  // We need an intermediate step for handling classes with ambiguous
111
  // inheritance to nsISupports.
112
  template<typename T>
113
  static void ProxyRelease(const char* aName,
114
                           nsIEventTarget* aTarget,
115
                           already_AddRefed<T> aDoomed,
116
                           bool aAlwaysProxy)
117
0
  {
118
0
    ProxyReleaseISupports(aName, aTarget, ToSupports(aDoomed.take()), aAlwaysProxy);
119
0
  }
Unexecuted instantiation: void detail::ProxyReleaseChooser<true>::ProxyRelease<mozilla::image::RasterImage>(char const*, nsIEventTarget*, already_AddRefed<mozilla::image::RasterImage>, bool)
Unexecuted instantiation: void detail::ProxyReleaseChooser<true>::ProxyRelease<nsIWorkerDebuggerListener>(char const*, nsIEventTarget*, already_AddRefed<nsIWorkerDebuggerListener>, bool)
120
121
  static void ProxyReleaseISupports(const char* aName,
122
                                    nsIEventTarget* aTarget,
123
                                    nsISupports* aDoomed,
124
                                    bool aAlwaysProxy);
125
};
126
127
} // namespace detail
128
129
/**
130
 * Ensures that the delete of a smart pointer occurs on the target thread.
131
 *
132
 * @param aName
133
 *        the labelling name of the runnable involved in the releasing
134
 * @param aTarget
135
 *        the target thread where the doomed object should be released.
136
 * @param aDoomed
137
 *        the doomed object; the object to be released on the target thread.
138
 * @param aAlwaysProxy
139
 *        normally, if NS_ProxyRelease is called on the target thread, then the
140
 *        doomed object will be released directly. However, if this parameter is
141
 *        true, then an event will always be posted to the target thread for
142
 *        asynchronous release.
143
 */
144
template<class T>
145
inline NS_HIDDEN_(void)
146
NS_ProxyRelease(const char* aName, nsIEventTarget* aTarget,
147
                already_AddRefed<T> aDoomed, bool aAlwaysProxy = false)
148
0
{
149
0
  ::detail::ProxyReleaseChooser<mozilla::IsBaseOf<nsISupports, T>::value>
150
0
    ::ProxyRelease(aName, aTarget, std::move(aDoomed), aAlwaysProxy);
151
0
}
Unexecuted instantiation: void NS_ProxyRelease<mozilla::image::RasterImage>(char const*, nsIEventTarget*, already_AddRefed<mozilla::image::RasterImage>, bool)
Unexecuted instantiation: void NS_ProxyRelease<nsIWorkerDebuggerListener>(char const*, nsIEventTarget*, already_AddRefed<nsIWorkerDebuggerListener>, bool)
152
153
/**
154
 * Ensures that the delete of a smart pointer occurs on the main thread.
155
 *
156
 * @param aName
157
 *        the labelling name of the runnable involved in the releasing
158
 * @param aDoomed
159
 *        the doomed object; the object to be released on the main thread.
160
 * @param aAlwaysProxy
161
 *        normally, if NS_ReleaseOnMainThreadSystemGroup is called on the main
162
 *        thread, then the doomed object will be released directly. However, if
163
 *        this parameter is true, then an event will always be posted to the
164
 *        main thread for asynchronous release.
165
 */
166
template<class T>
167
inline NS_HIDDEN_(void)
168
NS_ReleaseOnMainThreadSystemGroup(const char* aName,
169
                                  already_AddRefed<T> aDoomed,
170
                                  bool aAlwaysProxy = false)
171
0
{
172
0
  // NS_ProxyRelease treats a null event target as "the current thread".  So a
173
0
  // handle on the main thread is only necessary when we're not already on the
174
0
  // main thread or the release must happen asynchronously.
175
0
  nsCOMPtr<nsIEventTarget> systemGroupEventTarget;
176
0
  if (!NS_IsMainThread() || aAlwaysProxy) {
177
0
    systemGroupEventTarget = mozilla::SystemGroup::EventTargetFor(mozilla::TaskCategory::Other);
178
0
179
0
    if (!systemGroupEventTarget) {
180
0
      MOZ_ASSERT_UNREACHABLE("Could not get main thread; leaking an object!");
181
0
      mozilla::Unused << aDoomed.take();
182
0
      return;
183
0
    }
184
0
  }
185
0
186
0
  NS_ProxyRelease(aName, systemGroupEventTarget, std::move(aDoomed),
187
0
                  aAlwaysProxy);
188
0
}
Unexecuted instantiation: void NS_ReleaseOnMainThreadSystemGroup<mozilla::image::RasterImage>(char const*, already_AddRefed<mozilla::image::RasterImage>, bool)
Unexecuted instantiation: void NS_ReleaseOnMainThreadSystemGroup<nsIWorkerDebuggerListener>(char const*, already_AddRefed<nsIWorkerDebuggerListener>, bool)
189
190
template<class T>
191
inline NS_HIDDEN_(void)
192
NS_ReleaseOnMainThreadSystemGroup(already_AddRefed<T> aDoomed,
193
                                  bool aAlwaysProxy = false)
194
0
{
195
0
  NS_ReleaseOnMainThreadSystemGroup("NS_ReleaseOnMainThreadSystemGroup",
196
0
                                    std::move(aDoomed), aAlwaysProxy);
197
0
}
198
199
/**
200
 * Class to safely handle main-thread-only pointers off the main thread.
201
 *
202
 * Classes like XPCWrappedJS are main-thread-only, which means that it is
203
 * forbidden to call methods on instances of these classes off the main thread.
204
 * For various reasons (see bug 771074), this restriction recently began to
205
 * apply to AddRef/Release as well.
206
 *
207
 * This presents a problem for consumers that wish to hold a callback alive
208
 * on non-main-thread code. A common example of this is the proxy callback
209
 * pattern, where non-main-thread code holds a strong-reference to the callback
210
 * object, and dispatches new Runnables (also with a strong reference) to the
211
 * main thread in order to execute the callback. This involves several AddRef
212
 * and Release calls on the other thread, which is (now) verboten.
213
 *
214
 * The basic idea of this class is to introduce a layer of indirection.
215
 * nsMainThreadPtrHolder is a threadsafe reference-counted class that internally
216
 * maintains one strong reference to the main-thread-only object. It must be
217
 * instantiated on the main thread (so that the AddRef of the underlying object
218
 * happens on the main thread), but consumers may subsequently pass references
219
 * to the holder anywhere they please. These references are meant to be opaque
220
 * when accessed off-main-thread (assertions enforce this).
221
 *
222
 * The semantics of RefPtr<nsMainThreadPtrHolder<T> > would be cumbersome, so
223
 * we also introduce nsMainThreadPtrHandle<T>, which is conceptually identical
224
 * to the above (though it includes various convenience methods). The basic
225
 * pattern is as follows.
226
 *
227
 * // On the main thread:
228
 * nsCOMPtr<nsIFooCallback> callback = ...;
229
 * nsMainThreadPtrHandle<nsIFooCallback> callbackHandle =
230
 *   new nsMainThreadPtrHolder<nsIFooCallback>(callback);
231
 * // Pass callbackHandle to structs/classes that might be accessed on other
232
 * // threads.
233
 *
234
 * All structs and classes that might be accessed on other threads should store
235
 * an nsMainThreadPtrHandle<T> rather than an nsCOMPtr<T>.
236
 */
237
template<class T>
238
class nsMainThreadPtrHolder final
239
{
240
public:
241
  // We can only acquire a pointer on the main thread. We to fail fast for
242
  // threading bugs, so by default we assert if our pointer is used or acquired
243
  // off-main-thread. But some consumers need to use the same pointer for
244
  // multiple classes, some of which are main-thread-only and some of which
245
  // aren't. So we allow them to explicitly disable this strict checking.
246
  nsMainThreadPtrHolder(const char* aName, T* aPtr, bool aStrict = true,
247
                        nsIEventTarget* aMainThreadEventTarget = nullptr)
248
    : mRawPtr(nullptr)
249
    , mStrict(aStrict)
250
    , mMainThreadEventTarget(aMainThreadEventTarget)
251
#ifndef RELEASE_OR_BETA
252
    , mName(aName)
253
#endif
254
  {
255
    // We can only AddRef our pointer on the main thread, which means that the
256
    // holder must be constructed on the main thread.
257
    MOZ_ASSERT(!mStrict || NS_IsMainThread());
258
    NS_IF_ADDREF(mRawPtr = aPtr);
259
  }
260
  nsMainThreadPtrHolder(const char* aName, already_AddRefed<T> aPtr,
261
                        bool aStrict = true,
262
                        nsIEventTarget* aMainThreadEventTarget = nullptr)
263
    : mRawPtr(aPtr.take())
264
    , mStrict(aStrict)
265
    , mMainThreadEventTarget(aMainThreadEventTarget)
266
#ifndef RELEASE_OR_BETA
267
    , mName(aName)
268
#endif
269
  {
270
    // Since we don't need to AddRef the pointer, this constructor is safe to
271
    // call on any thread.
272
  }
273
274
private:
275
  // We can be released on any thread.
276
  ~nsMainThreadPtrHolder()
277
  {
278
    if (NS_IsMainThread()) {
279
      NS_IF_RELEASE(mRawPtr);
280
    } else if (mRawPtr) {
281
      if (!mMainThreadEventTarget) {
282
        mMainThreadEventTarget = do_GetMainThread();
283
      }
284
      MOZ_ASSERT(mMainThreadEventTarget);
285
      NS_ProxyRelease(
286
#ifdef RELEASE_OR_BETA
287
        nullptr,
288
#else
289
        mName,
290
#endif
291
        mMainThreadEventTarget, dont_AddRef(mRawPtr));
292
    }
293
  }
294
295
public:
296
  T* get()
297
  {
298
    // Nobody should be touching the raw pointer off-main-thread.
299
    if (mStrict && MOZ_UNLIKELY(!NS_IsMainThread())) {
300
      NS_ERROR("Can't dereference nsMainThreadPtrHolder off main thread");
301
      MOZ_CRASH();
302
    }
303
    return mRawPtr;
304
  }
305
306
  bool operator==(const nsMainThreadPtrHolder<T>& aOther) const
307
  {
308
    return mRawPtr == aOther.mRawPtr;
309
  }
310
  bool operator!() const
311
  {
312
    return !mRawPtr;
313
  }
314
315
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsMainThreadPtrHolder<T>)
316
317
private:
318
  // Our wrapped pointer.
319
  T* mRawPtr;
320
321
  // Whether to strictly enforce thread invariants in this class.
322
  bool mStrict;
323
324
  nsCOMPtr<nsIEventTarget> mMainThreadEventTarget;
325
326
#ifndef RELEASE_OR_BETA
327
  const char* mName = nullptr;
328
#endif
329
330
  // Copy constructor and operator= not implemented. Once constructed, the
331
  // holder is immutable.
332
  T& operator=(nsMainThreadPtrHolder& aOther);
333
  nsMainThreadPtrHolder(const nsMainThreadPtrHolder& aOther);
334
};
335
336
template<class T>
337
class nsMainThreadPtrHandle
338
{
339
  RefPtr<nsMainThreadPtrHolder<T>> mPtr;
340
341
public:
342
  nsMainThreadPtrHandle() : mPtr(nullptr) {}
343
  MOZ_IMPLICIT nsMainThreadPtrHandle(decltype(nullptr)) : mPtr(nullptr) {}
344
  explicit nsMainThreadPtrHandle(nsMainThreadPtrHolder<T>* aHolder)
345
    : mPtr(aHolder)
346
0
  {
347
0
  }
348
  explicit nsMainThreadPtrHandle(
349
      already_AddRefed<nsMainThreadPtrHolder<T>> aHolder)
350
    : mPtr(aHolder)
351
  {
352
  }
353
  nsMainThreadPtrHandle(const nsMainThreadPtrHandle& aOther)
354
    : mPtr(aOther.mPtr)
355
  {
356
  }
357
  nsMainThreadPtrHandle& operator=(const nsMainThreadPtrHandle& aOther)
358
  {
359
    mPtr = aOther.mPtr;
360
    return *this;
361
  }
362
  nsMainThreadPtrHandle& operator=(nsMainThreadPtrHolder<T>* aHolder)
363
  {
364
    mPtr = aHolder;
365
    return *this;
366
  }
367
368
  // These all call through to nsMainThreadPtrHolder, and thus implicitly
369
  // assert that we're on the main thread. Off-main-thread consumers must treat
370
  // these handles as opaque.
371
  T* get() const
372
  {
373
    if (mPtr) {
374
      return mPtr.get()->get();
375
    }
376
    return nullptr;
377
  }
378
379
  operator T*() const { return get(); }
380
  T* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN { return get(); }
381
382
  // These are safe to call on other threads with appropriate external locking.
383
  bool operator==(const nsMainThreadPtrHandle<T>& aOther) const
384
  {
385
    if (!mPtr || !aOther.mPtr) {
386
      return mPtr == aOther.mPtr;
387
    }
388
    return *mPtr == *aOther.mPtr;
389
  }
390
  bool operator!=(const nsMainThreadPtrHandle<T>& aOther) const
391
  {
392
    return !operator==(aOther);
393
  }
394
  bool operator==(decltype(nullptr)) const { return mPtr == nullptr; }
395
  bool operator!=(decltype(nullptr)) const { return mPtr != nullptr; }
396
  bool operator!() const {
397
    return !mPtr || !*mPtr;
398
  }
399
};
400
401
namespace mozilla {
402
403
template<typename T>
404
using PtrHolder = nsMainThreadPtrHolder<T>;
405
406
template<typename T>
407
using PtrHandle = nsMainThreadPtrHandle<T>;
408
409
} // namespace mozilla
410
411
#endif