diff options
Diffstat (limited to 'ipc/glue/MessagePump.cpp')
-rw-r--r-- | ipc/glue/MessagePump.cpp | 465 |
1 files changed, 465 insertions, 0 deletions
diff --git a/ipc/glue/MessagePump.cpp b/ipc/glue/MessagePump.cpp new file mode 100644 index 000000000..15c17b8f4 --- /dev/null +++ b/ipc/glue/MessagePump.cpp @@ -0,0 +1,465 @@ +/* -*- 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 "nsIRunnable.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; + +NS_DEFINE_NAMED_CID(NS_TIMER_CID); + +#ifdef DEBUG +static MessagePump::Delegate* gFirstDelegate; +#endif + +namespace mozilla { +namespace ipc { + +class DoWorkRunnable final : public CancelableRunnable, + public nsITimerCallback +{ +public: + explicit DoWorkRunnable(MessagePump* aPump) + : mPump(aPump) + { + MOZ_ASSERT(aPump); + } + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIRUNNABLE + NS_DECL_NSITIMERCALLBACK + nsresult Cancel() override; + +private: + ~DoWorkRunnable() + { } + + MessagePump* mPump; + // DoWorkRunnable is designed as a stateless singleton. Do not add stateful + // members here! +}; + +} /* namespace ipc */ +} /* namespace mozilla */ + +MessagePump::MessagePump(nsIThread* aThread) +: mThread(aThread) +{ + mDoWorkEvent = new DoWorkRunnable(this); +} + +MessagePump::~MessagePump() +{ +} + +void +MessagePump::Run(MessagePump::Delegate* aDelegate) +{ + MOZ_ASSERT(keep_running_); + MOZ_RELEASE_ASSERT(NS_IsMainThread(), + "Use mozilla::ipc::MessagePumpForNonMainThreads instead!"); + MOZ_RELEASE_ASSERT(!mThread); + + nsIThread* thisThread = NS_GetCurrentThread(); + MOZ_ASSERT(thisThread); + + mDelayedWorkTimer = do_CreateInstance(kNS_TIMER_CID); + 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 (mThread) { + mThread->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(NS_GetCurrentThread() == mThread || + (!mThread && NS_IsMainThread())); + + if (!mDelayedWorkTimer) { + mDelayedWorkTimer = do_CreateInstance(kNS_TIMER_CID); + 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); +} + +nsIEventTarget* +MessagePump::GetXPCOMThread() +{ + if (mThread) { + return mThread; + } + + // Main thread + nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); + return mainThread; +} + +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(mThread == thread); + + mDelayedWorkTimer = do_CreateInstance(kNS_TIMER_CID); + MOZ_ASSERT(mDelayedWorkTimer); + + if (NS_FAILED(mDelayedWorkTimer->SetTarget(thread))) { + MOZ_CRASH("Failed to set timer target!"); + } + + // 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 mThread) 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<bool> 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; +} + +#if defined(XP_WIN) + +NS_IMPL_QUERY_INTERFACE(MessagePumpForNonMainUIThreads, nsIThreadObserver) + +#define CHECK_QUIT_STATE { if (state_->should_quit) { break; } } + +void +MessagePumpForNonMainUIThreads::DoRunLoop() +{ + MOZ_RELEASE_ASSERT(!NS_IsMainThread(), "Use mozilla::ipc::MessagePump instead!"); + + // If this is a chromium thread and no nsThread is associated + // with it, this call will create a new nsThread. + nsIThread* thread = NS_GetCurrentThread(); + MOZ_ASSERT(thread); + + // Set the main thread observer so we can wake up when + // xpcom events need to get processed. + nsCOMPtr<nsIThreadInternal> ti(do_QueryInterface(thread)); + MOZ_ASSERT(ti); + ti->SetObserver(this); + + base::ScopedNSAutoreleasePool autoReleasePool; + for (;;) { + autoReleasePool.Recycle(); + + bool didWork = NS_ProcessNextEvent(thread, false); + + didWork |= ProcessNextWindowsMessage(); + CHECK_QUIT_STATE + + didWork |= state_->delegate->DoWork(); + CHECK_QUIT_STATE + + didWork |= state_->delegate->DoDelayedWork(&delayed_work_time_); + if (didWork && delayed_work_time_.is_null()) { + KillTimer(message_hwnd_, reinterpret_cast<UINT_PTR>(this)); + } + CHECK_QUIT_STATE + + if (didWork) { + continue; + } + + DebugOnly<bool> didIdleWork = state_->delegate->DoIdleWork(); + MOZ_ASSERT(!didIdleWork); + CHECK_QUIT_STATE + + SetInWait(); + bool hasWork = NS_HasPendingEvents(thread); + if (didWork || hasWork) { + ClearInWait(); + continue; + } + WaitForWork(); // Calls MsgWaitForMultipleObjectsEx(QS_ALLINPUT) + ClearInWait(); + } + + ClearInWait(); + + ti->SetObserver(nullptr); +} + +NS_IMETHODIMP +MessagePumpForNonMainUIThreads::OnDispatchedEvent(nsIThreadInternal *thread) +{ + // If our thread is sleeping in DoRunLoop's call to WaitForWork() and an + // event posts to the nsIThread event queue - break our thread out of + // chromium's WaitForWork. + if (GetInWait()) { + ScheduleWork(); + } + return NS_OK; +} + +NS_IMETHODIMP +MessagePumpForNonMainUIThreads::OnProcessNextEvent(nsIThreadInternal *thread, + bool mayWait) +{ + return NS_OK; +} + +NS_IMETHODIMP +MessagePumpForNonMainUIThreads::AfterProcessNextEvent(nsIThreadInternal *thread, + bool eventWasProcessed) +{ + return NS_OK; +} + +#endif // XP_WIN |