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 #include "MessagePump.h"
8
9 #include "nsIRunnable.h"
10 #include "nsIThread.h"
11 #include "nsITimer.h"
12 #include "nsICancelableRunnable.h"
13
14 #include "base/basictypes.h"
15 #include "base/logging.h"
16 #include "base/scoped_nsautorelease_pool.h"
17 #include "mozilla/Assertions.h"
18 #include "mozilla/DebugOnly.h"
19 #include "nsComponentManagerUtils.h"
20 #include "nsDebug.h"
21 #include "nsServiceManagerUtils.h"
22 #include "nsString.h"
23 #include "nsThreadUtils.h"
24 #include "nsTimerImpl.h"
25 #include "nsXULAppAPI.h"
26 #include "prthread.h"
27
28 using base::TimeTicks;
29 using namespace mozilla::ipc;
30
31 NS_DEFINE_NAMED_CID(NS_TIMER_CID);
32
33 #ifdef DEBUG
34 static MessagePump::Delegate* gFirstDelegate;
35 #endif
36
37 namespace mozilla {
38 namespace ipc {
39
40 class DoWorkRunnable final : public CancelableRunnable,
41 public nsITimerCallback
42 {
43 public:
DoWorkRunnable(MessagePump * aPump)44 explicit DoWorkRunnable(MessagePump* aPump)
45 : mPump(aPump)
46 {
47 MOZ_ASSERT(aPump);
48 }
49
50 NS_DECL_ISUPPORTS_INHERITED
51 NS_DECL_NSIRUNNABLE
52 NS_DECL_NSITIMERCALLBACK
53 nsresult Cancel() override;
54
55 private:
~DoWorkRunnable()56 ~DoWorkRunnable()
57 { }
58
59 MessagePump* mPump;
60 // DoWorkRunnable is designed as a stateless singleton. Do not add stateful
61 // members here!
62 };
63
64 } /* namespace ipc */
65 } /* namespace mozilla */
66
MessagePump(nsIThread * aThread)67 MessagePump::MessagePump(nsIThread* aThread)
68 : mThread(aThread)
69 {
70 mDoWorkEvent = new DoWorkRunnable(this);
71 }
72
~MessagePump()73 MessagePump::~MessagePump()
74 {
75 }
76
77 void
Run(MessagePump::Delegate * aDelegate)78 MessagePump::Run(MessagePump::Delegate* aDelegate)
79 {
80 MOZ_ASSERT(keep_running_);
81 MOZ_RELEASE_ASSERT(NS_IsMainThread(),
82 "Use mozilla::ipc::MessagePumpForNonMainThreads instead!");
83 MOZ_RELEASE_ASSERT(!mThread);
84
85 nsIThread* thisThread = NS_GetCurrentThread();
86 MOZ_ASSERT(thisThread);
87
88 mDelayedWorkTimer = do_CreateInstance(kNS_TIMER_CID);
89 MOZ_ASSERT(mDelayedWorkTimer);
90
91 base::ScopedNSAutoreleasePool autoReleasePool;
92
93 for (;;) {
94 autoReleasePool.Recycle();
95
96 bool did_work = NS_ProcessNextEvent(thisThread, false) ? true : false;
97 if (!keep_running_)
98 break;
99
100 // NB: it is crucial *not* to directly call |aDelegate->DoWork()|
101 // here. To ensure that MessageLoop tasks and XPCOM events have
102 // equal priority, we sensitively rely on processing exactly one
103 // Task per DoWorkRunnable XPCOM event.
104
105 did_work |= aDelegate->DoDelayedWork(&delayed_work_time_);
106
107 if (did_work && delayed_work_time_.is_null())
108 mDelayedWorkTimer->Cancel();
109
110 if (!keep_running_)
111 break;
112
113 if (did_work)
114 continue;
115
116 did_work = aDelegate->DoIdleWork();
117 if (!keep_running_)
118 break;
119
120 if (did_work)
121 continue;
122
123 // This will either sleep or process an event.
124 NS_ProcessNextEvent(thisThread, true);
125 }
126
127 mDelayedWorkTimer->Cancel();
128
129 keep_running_ = true;
130 }
131
132 void
ScheduleWork()133 MessagePump::ScheduleWork()
134 {
135 // Make sure the event loop wakes up.
136 if (mThread) {
137 mThread->Dispatch(mDoWorkEvent, NS_DISPATCH_NORMAL);
138 } else {
139 // Some things (like xpcshell) don't use the app shell and so Run hasn't
140 // been called. We still need to wake up the main thread.
141 NS_DispatchToMainThread(mDoWorkEvent);
142 }
143 event_.Signal();
144 }
145
146 void
ScheduleWorkForNestedLoop()147 MessagePump::ScheduleWorkForNestedLoop()
148 {
149 // This method is called when our MessageLoop has just allowed
150 // nested tasks. In our setup, whenever that happens we know that
151 // DoWork() will be called "soon", so there's no need to pay the
152 // cost of what will be a no-op nsThread::Dispatch(mDoWorkEvent).
153 }
154
155 void
ScheduleDelayedWork(const base::TimeTicks & aDelayedTime)156 MessagePump::ScheduleDelayedWork(const base::TimeTicks& aDelayedTime)
157 {
158 // To avoid racing on mDelayedWorkTimer, we need to be on the same thread as
159 // ::Run().
160 MOZ_RELEASE_ASSERT(NS_GetCurrentThread() == mThread ||
161 (!mThread && NS_IsMainThread()));
162
163 if (!mDelayedWorkTimer) {
164 mDelayedWorkTimer = do_CreateInstance(kNS_TIMER_CID);
165 if (!mDelayedWorkTimer) {
166 // Called before XPCOM has started up? We can't do this correctly.
167 NS_WARNING("Delayed task might not run!");
168 delayed_work_time_ = aDelayedTime;
169 return;
170 }
171 }
172
173 if (!delayed_work_time_.is_null()) {
174 mDelayedWorkTimer->Cancel();
175 }
176
177 delayed_work_time_ = aDelayedTime;
178
179 // TimeDelta's constructor initializes to 0
180 base::TimeDelta delay;
181 if (aDelayedTime > base::TimeTicks::Now())
182 delay = aDelayedTime - base::TimeTicks::Now();
183
184 uint32_t delayMS = uint32_t(delay.InMilliseconds());
185 mDelayedWorkTimer->InitWithCallback(mDoWorkEvent, delayMS,
186 nsITimer::TYPE_ONE_SHOT);
187 }
188
189 nsIEventTarget*
GetXPCOMThread()190 MessagePump::GetXPCOMThread()
191 {
192 if (mThread) {
193 return mThread;
194 }
195
196 // Main thread
197 nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
198 return mainThread;
199 }
200
201 void
DoDelayedWork(base::MessagePump::Delegate * aDelegate)202 MessagePump::DoDelayedWork(base::MessagePump::Delegate* aDelegate)
203 {
204 aDelegate->DoDelayedWork(&delayed_work_time_);
205 if (!delayed_work_time_.is_null()) {
206 ScheduleDelayedWork(delayed_work_time_);
207 }
208 }
209
NS_IMPL_ISUPPORTS_INHERITED(DoWorkRunnable,CancelableRunnable,nsITimerCallback)210 NS_IMPL_ISUPPORTS_INHERITED(DoWorkRunnable, CancelableRunnable,
211 nsITimerCallback)
212
213 NS_IMETHODIMP
214 DoWorkRunnable::Run()
215 {
216 MessageLoop* loop = MessageLoop::current();
217 MOZ_ASSERT(loop);
218
219 bool nestableTasksAllowed = loop->NestableTasksAllowed();
220
221 // MessageLoop::RunTask() disallows nesting, but our Frankenventloop will
222 // always dispatch DoWork() below from what looks to MessageLoop like a nested
223 // context. So we unconditionally allow nesting here.
224 loop->SetNestableTasksAllowed(true);
225 loop->DoWork();
226 loop->SetNestableTasksAllowed(nestableTasksAllowed);
227
228 return NS_OK;
229 }
230
231 NS_IMETHODIMP
Notify(nsITimer * aTimer)232 DoWorkRunnable::Notify(nsITimer* aTimer)
233 {
234 MessageLoop* loop = MessageLoop::current();
235 MOZ_ASSERT(loop);
236
237 bool nestableTasksAllowed = loop->NestableTasksAllowed();
238 loop->SetNestableTasksAllowed(true);
239 mPump->DoDelayedWork(loop);
240 loop->SetNestableTasksAllowed(nestableTasksAllowed);
241
242 return NS_OK;
243 }
244
245 nsresult
Cancel()246 DoWorkRunnable::Cancel()
247 {
248 // Workers require cancelable runnables, but we can't really cancel cleanly
249 // here. If we don't process this runnable then we will leave something
250 // unprocessed in the message_loop. Therefore, eagerly complete our work
251 // instead by immediately calling Run(). Run() should be called separately
252 // after this. Unfortunately we cannot use flags to verify this because
253 // DoWorkRunnable is a stateless singleton that can be in the event queue
254 // multiple times simultaneously.
255 MOZ_ALWAYS_SUCCEEDS(Run());
256 return NS_OK;
257 }
258
259 void
Run(base::MessagePump::Delegate * aDelegate)260 MessagePumpForChildProcess::Run(base::MessagePump::Delegate* aDelegate)
261 {
262 if (mFirstRun) {
263 MOZ_ASSERT(aDelegate && !gFirstDelegate);
264 #ifdef DEBUG
265 gFirstDelegate = aDelegate;
266 #endif
267
268 mFirstRun = false;
269 if (NS_FAILED(XRE_RunAppShell())) {
270 NS_WARNING("Failed to run app shell?!");
271 }
272
273 MOZ_ASSERT(aDelegate && aDelegate == gFirstDelegate);
274 #ifdef DEBUG
275 gFirstDelegate = nullptr;
276 #endif
277
278 return;
279 }
280
281 MOZ_ASSERT(aDelegate && aDelegate == gFirstDelegate);
282
283 // We can get to this point in startup with Tasks in our loop's
284 // incoming_queue_ or pending_queue_, but without a matching
285 // DoWorkRunnable(). In MessagePump::Run() above, we sensitively
286 // depend on *not* directly calling delegate->DoWork(), because that
287 // prioritizes Tasks above XPCOM events. However, from this point
288 // forward, any Task posted to our loop is guaranteed to have a
289 // DoWorkRunnable enqueued for it.
290 //
291 // So we just flush the pending work here and move on.
292 MessageLoop* loop = MessageLoop::current();
293 bool nestableTasksAllowed = loop->NestableTasksAllowed();
294 loop->SetNestableTasksAllowed(true);
295
296 while (aDelegate->DoWork());
297
298 loop->SetNestableTasksAllowed(nestableTasksAllowed);
299
300 // Really run.
301 mozilla::ipc::MessagePump::Run(aDelegate);
302 }
303
304 void
Run(base::MessagePump::Delegate * aDelegate)305 MessagePumpForNonMainThreads::Run(base::MessagePump::Delegate* aDelegate)
306 {
307 MOZ_ASSERT(keep_running_);
308 MOZ_RELEASE_ASSERT(!NS_IsMainThread(), "Use mozilla::ipc::MessagePump instead!");
309
310 nsIThread* thread = NS_GetCurrentThread();
311 MOZ_RELEASE_ASSERT(mThread == thread);
312
313 mDelayedWorkTimer = do_CreateInstance(kNS_TIMER_CID);
314 MOZ_ASSERT(mDelayedWorkTimer);
315
316 if (NS_FAILED(mDelayedWorkTimer->SetTarget(thread))) {
317 MOZ_CRASH("Failed to set timer target!");
318 }
319
320 // Chromium event notifications to be processed will be received by this
321 // event loop as a DoWorkRunnables via ScheduleWork. Chromium events that
322 // were received before our thread is valid, however, will not generate
323 // runnable wrappers. We must process any of these before we enter this
324 // loop, or we will forever have unprocessed chromium messages in our queue.
325 //
326 // Note we would like to request a flush of the chromium event queue
327 // using a runnable on the xpcom side, but some thread implementations
328 // (dom workers) get cranky if we call ScheduleWork here (ScheduleWork
329 // calls dispatch on mThread) before the thread processes an event. As
330 // such, clear the queue manually.
331 while (aDelegate->DoWork()) {
332 }
333
334 base::ScopedNSAutoreleasePool autoReleasePool;
335 for (;;) {
336 autoReleasePool.Recycle();
337
338 bool didWork = NS_ProcessNextEvent(thread, false) ? true : false;
339 if (!keep_running_) {
340 break;
341 }
342
343 didWork |= aDelegate->DoDelayedWork(&delayed_work_time_);
344
345 if (didWork && delayed_work_time_.is_null()) {
346 mDelayedWorkTimer->Cancel();
347 }
348
349 if (!keep_running_) {
350 break;
351 }
352
353 if (didWork) {
354 continue;
355 }
356
357 DebugOnly<bool> didIdleWork = aDelegate->DoIdleWork();
358 MOZ_ASSERT(!didIdleWork);
359 if (!keep_running_) {
360 break;
361 }
362
363 if (didWork) {
364 continue;
365 }
366
367 // This will either sleep or process an event.
368 NS_ProcessNextEvent(thread, true);
369 }
370
371 mDelayedWorkTimer->Cancel();
372
373 keep_running_ = true;
374 }
375
376 #if defined(XP_WIN)
377
NS_IMPL_QUERY_INTERFACE(MessagePumpForNonMainUIThreads,nsIThreadObserver)378 NS_IMPL_QUERY_INTERFACE(MessagePumpForNonMainUIThreads, nsIThreadObserver)
379
380 #define CHECK_QUIT_STATE { if (state_->should_quit) { break; } }
381
382 void
383 MessagePumpForNonMainUIThreads::DoRunLoop()
384 {
385 MOZ_RELEASE_ASSERT(!NS_IsMainThread(), "Use mozilla::ipc::MessagePump instead!");
386
387 // If this is a chromium thread and no nsThread is associated
388 // with it, this call will create a new nsThread.
389 nsIThread* thread = NS_GetCurrentThread();
390 MOZ_ASSERT(thread);
391
392 // Set the main thread observer so we can wake up when
393 // xpcom events need to get processed.
394 nsCOMPtr<nsIThreadInternal> ti(do_QueryInterface(thread));
395 MOZ_ASSERT(ti);
396 ti->SetObserver(this);
397
398 base::ScopedNSAutoreleasePool autoReleasePool;
399 for (;;) {
400 autoReleasePool.Recycle();
401
402 bool didWork = NS_ProcessNextEvent(thread, false);
403
404 didWork |= ProcessNextWindowsMessage();
405 CHECK_QUIT_STATE
406
407 didWork |= state_->delegate->DoWork();
408 CHECK_QUIT_STATE
409
410 didWork |= state_->delegate->DoDelayedWork(&delayed_work_time_);
411 if (didWork && delayed_work_time_.is_null()) {
412 KillTimer(message_hwnd_, reinterpret_cast<UINT_PTR>(this));
413 }
414 CHECK_QUIT_STATE
415
416 if (didWork) {
417 continue;
418 }
419
420 DebugOnly<bool> didIdleWork = state_->delegate->DoIdleWork();
421 MOZ_ASSERT(!didIdleWork);
422 CHECK_QUIT_STATE
423
424 SetInWait();
425 bool hasWork = NS_HasPendingEvents(thread);
426 if (didWork || hasWork) {
427 ClearInWait();
428 continue;
429 }
430 WaitForWork(); // Calls MsgWaitForMultipleObjectsEx(QS_ALLINPUT)
431 ClearInWait();
432 }
433
434 ClearInWait();
435
436 ti->SetObserver(nullptr);
437 }
438
439 NS_IMETHODIMP
OnDispatchedEvent(nsIThreadInternal * thread)440 MessagePumpForNonMainUIThreads::OnDispatchedEvent(nsIThreadInternal *thread)
441 {
442 // If our thread is sleeping in DoRunLoop's call to WaitForWork() and an
443 // event posts to the nsIThread event queue - break our thread out of
444 // chromium's WaitForWork.
445 if (GetInWait()) {
446 ScheduleWork();
447 }
448 return NS_OK;
449 }
450
451 NS_IMETHODIMP
OnProcessNextEvent(nsIThreadInternal * thread,bool mayWait)452 MessagePumpForNonMainUIThreads::OnProcessNextEvent(nsIThreadInternal *thread,
453 bool mayWait)
454 {
455 return NS_OK;
456 }
457
458 NS_IMETHODIMP
AfterProcessNextEvent(nsIThreadInternal * thread,bool eventWasProcessed)459 MessagePumpForNonMainUIThreads::AfterProcessNextEvent(nsIThreadInternal *thread,
460 bool eventWasProcessed)
461 {
462 return NS_OK;
463 }
464
465 #endif // XP_WIN
466