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