/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "MessagePump.h" #include "nsIThread.h" #include "nsITimer.h" #include "nsICancelableRunnable.h" #include "base/basictypes.h" #include "base/logging.h" #include "base/scoped_nsautorelease_pool.h" #include "mozilla/Assertions.h" #include "mozilla/DebugOnly.h" #include "nsComponentManagerUtils.h" #include "nsDebug.h" #include "nsServiceManagerUtils.h" #include "nsString.h" #include "nsThreadUtils.h" #include "nsTimerImpl.h" #include "nsXULAppAPI.h" #include "prthread.h" using base::TimeTicks; using namespace mozilla::ipc; #ifdef DEBUG static MessagePump::Delegate* gFirstDelegate; #endif namespace mozilla { namespace ipc { class DoWorkRunnable final : public CancelableRunnable, public nsITimerCallback { public: explicit DoWorkRunnable(MessagePump* aPump) : CancelableRunnable("ipc::DoWorkRunnable"), mPump(aPump) { MOZ_ASSERT(aPump); } NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSIRUNNABLE NS_DECL_NSITIMERCALLBACK nsresult Cancel() override; private: ~DoWorkRunnable() = default; MessagePump* mPump; // DoWorkRunnable is designed as a stateless singleton. Do not add stateful // members here! }; } /* namespace ipc */ } /* namespace mozilla */ MessagePump::MessagePump(nsISerialEventTarget* aEventTarget) : mEventTarget(aEventTarget) { mDoWorkEvent = new DoWorkRunnable(this); } MessagePump::~MessagePump() = default; void MessagePump::Run(MessagePump::Delegate* aDelegate) { MOZ_ASSERT(keep_running_); MOZ_RELEASE_ASSERT(NS_IsMainThread(), "Use mozilla::ipc::MessagePumpForNonMainThreads instead!"); MOZ_RELEASE_ASSERT(!mEventTarget); nsIThread* thisThread = NS_GetCurrentThread(); MOZ_ASSERT(thisThread); mDelayedWorkTimer = NS_NewTimer(); MOZ_ASSERT(mDelayedWorkTimer); base::ScopedNSAutoreleasePool autoReleasePool; for (;;) { autoReleasePool.Recycle(); bool did_work = NS_ProcessNextEvent(thisThread, false) ? true : false; if (!keep_running_) break; // NB: it is crucial *not* to directly call |aDelegate->DoWork()| // here. To ensure that MessageLoop tasks and XPCOM events have // equal priority, we sensitively rely on processing exactly one // Task per DoWorkRunnable XPCOM event. did_work |= aDelegate->DoDelayedWork(&delayed_work_time_); if (did_work && delayed_work_time_.is_null()) mDelayedWorkTimer->Cancel(); if (!keep_running_) break; if (did_work) continue; did_work = aDelegate->DoIdleWork(); if (!keep_running_) break; if (did_work) continue; // This will either sleep or process an event. NS_ProcessNextEvent(thisThread, true); } mDelayedWorkTimer->Cancel(); keep_running_ = true; } void MessagePump::ScheduleWork() { // Make sure the event loop wakes up. if (mEventTarget) { mEventTarget->Dispatch(mDoWorkEvent, NS_DISPATCH_NORMAL); } else { // Some things (like xpcshell) don't use the app shell and so Run hasn't // been called. We still need to wake up the main thread. NS_DispatchToMainThread(mDoWorkEvent); } event_.Signal(); } void MessagePump::ScheduleWorkForNestedLoop() { // This method is called when our MessageLoop has just allowed // nested tasks. In our setup, whenever that happens we know that // DoWork() will be called "soon", so there's no need to pay the // cost of what will be a no-op nsThread::Dispatch(mDoWorkEvent). } void MessagePump::ScheduleDelayedWork(const base::TimeTicks& aDelayedTime) { // To avoid racing on mDelayedWorkTimer, we need to be on the same thread as // ::Run(). MOZ_RELEASE_ASSERT((!mEventTarget && NS_IsMainThread()) || mEventTarget->IsOnCurrentThread()); if (!mDelayedWorkTimer) { mDelayedWorkTimer = NS_NewTimer(); if (!mDelayedWorkTimer) { // Called before XPCOM has started up? We can't do this correctly. NS_WARNING("Delayed task might not run!"); delayed_work_time_ = aDelayedTime; return; } } if (!delayed_work_time_.is_null()) { mDelayedWorkTimer->Cancel(); } delayed_work_time_ = aDelayedTime; // TimeDelta's constructor initializes to 0 base::TimeDelta delay; if (aDelayedTime > base::TimeTicks::Now()) delay = aDelayedTime - base::TimeTicks::Now(); uint32_t delayMS = uint32_t(delay.InMilliseconds()); mDelayedWorkTimer->InitWithCallback(mDoWorkEvent, delayMS, nsITimer::TYPE_ONE_SHOT); } nsISerialEventTarget* MessagePump::GetXPCOMThread() { if (mEventTarget) { return mEventTarget; } // Main thread return GetMainThreadSerialEventTarget(); } void MessagePump::DoDelayedWork(base::MessagePump::Delegate* aDelegate) { aDelegate->DoDelayedWork(&delayed_work_time_); if (!delayed_work_time_.is_null()) { ScheduleDelayedWork(delayed_work_time_); } } NS_IMPL_ISUPPORTS_INHERITED(DoWorkRunnable, CancelableRunnable, nsITimerCallback) NS_IMETHODIMP DoWorkRunnable::Run() { MessageLoop* loop = MessageLoop::current(); MOZ_ASSERT(loop); bool nestableTasksAllowed = loop->NestableTasksAllowed(); // MessageLoop::RunTask() disallows nesting, but our Frankenventloop will // always dispatch DoWork() below from what looks to MessageLoop like a nested // context. So we unconditionally allow nesting here. loop->SetNestableTasksAllowed(true); loop->DoWork(); loop->SetNestableTasksAllowed(nestableTasksAllowed); return NS_OK; } NS_IMETHODIMP DoWorkRunnable::Notify(nsITimer* aTimer) { MessageLoop* loop = MessageLoop::current(); MOZ_ASSERT(loop); bool nestableTasksAllowed = loop->NestableTasksAllowed(); loop->SetNestableTasksAllowed(true); mPump->DoDelayedWork(loop); loop->SetNestableTasksAllowed(nestableTasksAllowed); return NS_OK; } nsresult DoWorkRunnable::Cancel() { // Workers require cancelable runnables, but we can't really cancel cleanly // here. If we don't process this runnable then we will leave something // unprocessed in the message_loop. Therefore, eagerly complete our work // instead by immediately calling Run(). Run() should be called separately // after this. Unfortunately we cannot use flags to verify this because // DoWorkRunnable is a stateless singleton that can be in the event queue // multiple times simultaneously. MOZ_ALWAYS_SUCCEEDS(Run()); return NS_OK; } void MessagePumpForChildProcess::Run(base::MessagePump::Delegate* aDelegate) { if (mFirstRun) { MOZ_ASSERT(aDelegate && !gFirstDelegate); #ifdef DEBUG gFirstDelegate = aDelegate; #endif mFirstRun = false; if (NS_FAILED(XRE_RunAppShell())) { NS_WARNING("Failed to run app shell?!"); } MOZ_ASSERT(aDelegate && aDelegate == gFirstDelegate); #ifdef DEBUG gFirstDelegate = nullptr; #endif return; } MOZ_ASSERT(aDelegate && aDelegate == gFirstDelegate); // We can get to this point in startup with Tasks in our loop's // incoming_queue_ or pending_queue_, but without a matching // DoWorkRunnable(). In MessagePump::Run() above, we sensitively // depend on *not* directly calling delegate->DoWork(), because that // prioritizes Tasks above XPCOM events. However, from this point // forward, any Task posted to our loop is guaranteed to have a // DoWorkRunnable enqueued for it. // // So we just flush the pending work here and move on. MessageLoop* loop = MessageLoop::current(); bool nestableTasksAllowed = loop->NestableTasksAllowed(); loop->SetNestableTasksAllowed(true); while (aDelegate->DoWork()) ; loop->SetNestableTasksAllowed(nestableTasksAllowed); // Really run. mozilla::ipc::MessagePump::Run(aDelegate); } void MessagePumpForNonMainThreads::Run(base::MessagePump::Delegate* aDelegate) { MOZ_ASSERT(keep_running_); MOZ_RELEASE_ASSERT(!NS_IsMainThread(), "Use mozilla::ipc::MessagePump instead!"); nsIThread* thread = NS_GetCurrentThread(); MOZ_RELEASE_ASSERT(mEventTarget->IsOnCurrentThread()); mDelayedWorkTimer = NS_NewTimer(mEventTarget); MOZ_ASSERT(mDelayedWorkTimer); // Chromium event notifications to be processed will be received by this // event loop as a DoWorkRunnables via ScheduleWork. Chromium events that // were received before our thread is valid, however, will not generate // runnable wrappers. We must process any of these before we enter this // loop, or we will forever have unprocessed chromium messages in our queue. // // Note we would like to request a flush of the chromium event queue // using a runnable on the xpcom side, but some thread implementations // (dom workers) get cranky if we call ScheduleWork here (ScheduleWork // calls dispatch on mEventTarget) before the thread processes an event. As // such, clear the queue manually. while (aDelegate->DoWork()) { } base::ScopedNSAutoreleasePool autoReleasePool; for (;;) { autoReleasePool.Recycle(); bool didWork = NS_ProcessNextEvent(thread, false) ? true : false; if (!keep_running_) { break; } didWork |= aDelegate->DoDelayedWork(&delayed_work_time_); if (didWork && delayed_work_time_.is_null()) { mDelayedWorkTimer->Cancel(); } if (!keep_running_) { break; } if (didWork) { continue; } DebugOnly didIdleWork = aDelegate->DoIdleWork(); MOZ_ASSERT(!didIdleWork); if (!keep_running_) { break; } if (didWork) { continue; } // This will either sleep or process an event. NS_ProcessNextEvent(thread, true); } mDelayedWorkTimer->Cancel(); keep_running_ = true; }