MessagePump.cpp (9935B)
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 "nsIThread.h" 10 #include "nsITimer.h" 11 #include "nsICancelableRunnable.h" 12 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" 19 #include "nsDebug.h" 20 #include "nsServiceManagerUtils.h" 21 #include "nsString.h" 22 #include "nsThreadUtils.h" 23 #include "nsTimerImpl.h" 24 #include "nsXULAppAPI.h" 25 #include "prthread.h" 26 27 using base::TimeTicks; 28 using namespace mozilla::ipc; 29 30 #ifdef DEBUG 31 static MessagePump::Delegate* gFirstDelegate; 32 #endif 33 34 namespace mozilla { 35 namespace ipc { 36 37 class DoWorkRunnable final : public CancelableRunnable, 38 public nsITimerCallback { 39 public: 40 explicit DoWorkRunnable(MessagePump* aPump) 41 : CancelableRunnable("ipc::DoWorkRunnable"), mPump(aPump) { 42 MOZ_ASSERT(aPump); 43 } 44 45 NS_DECL_ISUPPORTS_INHERITED 46 NS_DECL_NSIRUNNABLE 47 NS_DECL_NSITIMERCALLBACK 48 nsresult Cancel() override; 49 50 private: 51 ~DoWorkRunnable() = default; 52 53 MessagePump* mPump; 54 // DoWorkRunnable is designed as a stateless singleton. Do not add stateful 55 // members here! 56 }; 57 58 } /* namespace ipc */ 59 } /* namespace mozilla */ 60 61 MessagePump::MessagePump(nsISerialEventTarget* aEventTarget) 62 : mEventTarget(aEventTarget) { 63 mDoWorkEvent = new DoWorkRunnable(this); 64 } 65 66 MessagePump::~MessagePump() = default; 67 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); 73 74 nsIThread* thisThread = NS_GetCurrentThread(); 75 MOZ_ASSERT(thisThread); 76 77 mDelayedWorkTimer = NS_NewTimer(); 78 MOZ_ASSERT(mDelayedWorkTimer); 79 80 base::ScopedNSAutoreleasePool autoReleasePool; 81 82 for (;;) { 83 autoReleasePool.Recycle(); 84 85 bool did_work = NS_ProcessNextEvent(thisThread, false) ? true : false; 86 if (!keep_running_) break; 87 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. 92 93 did_work |= aDelegate->DoDelayedWork(&delayed_work_time_); 94 95 if (did_work && delayed_work_time_.is_null()) mDelayedWorkTimer->Cancel(); 96 97 if (!keep_running_) break; 98 99 if (did_work) continue; 100 101 did_work = aDelegate->DoIdleWork(); 102 if (!keep_running_) break; 103 104 if (did_work) continue; 105 106 // This will either sleep or process an event. 107 NS_ProcessNextEvent(thisThread, true); 108 } 109 110 mDelayedWorkTimer->Cancel(); 111 112 keep_running_ = true; 113 } 114 115 void MessagePump::ScheduleWork() { 116 // Make sure the event loop wakes up. 117 if (mEventTarget) { 118 mEventTarget->Dispatch(mDoWorkEvent, NS_DISPATCH_NORMAL); 119 } else { 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); 123 } 124 event_.Signal(); 125 } 126 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). 132 } 133 134 void MessagePump::ScheduleDelayedWork(const base::TimeTicks& aDelayedTime) { 135 // To avoid racing on mDelayedWorkTimer, we need to be on the same thread as 136 // ::Run(). 137 MOZ_RELEASE_ASSERT((!mEventTarget && NS_IsMainThread()) || 138 mEventTarget->IsOnCurrentThread()); 139 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; 146 return; 147 } 148 } 149 150 if (!delayed_work_time_.is_null()) { 151 mDelayedWorkTimer->Cancel(); 152 } 153 154 delayed_work_time_ = aDelayedTime; 155 156 // TimeDelta's constructor initializes to 0 157 base::TimeDelta delay; 158 if (aDelayedTime > base::TimeTicks::Now()) 159 delay = aDelayedTime - base::TimeTicks::Now(); 160 161 uint32_t delayMS = uint32_t(delay.InMilliseconds()); 162 mDelayedWorkTimer->InitWithCallback(mDoWorkEvent, delayMS, 163 nsITimer::TYPE_ONE_SHOT); 164 } 165 166 nsISerialEventTarget* MessagePump::GetXPCOMThread() { 167 if (mEventTarget) { 168 return mEventTarget; 169 } 170 171 // Main thread 172 return GetMainThreadSerialEventTarget(); 173 } 174 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_); 179 } 180 } 181 182 NS_IMPL_ISUPPORTS_INHERITED(DoWorkRunnable, CancelableRunnable, 183 nsITimerCallback) 184 185 NS_IMETHODIMP 186 DoWorkRunnable::Run() { 187 MessageLoop* loop = MessageLoop::current(); 188 MOZ_ASSERT(loop); 189 190 bool nestableTasksAllowed = loop->NestableTasksAllowed(); 191 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); 196 loop->DoWork(); 197 loop->SetNestableTasksAllowed(nestableTasksAllowed); 198 199 return NS_OK; 200 } 201 202 NS_IMETHODIMP 203 DoWorkRunnable::Notify(nsITimer* aTimer) { 204 MessageLoop* loop = MessageLoop::current(); 205 MOZ_ASSERT(loop); 206 207 bool nestableTasksAllowed = loop->NestableTasksAllowed(); 208 loop->SetNestableTasksAllowed(true); 209 mPump->DoDelayedWork(loop); 210 loop->SetNestableTasksAllowed(nestableTasksAllowed); 211 212 return NS_OK; 213 } 214 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()); 224 return NS_OK; 225 } 226 227 void MessagePumpForChildProcess::Run(base::MessagePump::Delegate* aDelegate) { 228 if (mFirstRun) { 229 MOZ_ASSERT(aDelegate && !gFirstDelegate); 230 #ifdef DEBUG 231 gFirstDelegate = aDelegate; 232 #endif 233 234 mFirstRun = false; 235 if (NS_FAILED(XRE_RunAppShell())) { 236 NS_WARNING("Failed to run app shell?!"); 237 } 238 239 MOZ_ASSERT(aDelegate && aDelegate == gFirstDelegate); 240 #ifdef DEBUG 241 gFirstDelegate = nullptr; 242 #endif 243 244 return; 245 } 246 247 MOZ_ASSERT(aDelegate && aDelegate == gFirstDelegate); 248 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. 256 // 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); 261 262 while (aDelegate->DoWork()); 263 264 loop->SetNestableTasksAllowed(nestableTasksAllowed); 265 266 // Really run. 267 mozilla::ipc::MessagePump::Run(aDelegate); 268 } 269 270 void MessagePumpForNonMainThreads::Run(base::MessagePump::Delegate* aDelegate) { 271 MOZ_ASSERT(keep_running_); 272 MOZ_RELEASE_ASSERT(!NS_IsMainThread(), 273 "Use mozilla::ipc::MessagePump instead!"); 274 275 nsIThread* thread = NS_GetCurrentThread(); 276 MOZ_RELEASE_ASSERT(mEventTarget->IsOnCurrentThread()); 277 278 mDelayedWorkTimer = NS_NewTimer(mEventTarget); 279 MOZ_ASSERT(mDelayedWorkTimer); 280 281 // Chromium event notifications to be processed will be received by this 282 // event loop as a DoWorkRunnables via ScheduleWork. Chromium events that 283 // were received before our thread is valid, however, will not generate 284 // runnable wrappers. We must process any of these before we enter this 285 // loop, or we will forever have unprocessed chromium messages in our queue. 286 // 287 // Note we would like to request a flush of the chromium event queue 288 // using a runnable on the xpcom side, but some thread implementations 289 // (dom workers) get cranky if we call ScheduleWork here (ScheduleWork 290 // calls dispatch on mEventTarget) before the thread processes an event. As 291 // such, clear the queue manually. 292 while (aDelegate->DoWork()) { 293 } 294 295 base::ScopedNSAutoreleasePool autoReleasePool; 296 for (;;) { 297 autoReleasePool.Recycle(); 298 299 bool didWork = NS_ProcessNextEvent(thread, false) ? true : false; 300 if (!keep_running_) { 301 break; 302 } 303 304 didWork |= aDelegate->DoDelayedWork(&delayed_work_time_); 305 306 if (didWork && delayed_work_time_.is_null()) { 307 mDelayedWorkTimer->Cancel(); 308 } 309 310 if (!keep_running_) { 311 break; 312 } 313 314 if (didWork) { 315 continue; 316 } 317 318 DebugOnly<bool> didIdleWork = aDelegate->DoIdleWork(); 319 MOZ_ASSERT(!didIdleWork); 320 if (!keep_running_) { 321 break; 322 } 323 324 if (didWork) { 325 continue; 326 } 327 328 // This will either sleep or process an event. 329 NS_ProcessNextEvent(thread, true); 330 } 331 332 mDelayedWorkTimer->Cancel(); 333 334 keep_running_ = true; 335 }