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(GetMainThreadEventTarget());
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
106 class ChannelEventQueue final
{
107 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ChannelEventQueue
)
110 explicit ChannelEventQueue(nsISupports
* owner
)
115 mHasCheckedForXMLHttpRequest(false),
116 mForXMLHttpRequest(false),
118 mMutex("ChannelEventQueue::mMutex"),
119 mRunningMutex("ChannelEventQueue::mRunningMutex") {}
121 // Puts IPDL-generated channel event into queue, to be run later
122 // automatically when EndForcedQueueing and/or Resume is called.
124 // @param aCallback - the ChannelEvent
125 // @param aAssertionWhenNotQueued - this optional param will be used in an
126 // assertion when the event is executed directly.
127 inline void RunOrEnqueue(ChannelEvent
* aCallback
,
128 bool aAssertionWhenNotQueued
= false);
130 // Append ChannelEvent in front of the event queue.
131 inline void PrependEvent(UniquePtr
<ChannelEvent
>&& aEvent
);
132 inline void PrependEvents(nsTArray
<UniquePtr
<ChannelEvent
>>& aEvents
);
134 // After StartForcedQueueing is called, RunOrEnqueue() will start enqueuing
135 // events that will be run/flushed when EndForcedQueueing is called.
136 // - Note: queueing may still be required after EndForcedQueueing() (if the
137 // queue is suspended, etc): always call RunOrEnqueue() to avoid race
139 inline void StartForcedQueueing();
140 inline void EndForcedQueueing();
142 // Suspend/resume event queue. RunOrEnqueue() will start enqueuing
143 // events and they will be run/flushed when resume is called. These should be
144 // called when the channel owning the event queue is suspended/resumed.
146 // Resume flushes the queue asynchronously, i.e. items in queue will be
147 // dispatched in a new event on the current thread.
150 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
151 bool IsEmpty() const { return mEventQueue
.IsEmpty(); }
155 // Private destructor, to discourage deletion outside of Release():
156 ~ChannelEventQueue() = default;
158 void SuspendInternal();
159 void ResumeInternal();
161 bool MaybeSuspendIfEventsAreSuppressed();
163 inline void MaybeFlushQueue();
165 inline void CompleteResume();
167 ChannelEvent
* TakeEvent();
169 nsTArray
<UniquePtr
<ChannelEvent
>> mEventQueue
;
171 uint32_t mSuspendCount
;
173 uint32_t mForcedCount
; // Support ForcedQueueing on multiple thread.
176 // Whether the queue is associated with an XHR. This is lazily instantiated
177 // the first time it is needed.
178 bool mHasCheckedForXMLHttpRequest
;
179 bool mForXMLHttpRequest
;
181 // Keep ptr to avoid refcount cycle: only grab ref during flushing.
184 // For atomic mEventQueue operation and state update
187 // To guarantee event execution order among threads
188 RecursiveMutex mRunningMutex
;
190 friend class AutoEventEnqueuer
;
193 inline void ChannelEventQueue::RunOrEnqueue(ChannelEvent
* aCallback
,
194 bool aAssertionWhenNotQueued
) {
195 MOZ_ASSERT(aCallback
);
197 // Events execution could be a destruction of the channel (and our own
198 // destructor) unless we make sure its refcount doesn't drop to 0 while this
199 // method is running.
200 nsCOMPtr
<nsISupports
> kungFuDeathGrip(mOwner
);
201 Unused
<< kungFuDeathGrip
; // Not used in this function
204 UniquePtr
<ChannelEvent
> event(aCallback
);
206 // To guarantee that the running event and all the events generated within
207 // it will be finished before events on other threads.
208 RecursiveMutexAutoLock
lock(mRunningMutex
);
211 MutexAutoLock
lock(mMutex
);
213 bool enqueue
= !!mForcedCount
|| mSuspended
|| mFlushing
||
214 !mEventQueue
.IsEmpty() ||
215 MaybeSuspendIfEventsAreSuppressed();
218 mEventQueue
.AppendElement(std::move(event
));
222 nsCOMPtr
<nsIEventTarget
> target
= event
->GetEventTarget();
225 bool isCurrentThread
= false;
226 DebugOnly
<nsresult
> rv
= target
->IsOnCurrentThread(&isCurrentThread
);
227 MOZ_ASSERT(NS_SUCCEEDED(rv
));
229 if (!isCurrentThread
) {
230 // Leverage Suspend/Resume mechanism to trigger flush procedure without
231 // creating a new one.
233 mEventQueue
.AppendElement(std::move(event
));
239 MOZ_RELEASE_ASSERT(!aAssertionWhenNotQueued
);
243 inline void ChannelEventQueue::StartForcedQueueing() {
244 MutexAutoLock
lock(mMutex
);
248 inline void ChannelEventQueue::EndForcedQueueing() {
249 bool tryFlush
= false;
251 MutexAutoLock
lock(mMutex
);
252 MOZ_ASSERT(mForcedCount
> 0);
253 if (!--mForcedCount
) {
263 inline void ChannelEventQueue::PrependEvent(UniquePtr
<ChannelEvent
>&& aEvent
) {
264 MutexAutoLock
lock(mMutex
);
266 // Prepending event while no queue flush foreseen might cause the following
267 // channel events not run. This assertion here guarantee there must be a
268 // queue flush, either triggered by Resume or EndForcedQueueing, to execute
270 MOZ_ASSERT(mSuspended
|| !!mForcedCount
);
272 mEventQueue
.InsertElementAt(0, std::move(aEvent
));
275 inline void ChannelEventQueue::PrependEvents(
276 nsTArray
<UniquePtr
<ChannelEvent
>>& aEvents
) {
277 MutexAutoLock
lock(mMutex
);
279 // Prepending event while no queue flush foreseen might cause the following
280 // channel events not run. This assertion here guarantee there must be a
281 // queue flush, either triggered by Resume or EndForcedQueueing, to execute
283 MOZ_ASSERT(mSuspended
|| !!mForcedCount
);
285 mEventQueue
.InsertElementsAt(0, aEvents
.Length());
287 for (uint32_t i
= 0; i
< aEvents
.Length(); i
++) {
288 mEventQueue
[i
] = std::move(aEvents
[i
]);
292 inline void ChannelEventQueue::CompleteResume() {
293 bool tryFlush
= false;
295 MutexAutoLock
lock(mMutex
);
297 // channel may have been suspended again since Resume fired event to call
299 if (!mSuspendCount
) {
300 // we need to remain logically suspended (for purposes of queuing incoming
301 // messages) until this point, else new incoming messages could run before
313 inline void ChannelEventQueue::MaybeFlushQueue() {
314 // Don't flush if forced queuing on, we're already being flushed, or
315 // suspended, or there's nothing to flush
316 bool flushQueue
= false;
319 MutexAutoLock
lock(mMutex
);
320 flushQueue
= !mForcedCount
&& !mFlushing
&& !mSuspended
&&
321 !mEventQueue
.IsEmpty() && !MaybeSuspendIfEventsAreSuppressed();
323 // Only one thread is allowed to run FlushQueue at a time.
334 // Ensures that RunOrEnqueue() will be collecting events during its lifetime
335 // (letting caller know incoming IPDL msgs should be queued). Flushes the queue
336 // when it goes out of scope.
337 class MOZ_STACK_CLASS AutoEventEnqueuer
{
339 explicit AutoEventEnqueuer(ChannelEventQueue
* queue
)
340 : mEventQueue(queue
), mOwner(queue
->mOwner
) {
341 mEventQueue
->StartForcedQueueing();
343 ~AutoEventEnqueuer() { mEventQueue
->EndForcedQueueing(); }
346 RefPtr
<ChannelEventQueue
> mEventQueue
;
347 // Ensure channel object lives longer than ChannelEventQueue.
348 nsCOMPtr
<nsISupports
> mOwner
;
352 } // namespace mozilla