Backed out 10 changesets (bug 1803810) for xpcshell failures on test_import_global...
[gecko.git] / netwerk / ipc / ChannelEventQueue.h
blob1b61088e91bd0e2df3db28752fb85ec632ab4038
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set sw=2 ts=8 et tw=80 :
3 */
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
11 #include "nsTArray.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"
21 class nsISupports;
23 namespace mozilla {
24 namespace net {
26 class ChannelEvent {
27 public:
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 {
36 public:
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 {
48 public:
49 ChannelFunctionEvent(
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();
60 private:
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
69 // is resolved.
70 template <typename T>
71 class UnsafePtr {
72 public:
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");
78 return mPtr;
80 operator T*() const& { return mPtr; }
81 explicit operator bool() const { return mPtr != nullptr; }
83 private:
84 T* const mPtr;
87 class NeckoTargetChannelFunctionEvent : public ChannelFunctionEvent {
88 public:
89 template <typename T>
90 NeckoTargetChannelFunctionEvent(T* aChild, std::function<void()>&& aCallback)
91 : ChannelFunctionEvent(
92 [child = UnsafePtr<T>(aChild)]() {
93 MOZ_ASSERT(child);
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
104 // completed.
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)
111 public:
112 explicit ChannelEventQueue(nsISupports* owner)
113 : mSuspendCount(0),
114 mSuspended(false),
115 mForcedCount(0),
116 mFlushing(false),
117 mHasCheckedForXMLHttpRequest(false),
118 mForXMLHttpRequest(false),
119 mOwner(owner),
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
140 // conditions.
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.
147 void Suspend();
148 // Resume flushes the queue asynchronously, i.e. items in queue will be
149 // dispatched in a new event on the current thread.
150 void Resume();
152 void NotifyReleasingOwner() {
153 MutexAutoLock lock(mMutex);
154 mOwner = nullptr;
157 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
158 bool IsEmpty() {
159 MutexAutoLock lock(mMutex);
160 return mEventQueue.IsEmpty();
162 #endif
164 private:
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();
174 void FlushQueue();
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
196 Mutex mMutex;
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;
212 // To avoid leaks.
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)
231 if (enqueue) {
232 mEventQueue.AppendElement(std::move(event));
233 return;
236 nsCOMPtr<nsIEventTarget> target = event->GetEventTarget();
237 MOZ_ASSERT(target);
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.
254 SuspendInternal();
255 mEventQueue.AppendElement(std::move(event));
256 ResumeInternal();
257 return;
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
264 event->Run();
267 inline void ChannelEventQueue::StartForcedQueueing() {
268 MutexAutoLock lock(mMutex);
269 ++mForcedCount;
272 inline void ChannelEventQueue::EndForcedQueueing() {
273 bool tryFlush = false;
275 MutexAutoLock lock(mMutex);
276 MOZ_ASSERT(mForcedCount > 0);
277 if (!--mForcedCount) {
278 tryFlush = true;
282 if (tryFlush) {
283 MaybeFlushQueue();
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
293 // the added event.
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
306 // the added events.
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
322 // this.
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
326 // queued ones.
327 mSuspended = false;
328 tryFlush = true;
332 if (tryFlush) {
333 MaybeFlushQueue();
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.
348 if (flushQueue) {
349 mFlushing = true;
353 if (flushQueue) {
354 FlushQueue();
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 {
362 public:
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(); }
374 private:
375 RefPtr<ChannelEventQueue> mEventQueue;
376 // Ensure channel object lives longer than ChannelEventQueue.
377 nsCOMPtr<nsISupports> mOwner;
380 } // namespace net
381 } // namespace mozilla
383 #endif