1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set sw=2 ts=8 et tw=80 :
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #ifndef mozilla_net_ChannelEventQueue_h
9 #define mozilla_net_ChannelEventQueue_h
12 #include "nsIEventTarget.h"
13 #include "nsThreadUtils.h"
14 #include "nsXULAppAPI.h"
15 #include "mozilla/DebugOnly.h"
16 #include "mozilla/Mutex.h"
17 #include "mozilla/RecursiveMutex.h"
18 #include "mozilla/UniquePtr.h"
19 #include "mozilla/Unused.h"
28 MOZ_COUNTED_DEFAULT_CTOR(ChannelEvent
)
29 MOZ_COUNTED_DTOR_VIRTUAL(ChannelEvent
) virtual void Run() = 0;
30 virtual already_AddRefed
<nsIEventTarget
> GetEventTarget() = 0;
33 // Note that MainThreadChannelEvent should not be used in child process since
34 // GetEventTarget() directly returns an unlabeled event target.
35 class MainThreadChannelEvent
: public ChannelEvent
{
37 MOZ_COUNTED_DEFAULT_CTOR(MainThreadChannelEvent
)
38 MOZ_COUNTED_DTOR_OVERRIDE(MainThreadChannelEvent
)
40 already_AddRefed
<nsIEventTarget
> GetEventTarget() override
{
41 MOZ_ASSERT(XRE_IsParentProcess());
43 return do_AddRef(GetMainThreadSerialEventTarget());
47 class ChannelFunctionEvent
: public ChannelEvent
{
50 std::function
<already_AddRefed
<nsIEventTarget
>()>&& aGetEventTarget
,
51 std::function
<void()>&& aCallback
)
52 : mGetEventTarget(std::move(aGetEventTarget
)),
53 mCallback(std::move(aCallback
)) {}
55 void Run() override
{ mCallback(); }
56 already_AddRefed
<nsIEventTarget
> GetEventTarget() override
{
57 return mGetEventTarget();
61 const std::function
<already_AddRefed
<nsIEventTarget
>()> mGetEventTarget
;
62 const std::function
<void()> mCallback
;
65 // UnsafePtr is a work-around our static analyzer that requires all
66 // ref-counted objects to be captured in lambda via a RefPtr
67 // The ChannelEventQueue makes it safe to capture "this" by pointer only.
68 // This is required as work-around to prevent cycles until bug 1596295
73 explicit UnsafePtr(T
* aPtr
) : mPtr(aPtr
) {}
75 T
& operator*() const { return *mPtr
; }
76 T
* operator->() const {
77 MOZ_ASSERT(mPtr
, "dereferencing a null pointer");
80 operator T
*() const& { return mPtr
; }
81 explicit operator bool() const { return mPtr
!= nullptr; }
87 class NeckoTargetChannelFunctionEvent
: public ChannelFunctionEvent
{
90 NeckoTargetChannelFunctionEvent(T
* aChild
, std::function
<void()>&& aCallback
)
91 : ChannelFunctionEvent(
92 [child
= UnsafePtr
<T
>(aChild
)]() {
94 return child
->GetNeckoTarget();
96 std::move(aCallback
)) {}
99 // Workaround for Necko re-entrancy dangers. We buffer IPDL messages in a
100 // queue if still dispatching previous one(s) to listeners/observers.
101 // Otherwise synchronous XMLHttpRequests and/or other code that spins the
102 // event loop (ex: IPDL rpc) could cause listener->OnDataAvailable (for
103 // instance) to be dispatched and called before mListener->OnStartRequest has
105 // The ChannelEventQueue implementation ensures strict ordering of
106 // event execution across target threads.
108 class ChannelEventQueue final
{
109 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ChannelEventQueue
)
112 explicit ChannelEventQueue(nsISupports
* owner
)
117 mHasCheckedForXMLHttpRequest(false),
118 mForXMLHttpRequest(false),
120 mMutex("ChannelEventQueue::mMutex"),
121 mRunningMutex("ChannelEventQueue::mRunningMutex") {}
123 // Puts IPDL-generated channel event into queue, to be run later
124 // automatically when EndForcedQueueing and/or Resume is called.
126 // @param aCallback - the ChannelEvent
127 // @param aAssertionWhenNotQueued - this optional param will be used in an
128 // assertion when the event is executed directly.
129 inline void RunOrEnqueue(ChannelEvent
* aCallback
,
130 bool aAssertionWhenNotQueued
= false);
132 // Append ChannelEvent in front of the event queue.
133 inline void PrependEvent(UniquePtr
<ChannelEvent
>&& aEvent
);
134 inline void PrependEvents(nsTArray
<UniquePtr
<ChannelEvent
>>& aEvents
);
136 // After StartForcedQueueing is called, RunOrEnqueue() will start enqueuing
137 // events that will be run/flushed when EndForcedQueueing is called.
138 // - Note: queueing may still be required after EndForcedQueueing() (if the
139 // queue is suspended, etc): always call RunOrEnqueue() to avoid race
141 inline void StartForcedQueueing();
142 inline void EndForcedQueueing();
144 // Suspend/resume event queue. RunOrEnqueue() will start enqueuing
145 // events and they will be run/flushed when resume is called. These should be
146 // called when the channel owning the event queue is suspended/resumed.
148 // Resume flushes the queue asynchronously, i.e. items in queue will be
149 // dispatched in a new event on the current thread.
152 void NotifyReleasingOwner() {
153 MutexAutoLock
lock(mMutex
);
157 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
159 MutexAutoLock
lock(mMutex
);
160 return mEventQueue
.IsEmpty();
165 // Private destructor, to discourage deletion outside of Release():
166 ~ChannelEventQueue() = default;
168 void SuspendInternal();
169 void ResumeInternal();
171 bool MaybeSuspendIfEventsAreSuppressed() MOZ_REQUIRES(mMutex
);
173 inline void MaybeFlushQueue();
175 inline void CompleteResume();
177 ChannelEvent
* TakeEvent();
179 nsTArray
<UniquePtr
<ChannelEvent
>> mEventQueue
MOZ_GUARDED_BY(mMutex
);
181 uint32_t mSuspendCount
MOZ_GUARDED_BY(mMutex
);
182 bool mSuspended
MOZ_GUARDED_BY(mMutex
);
183 uint32_t mForcedCount
// Support ForcedQueueing on multiple thread.
184 MOZ_GUARDED_BY(mMutex
);
185 bool mFlushing
MOZ_GUARDED_BY(mMutex
);
187 // Whether the queue is associated with an XHR. This is lazily instantiated
188 // the first time it is needed. These are MainThread-only.
189 bool mHasCheckedForXMLHttpRequest
;
190 bool mForXMLHttpRequest
;
192 // Keep ptr to avoid refcount cycle: only grab ref during flushing.
193 nsISupports
* mOwner
MOZ_GUARDED_BY(mMutex
);
195 // For atomic mEventQueue operation and state update
198 // To guarantee event execution order among threads
199 RecursiveMutex mRunningMutex
MOZ_ACQUIRED_BEFORE(mMutex
);
201 friend class AutoEventEnqueuer
;
204 inline void ChannelEventQueue::RunOrEnqueue(ChannelEvent
* aCallback
,
205 bool aAssertionWhenNotQueued
) {
206 MOZ_ASSERT(aCallback
);
207 // Events execution could be a destruction of the channel (and our own
208 // destructor) unless we make sure its refcount doesn't drop to 0 while this
209 // method is running.
210 nsCOMPtr
<nsISupports
> kungFuDeathGrip
;
213 UniquePtr
<ChannelEvent
> event(aCallback
);
215 // To guarantee that the running event and all the events generated within
216 // it will be finished before events on other threads.
217 RecursiveMutexAutoLock
lock(mRunningMutex
);
219 MutexAutoLock
lock(mMutex
);
220 kungFuDeathGrip
= mOwner
; // must be under the lock
222 bool enqueue
= !!mForcedCount
|| mSuspended
|| mFlushing
||
223 !mEventQueue
.IsEmpty() ||
224 MaybeSuspendIfEventsAreSuppressed();
225 // To ensure strict ordering of events across multiple threads we buffer the
226 // events for the below cases:
227 // a. event queuing is forced by AutoEventEnqueuer
228 // b. event queue is suspended
229 // c. an event is currently flushed/executed from the queue
230 // d. queue is non-empty (pending events on remote thread targets)
232 mEventQueue
.AppendElement(std::move(event
));
236 nsCOMPtr
<nsIEventTarget
> target
= event
->GetEventTarget();
239 bool isCurrentThread
= false;
240 DebugOnly
<nsresult
> rv
= target
->IsOnCurrentThread(&isCurrentThread
);
241 MOZ_ASSERT(NS_SUCCEEDED(rv
));
243 if (!isCurrentThread
) {
244 // Leverage Suspend/Resume mechanism to trigger flush procedure without
245 // creating a new one.
246 // The execution of further events in the queue is blocked until the
247 // target thread completes the execution of this event.
248 // A callback is dispatched to the target thread to flush events from the
249 // queue. This is done
250 // by ResumeInternal which dispatches a runnable
251 // (CompleteResumeRunnable) to the target thread. The target thread will
252 // call CompleteResume to flush the queue. All the events are run
253 // synchronously in their respective target threads.
255 mEventQueue
.AppendElement(std::move(event
));
261 MOZ_RELEASE_ASSERT(!aAssertionWhenNotQueued
);
262 // execute the event synchronously if we are not queuing it and
263 // the target thread is the current thread
267 inline void ChannelEventQueue::StartForcedQueueing() {
268 MutexAutoLock
lock(mMutex
);
272 inline void ChannelEventQueue::EndForcedQueueing() {
273 bool tryFlush
= false;
275 MutexAutoLock
lock(mMutex
);
276 MOZ_ASSERT(mForcedCount
> 0);
277 if (!--mForcedCount
) {
287 inline void ChannelEventQueue::PrependEvent(UniquePtr
<ChannelEvent
>&& aEvent
) {
288 MutexAutoLock
lock(mMutex
);
290 // Prepending event while no queue flush foreseen might cause the following
291 // channel events not run. This assertion here guarantee there must be a
292 // queue flush, either triggered by Resume or EndForcedQueueing, to execute
294 MOZ_ASSERT(mSuspended
|| !!mForcedCount
);
296 mEventQueue
.InsertElementAt(0, std::move(aEvent
));
299 inline void ChannelEventQueue::PrependEvents(
300 nsTArray
<UniquePtr
<ChannelEvent
>>& aEvents
) {
301 MutexAutoLock
lock(mMutex
);
303 // Prepending event while no queue flush foreseen might cause the following
304 // channel events not run. This assertion here guarantee there must be a
305 // queue flush, either triggered by Resume or EndForcedQueueing, to execute
307 MOZ_ASSERT(mSuspended
|| !!mForcedCount
);
309 mEventQueue
.InsertElementsAt(0, aEvents
.Length());
311 for (uint32_t i
= 0; i
< aEvents
.Length(); i
++) {
312 mEventQueue
[i
] = std::move(aEvents
[i
]);
316 inline void ChannelEventQueue::CompleteResume() {
317 bool tryFlush
= false;
319 MutexAutoLock
lock(mMutex
);
321 // channel may have been suspended again since Resume fired event to call
323 if (!mSuspendCount
) {
324 // we need to remain logically suspended (for purposes of queuing incoming
325 // messages) until this point, else new incoming messages could run before
337 inline void ChannelEventQueue::MaybeFlushQueue() {
338 // Don't flush if forced queuing on, we're already being flushed, or
339 // suspended, or there's nothing to flush
340 bool flushQueue
= false;
343 MutexAutoLock
lock(mMutex
);
344 flushQueue
= !mForcedCount
&& !mFlushing
&& !mSuspended
&&
345 !mEventQueue
.IsEmpty() && !MaybeSuspendIfEventsAreSuppressed();
347 // Only one thread is allowed to run FlushQueue at a time.
358 // Ensures that RunOrEnqueue() will be collecting events during its lifetime
359 // (letting caller know incoming IPDL msgs should be queued). Flushes the queue
360 // when it goes out of scope.
361 class MOZ_STACK_CLASS AutoEventEnqueuer
{
363 explicit AutoEventEnqueuer(ChannelEventQueue
* queue
) : mEventQueue(queue
) {
365 // Probably not actually needed, since NotifyReleasingOwner should
366 // only happen after this, but safer to take it in case things change
367 MutexAutoLock
lock(queue
->mMutex
);
368 mOwner
= queue
->mOwner
;
370 mEventQueue
->StartForcedQueueing();
372 ~AutoEventEnqueuer() { mEventQueue
->EndForcedQueueing(); }
375 RefPtr
<ChannelEventQueue
> mEventQueue
;
376 // Ensure channel object lives longer than ChannelEventQueue.
377 nsCOMPtr
<nsISupports
> mOwner
;
381 } // namespace mozilla