summaryrefslogtreecommitdiffstats
path: root/ipc/glue/MessagePump.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--ipc/glue/MessagePump.cpp336
1 files changed, 336 insertions, 0 deletions
diff --git a/ipc/glue/MessagePump.cpp b/ipc/glue/MessagePump.cpp
new file mode 100644
index 0000000000..50c916608c
--- /dev/null
+++ b/ipc/glue/MessagePump.cpp
@@ -0,0 +1,336 @@
+/* -*- 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<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;
+}