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/. */
7 #include "MessagePump.h"
11 #include "nsICancelableRunnable.h"
13 #include "base/basictypes.h"
14 #include "base/logging.h"
15 #include "base/scoped_nsautorelease_pool.h"
16 #include "mozilla/Assertions.h"
17 #include "mozilla/DebugOnly.h"
18 #include "nsComponentManagerUtils.h"
20 #include "nsServiceManagerUtils.h"
22 #include "nsThreadUtils.h"
23 #include "nsTimerImpl.h"
24 #include "nsXULAppAPI.h"
27 using base::TimeTicks
;
28 using namespace mozilla::ipc
;
31 static MessagePump::Delegate
* gFirstDelegate
;
37 class DoWorkRunnable final
: public CancelableRunnable
,
38 public nsITimerCallback
{
40 explicit DoWorkRunnable(MessagePump
* aPump
)
41 : CancelableRunnable("ipc::DoWorkRunnable"), mPump(aPump
) {
45 NS_DECL_ISUPPORTS_INHERITED
47 NS_DECL_NSITIMERCALLBACK
48 nsresult
Cancel() override
;
51 ~DoWorkRunnable() = default;
54 // DoWorkRunnable is designed as a stateless singleton. Do not add stateful
59 } /* namespace mozilla */
61 MessagePump::MessagePump(nsISerialEventTarget
* aEventTarget
)
62 : mEventTarget(aEventTarget
) {
63 mDoWorkEvent
= new DoWorkRunnable(this);
66 MessagePump::~MessagePump() = default;
68 void MessagePump::Run(MessagePump::Delegate
* aDelegate
) {
69 MOZ_ASSERT(keep_running_
);
70 MOZ_RELEASE_ASSERT(NS_IsMainThread(),
71 "Use mozilla::ipc::MessagePumpForNonMainThreads instead!");
72 MOZ_RELEASE_ASSERT(!mEventTarget
);
74 nsIThread
* thisThread
= NS_GetCurrentThread();
75 MOZ_ASSERT(thisThread
);
77 mDelayedWorkTimer
= NS_NewTimer();
78 MOZ_ASSERT(mDelayedWorkTimer
);
80 base::ScopedNSAutoreleasePool autoReleasePool
;
83 autoReleasePool
.Recycle();
85 bool did_work
= NS_ProcessNextEvent(thisThread
, false) ? true : false;
86 if (!keep_running_
) break;
88 // NB: it is crucial *not* to directly call |aDelegate->DoWork()|
89 // here. To ensure that MessageLoop tasks and XPCOM events have
90 // equal priority, we sensitively rely on processing exactly one
91 // Task per DoWorkRunnable XPCOM event.
93 did_work
|= aDelegate
->DoDelayedWork(&delayed_work_time_
);
95 if (did_work
&& delayed_work_time_
.is_null()) mDelayedWorkTimer
->Cancel();
97 if (!keep_running_
) break;
99 if (did_work
) continue;
101 did_work
= aDelegate
->DoIdleWork();
102 if (!keep_running_
) break;
104 if (did_work
) continue;
106 // This will either sleep or process an event.
107 NS_ProcessNextEvent(thisThread
, true);
110 mDelayedWorkTimer
->Cancel();
112 keep_running_
= true;
115 void MessagePump::ScheduleWork() {
116 // Make sure the event loop wakes up.
118 mEventTarget
->Dispatch(mDoWorkEvent
, NS_DISPATCH_NORMAL
);
120 // Some things (like xpcshell) don't use the app shell and so Run hasn't
121 // been called. We still need to wake up the main thread.
122 NS_DispatchToMainThread(mDoWorkEvent
);
127 void MessagePump::ScheduleWorkForNestedLoop() {
128 // This method is called when our MessageLoop has just allowed
129 // nested tasks. In our setup, whenever that happens we know that
130 // DoWork() will be called "soon", so there's no need to pay the
131 // cost of what will be a no-op nsThread::Dispatch(mDoWorkEvent).
134 void MessagePump::ScheduleDelayedWork(const base::TimeTicks
& aDelayedTime
) {
135 // To avoid racing on mDelayedWorkTimer, we need to be on the same thread as
137 MOZ_RELEASE_ASSERT((!mEventTarget
&& NS_IsMainThread()) ||
138 mEventTarget
->IsOnCurrentThread());
140 if (!mDelayedWorkTimer
) {
141 mDelayedWorkTimer
= NS_NewTimer();
142 if (!mDelayedWorkTimer
) {
143 // Called before XPCOM has started up? We can't do this correctly.
144 NS_WARNING("Delayed task might not run!");
145 delayed_work_time_
= aDelayedTime
;
150 if (!delayed_work_time_
.is_null()) {
151 mDelayedWorkTimer
->Cancel();
154 delayed_work_time_
= aDelayedTime
;
156 // TimeDelta's constructor initializes to 0
157 base::TimeDelta delay
;
158 if (aDelayedTime
> base::TimeTicks::Now())
159 delay
= aDelayedTime
- base::TimeTicks::Now();
161 uint32_t delayMS
= uint32_t(delay
.InMilliseconds());
162 mDelayedWorkTimer
->InitWithCallback(mDoWorkEvent
, delayMS
,
163 nsITimer::TYPE_ONE_SHOT
);
166 nsISerialEventTarget
* MessagePump::GetXPCOMThread() {
172 return GetMainThreadSerialEventTarget();
175 void MessagePump::DoDelayedWork(base::MessagePump::Delegate
* aDelegate
) {
176 aDelegate
->DoDelayedWork(&delayed_work_time_
);
177 if (!delayed_work_time_
.is_null()) {
178 ScheduleDelayedWork(delayed_work_time_
);
182 NS_IMPL_ISUPPORTS_INHERITED(DoWorkRunnable
, CancelableRunnable
,
186 DoWorkRunnable::Run() {
187 MessageLoop
* loop
= MessageLoop::current();
190 bool nestableTasksAllowed
= loop
->NestableTasksAllowed();
192 // MessageLoop::RunTask() disallows nesting, but our Frankenventloop will
193 // always dispatch DoWork() below from what looks to MessageLoop like a nested
194 // context. So we unconditionally allow nesting here.
195 loop
->SetNestableTasksAllowed(true);
197 loop
->SetNestableTasksAllowed(nestableTasksAllowed
);
203 DoWorkRunnable::Notify(nsITimer
* aTimer
) {
204 MessageLoop
* loop
= MessageLoop::current();
207 bool nestableTasksAllowed
= loop
->NestableTasksAllowed();
208 loop
->SetNestableTasksAllowed(true);
209 mPump
->DoDelayedWork(loop
);
210 loop
->SetNestableTasksAllowed(nestableTasksAllowed
);
215 nsresult
DoWorkRunnable::Cancel() {
216 // Workers require cancelable runnables, but we can't really cancel cleanly
217 // here. If we don't process this runnable then we will leave something
218 // unprocessed in the message_loop. Therefore, eagerly complete our work
219 // instead by immediately calling Run(). Run() should be called separately
220 // after this. Unfortunately we cannot use flags to verify this because
221 // DoWorkRunnable is a stateless singleton that can be in the event queue
222 // multiple times simultaneously.
223 MOZ_ALWAYS_SUCCEEDS(Run());
227 void MessagePumpForChildProcess::Run(base::MessagePump::Delegate
* aDelegate
) {
229 MOZ_ASSERT(aDelegate
&& !gFirstDelegate
);
231 gFirstDelegate
= aDelegate
;
235 if (NS_FAILED(XRE_RunAppShell())) {
236 NS_WARNING("Failed to run app shell?!");
239 MOZ_ASSERT(aDelegate
&& aDelegate
== gFirstDelegate
);
241 gFirstDelegate
= nullptr;
247 MOZ_ASSERT(aDelegate
&& aDelegate
== gFirstDelegate
);
249 // We can get to this point in startup with Tasks in our loop's
250 // incoming_queue_ or pending_queue_, but without a matching
251 // DoWorkRunnable(). In MessagePump::Run() above, we sensitively
252 // depend on *not* directly calling delegate->DoWork(), because that
253 // prioritizes Tasks above XPCOM events. However, from this point
254 // forward, any Task posted to our loop is guaranteed to have a
255 // DoWorkRunnable enqueued for it.
257 // So we just flush the pending work here and move on.
258 MessageLoop
* loop
= MessageLoop::current();
259 bool nestableTasksAllowed
= loop
->NestableTasksAllowed();
260 loop
->SetNestableTasksAllowed(true);
262 while (aDelegate
->DoWork())
265 loop
->SetNestableTasksAllowed(nestableTasksAllowed
);
268 mozilla::ipc::MessagePump::Run(aDelegate
);
271 void MessagePumpForNonMainThreads::Run(base::MessagePump::Delegate
* aDelegate
) {
272 MOZ_ASSERT(keep_running_
);
273 MOZ_RELEASE_ASSERT(!NS_IsMainThread(),
274 "Use mozilla::ipc::MessagePump instead!");
276 nsIThread
* thread
= NS_GetCurrentThread();
277 MOZ_RELEASE_ASSERT(mEventTarget
->IsOnCurrentThread());
279 mDelayedWorkTimer
= NS_NewTimer(mEventTarget
);
280 MOZ_ASSERT(mDelayedWorkTimer
);
282 // Chromium event notifications to be processed will be received by this
283 // event loop as a DoWorkRunnables via ScheduleWork. Chromium events that
284 // were received before our thread is valid, however, will not generate
285 // runnable wrappers. We must process any of these before we enter this
286 // loop, or we will forever have unprocessed chromium messages in our queue.
288 // Note we would like to request a flush of the chromium event queue
289 // using a runnable on the xpcom side, but some thread implementations
290 // (dom workers) get cranky if we call ScheduleWork here (ScheduleWork
291 // calls dispatch on mEventTarget) before the thread processes an event. As
292 // such, clear the queue manually.
293 while (aDelegate
->DoWork()) {
296 base::ScopedNSAutoreleasePool autoReleasePool
;
298 autoReleasePool
.Recycle();
300 bool didWork
= NS_ProcessNextEvent(thread
, false) ? true : false;
301 if (!keep_running_
) {
305 didWork
|= aDelegate
->DoDelayedWork(&delayed_work_time_
);
307 if (didWork
&& delayed_work_time_
.is_null()) {
308 mDelayedWorkTimer
->Cancel();
311 if (!keep_running_
) {
319 DebugOnly
<bool> didIdleWork
= aDelegate
->DoIdleWork();
320 MOZ_ASSERT(!didIdleWork
);
321 if (!keep_running_
) {
329 // This will either sleep or process an event.
330 NS_ProcessNextEvent(thread
, true);
333 mDelayedWorkTimer
->Cancel();
335 keep_running_
= true;