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