summaryrefslogtreecommitdiffstats
path: root/xpcom/threads/nsThread.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xpcom/threads/nsThread.cpp')
-rw-r--r--xpcom/threads/nsThread.cpp1500
1 files changed, 1500 insertions, 0 deletions
diff --git a/xpcom/threads/nsThread.cpp b/xpcom/threads/nsThread.cpp
new file mode 100644
index 000000000..63bd28ca3
--- /dev/null
+++ b/xpcom/threads/nsThread.cpp
@@ -0,0 +1,1500 @@
+/* -*- 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 "nsThread.h"
+
+#include "base/message_loop.h"
+
+// Chromium's logging can sometimes leak through...
+#ifdef LOG
+#undef LOG
+#endif
+
+#include "mozilla/ReentrantMonitor.h"
+#include "nsMemoryPressure.h"
+#include "nsThreadManager.h"
+#include "nsIClassInfoImpl.h"
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsQueryObject.h"
+#include "pratom.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/Logging.h"
+#include "nsIObserverService.h"
+#include "mozilla/HangMonitor.h"
+#include "mozilla/IOInterposer.h"
+#include "mozilla/ipc/MessageChannel.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/Services.h"
+#include "nsXPCOMPrivate.h"
+#include "mozilla/ChaosMode.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "nsIIdlePeriod.h"
+#include "nsIIncrementalRunnable.h"
+#include "nsThreadSyncDispatch.h"
+#include "LeakRefPtr.h"
+
+#ifdef MOZ_CRASHREPORTER
+#include "nsServiceManagerUtils.h"
+#include "nsICrashReporter.h"
+#include "mozilla/dom/ContentChild.h"
+#endif
+
+#ifdef XP_LINUX
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sched.h>
+#endif
+
+#define HAVE_UALARM _BSD_SOURCE || (_XOPEN_SOURCE >= 500 || \
+ _XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED) && \
+ !(_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700)
+
+#if defined(XP_LINUX) && !defined(ANDROID) && defined(_GNU_SOURCE)
+#define HAVE_SCHED_SETAFFINITY
+#endif
+
+#ifdef XP_MACOSX
+#include <mach/mach.h>
+#include <mach/thread_policy.h>
+#endif
+
+#ifdef MOZ_CANARY
+# include <unistd.h>
+# include <execinfo.h>
+# include <signal.h>
+# include <fcntl.h>
+# include "nsXULAppAPI.h"
+#endif
+
+#if defined(NS_FUNCTION_TIMER) && defined(_MSC_VER)
+#include "nsTimerImpl.h"
+#include "mozilla/StackWalk.h"
+#endif
+#ifdef NS_FUNCTION_TIMER
+#include "nsCRT.h"
+#endif
+
+#ifdef MOZ_TASK_TRACER
+#include "GeckoTaskTracer.h"
+#include "TracedTaskCommon.h"
+using namespace mozilla::tasktracer;
+#endif
+
+using namespace mozilla;
+
+static LazyLogModule sThreadLog("nsThread");
+#ifdef LOG
+#undef LOG
+#endif
+#define LOG(args) MOZ_LOG(sThreadLog, mozilla::LogLevel::Debug, args)
+
+NS_DECL_CI_INTERFACE_GETTER(nsThread)
+
+//-----------------------------------------------------------------------------
+// Because we do not have our own nsIFactory, we have to implement nsIClassInfo
+// somewhat manually.
+
+class nsThreadClassInfo : public nsIClassInfo
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED // no mRefCnt
+ NS_DECL_NSICLASSINFO
+
+ nsThreadClassInfo()
+ {
+ }
+};
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsThreadClassInfo::AddRef()
+{
+ return 2;
+}
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsThreadClassInfo::Release()
+{
+ return 1;
+}
+NS_IMPL_QUERY_INTERFACE(nsThreadClassInfo, nsIClassInfo)
+
+NS_IMETHODIMP
+nsThreadClassInfo::GetInterfaces(uint32_t* aCount, nsIID*** aArray)
+{
+ return NS_CI_INTERFACE_GETTER_NAME(nsThread)(aCount, aArray);
+}
+
+NS_IMETHODIMP
+nsThreadClassInfo::GetScriptableHelper(nsIXPCScriptable** aResult)
+{
+ *aResult = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadClassInfo::GetContractID(char** aResult)
+{
+ *aResult = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadClassInfo::GetClassDescription(char** aResult)
+{
+ *aResult = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadClassInfo::GetClassID(nsCID** aResult)
+{
+ *aResult = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadClassInfo::GetFlags(uint32_t* aResult)
+{
+ *aResult = THREADSAFE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadClassInfo::GetClassIDNoAlloc(nsCID* aResult)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(nsThread)
+NS_IMPL_RELEASE(nsThread)
+NS_INTERFACE_MAP_BEGIN(nsThread)
+ NS_INTERFACE_MAP_ENTRY(nsIThread)
+ NS_INTERFACE_MAP_ENTRY(nsIThreadInternal)
+ NS_INTERFACE_MAP_ENTRY(nsIEventTarget)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIThread)
+ if (aIID.Equals(NS_GET_IID(nsIClassInfo))) {
+ static nsThreadClassInfo sThreadClassInfo;
+ foundInterface = static_cast<nsIClassInfo*>(&sThreadClassInfo);
+ } else
+NS_INTERFACE_MAP_END
+NS_IMPL_CI_INTERFACE_GETTER(nsThread, nsIThread, nsIThreadInternal,
+ nsIEventTarget, nsISupportsPriority)
+
+//-----------------------------------------------------------------------------
+
+class nsThreadStartupEvent : public Runnable
+{
+public:
+ nsThreadStartupEvent()
+ : mMon("nsThreadStartupEvent.mMon")
+ , mInitialized(false)
+ {
+ }
+
+ // This method does not return until the thread startup object is in the
+ // completion state.
+ void Wait()
+ {
+ ReentrantMonitorAutoEnter mon(mMon);
+ while (!mInitialized) {
+ mon.Wait();
+ }
+ }
+
+ // This method needs to be public to support older compilers (xlC_r on AIX).
+ // It should be called directly as this class type is reference counted.
+ virtual ~nsThreadStartupEvent() {}
+
+private:
+ NS_IMETHOD Run() override
+ {
+ ReentrantMonitorAutoEnter mon(mMon);
+ mInitialized = true;
+ mon.Notify();
+ return NS_OK;
+ }
+
+ ReentrantMonitor mMon;
+ bool mInitialized;
+};
+//-----------------------------------------------------------------------------
+
+namespace {
+class DelayedRunnable : public Runnable,
+ public nsITimerCallback
+{
+public:
+ DelayedRunnable(already_AddRefed<nsIThread> aTargetThread,
+ already_AddRefed<nsIRunnable> aRunnable,
+ uint32_t aDelay)
+ : mTargetThread(aTargetThread),
+ mWrappedRunnable(aRunnable),
+ mDelayedFrom(TimeStamp::NowLoRes()),
+ mDelay(aDelay)
+ { }
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsresult Init()
+ {
+ nsresult rv;
+ mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MOZ_ASSERT(mTimer);
+ rv = mTimer->SetTarget(mTargetThread);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mTimer->InitWithCallback(this, mDelay, nsITimer::TYPE_ONE_SHOT);
+ }
+
+ nsresult DoRun()
+ {
+ nsCOMPtr<nsIRunnable> r = mWrappedRunnable.forget();
+ return r->Run();
+ }
+
+ NS_IMETHOD Run() override
+ {
+ // Already ran?
+ if (!mWrappedRunnable) {
+ return NS_OK;
+ }
+
+ // Are we too early?
+ if ((TimeStamp::NowLoRes() - mDelayedFrom).ToMilliseconds() < mDelay) {
+ return NS_OK; // Let the nsITimer run us.
+ }
+
+ mTimer->Cancel();
+ return DoRun();
+ }
+
+ NS_IMETHOD Notify(nsITimer* aTimer) override
+ {
+ // If we already ran, the timer should have been canceled.
+ MOZ_ASSERT(mWrappedRunnable);
+ MOZ_ASSERT(aTimer == mTimer);
+
+ return DoRun();
+ }
+
+private:
+ ~DelayedRunnable() {}
+
+ nsCOMPtr<nsIThread> mTargetThread;
+ nsCOMPtr<nsIRunnable> mWrappedRunnable;
+ nsCOMPtr<nsITimer> mTimer;
+ TimeStamp mDelayedFrom;
+ uint32_t mDelay;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(DelayedRunnable, Runnable, nsITimerCallback)
+
+} // anonymous namespace
+
+//-----------------------------------------------------------------------------
+
+struct nsThreadShutdownContext
+{
+ nsThreadShutdownContext(NotNull<nsThread*> aTerminatingThread,
+ NotNull<nsThread*> aJoiningThread,
+ bool aAwaitingShutdownAck)
+ : mTerminatingThread(aTerminatingThread)
+ , mJoiningThread(aJoiningThread)
+ , mAwaitingShutdownAck(aAwaitingShutdownAck)
+ {
+ MOZ_COUNT_CTOR(nsThreadShutdownContext);
+ }
+ ~nsThreadShutdownContext()
+ {
+ MOZ_COUNT_DTOR(nsThreadShutdownContext);
+ }
+
+ // NB: This will be the last reference.
+ NotNull<RefPtr<nsThread>> mTerminatingThread;
+ NotNull<nsThread*> mJoiningThread;
+ bool mAwaitingShutdownAck;
+};
+
+// This event is responsible for notifying nsThread::Shutdown that it is time
+// to call PR_JoinThread. It implements nsICancelableRunnable so that it can
+// run on a DOM Worker thread (where all events must implement
+// nsICancelableRunnable.)
+class nsThreadShutdownAckEvent : public CancelableRunnable
+{
+public:
+ explicit nsThreadShutdownAckEvent(NotNull<nsThreadShutdownContext*> aCtx)
+ : mShutdownContext(aCtx)
+ {
+ }
+ NS_IMETHOD Run() override
+ {
+ mShutdownContext->mTerminatingThread->ShutdownComplete(mShutdownContext);
+ return NS_OK;
+ }
+ nsresult Cancel() override
+ {
+ return Run();
+ }
+private:
+ virtual ~nsThreadShutdownAckEvent() { }
+
+ NotNull<nsThreadShutdownContext*> mShutdownContext;
+};
+
+// This event is responsible for setting mShutdownContext
+class nsThreadShutdownEvent : public Runnable
+{
+public:
+ nsThreadShutdownEvent(NotNull<nsThread*> aThr,
+ NotNull<nsThreadShutdownContext*> aCtx)
+ : mThread(aThr)
+ , mShutdownContext(aCtx)
+ {
+ }
+ NS_IMETHOD Run() override
+ {
+ mThread->mShutdownContext = mShutdownContext;
+ MessageLoop::current()->Quit();
+ return NS_OK;
+ }
+private:
+ NotNull<RefPtr<nsThread>> mThread;
+ NotNull<nsThreadShutdownContext*> mShutdownContext;
+};
+
+//-----------------------------------------------------------------------------
+
+static void
+SetThreadAffinity(unsigned int cpu)
+{
+#ifdef HAVE_SCHED_SETAFFINITY
+ cpu_set_t cpus;
+ CPU_ZERO(&cpus);
+ CPU_SET(cpu, &cpus);
+ sched_setaffinity(0, sizeof(cpus), &cpus);
+ // Don't assert sched_setaffinity's return value because it intermittently (?)
+ // fails with EINVAL on Linux x64 try runs.
+#elif defined(XP_MACOSX)
+ // OS X does not provide APIs to pin threads to specific processors, but you
+ // can tag threads as belonging to the same "affinity set" and the OS will try
+ // to run them on the same processor. To run threads on different processors,
+ // tag them as belonging to different affinity sets. Tag 0, the default, means
+ // "no affinity" so let's pretend each CPU has its own tag `cpu+1`.
+ thread_affinity_policy_data_t policy;
+ policy.affinity_tag = cpu + 1;
+ MOZ_ALWAYS_TRUE(thread_policy_set(mach_thread_self(), THREAD_AFFINITY_POLICY,
+ &policy.affinity_tag, 1) == KERN_SUCCESS);
+#elif defined(XP_WIN)
+ MOZ_ALWAYS_TRUE(SetThreadIdealProcessor(GetCurrentThread(), cpu) != -1);
+#endif
+}
+
+static void
+SetupCurrentThreadForChaosMode()
+{
+ if (!ChaosMode::isActive(ChaosFeature::ThreadScheduling)) {
+ return;
+ }
+
+#ifdef XP_LINUX
+ // PR_SetThreadPriority doesn't really work since priorities >
+ // PR_PRIORITY_NORMAL can't be set by non-root users. Instead we'll just use
+ // setpriority(2) to set random 'nice values'. In regular Linux this is only
+ // a dynamic adjustment so it still doesn't really do what we want, but tools
+ // like 'rr' can be more aggressive about honoring these values.
+ // Some of these calls may fail due to trying to lower the priority
+ // (e.g. something may have already called setpriority() for this thread).
+ // This makes it hard to have non-main threads with higher priority than the
+ // main thread, but that's hard to fix. Tools like rr can choose to honor the
+ // requested values anyway.
+ // Use just 4 priorities so there's a reasonable chance of any two threads
+ // having equal priority.
+ setpriority(PRIO_PROCESS, 0, ChaosMode::randomUint32LessThan(4));
+#else
+ // We should set the affinity here but NSPR doesn't provide a way to expose it.
+ uint32_t priority = ChaosMode::randomUint32LessThan(PR_PRIORITY_LAST + 1);
+ PR_SetThreadPriority(PR_GetCurrentThread(), PRThreadPriority(priority));
+#endif
+
+ // Force half the threads to CPU 0 so they compete for CPU
+ if (ChaosMode::randomUint32LessThan(2)) {
+ SetThreadAffinity(0);
+ }
+}
+
+/*static*/ void
+nsThread::ThreadFunc(void* aArg)
+{
+ using mozilla::ipc::BackgroundChild;
+
+ nsThread* self = static_cast<nsThread*>(aArg); // strong reference
+ self->mThread = PR_GetCurrentThread();
+ SetupCurrentThreadForChaosMode();
+
+ // Inform the ThreadManager
+ nsThreadManager::get().RegisterCurrentThread(*self);
+
+ mozilla::IOInterposer::RegisterCurrentThread();
+
+ // Wait for and process startup event
+ nsCOMPtr<nsIRunnable> event;
+ {
+ MutexAutoLock lock(self->mLock);
+ if (!self->mEvents->GetEvent(true, getter_AddRefs(event), lock)) {
+ NS_WARNING("failed waiting for thread startup event");
+ return;
+ }
+ }
+ event->Run(); // unblocks nsThread::Init
+ event = nullptr;
+
+ {
+ // Scope for MessageLoop.
+ nsAutoPtr<MessageLoop> loop(
+ new MessageLoop(MessageLoop::TYPE_MOZILLA_NONMAINTHREAD, self));
+
+ // Now, process incoming events...
+ loop->Run();
+
+ BackgroundChild::CloseForCurrentThread();
+
+ // NB: The main thread does not shut down here! It shuts down via
+ // nsThreadManager::Shutdown.
+
+ // Do NS_ProcessPendingEvents but with special handling to set
+ // mEventsAreDoomed atomically with the removal of the last event. The key
+ // invariant here is that we will never permit PutEvent to succeed if the
+ // event would be left in the queue after our final call to
+ // NS_ProcessPendingEvents. We also have to keep processing events as long
+ // as we have outstanding mRequestedShutdownContexts.
+ while (true) {
+ // Check and see if we're waiting on any threads.
+ self->WaitForAllAsynchronousShutdowns();
+
+ {
+ MutexAutoLock lock(self->mLock);
+ if (!self->mEvents->HasPendingEvent(lock)) {
+ // No events in the queue, so we will stop now. Don't let any more
+ // events be added, since they won't be processed. It is critical
+ // that no PutEvent can occur between testing that the event queue is
+ // empty and setting mEventsAreDoomed!
+ self->mEventsAreDoomed = true;
+ break;
+ }
+ }
+ NS_ProcessPendingEvents(self);
+ }
+ }
+
+ mozilla::IOInterposer::UnregisterCurrentThread();
+
+ // Inform the threadmanager that this thread is going away
+ nsThreadManager::get().UnregisterCurrentThread(*self);
+
+ // Dispatch shutdown ACK
+ NotNull<nsThreadShutdownContext*> context =
+ WrapNotNull(self->mShutdownContext);
+ MOZ_ASSERT(context->mTerminatingThread == self);
+ event = do_QueryObject(new nsThreadShutdownAckEvent(context));
+ context->mJoiningThread->Dispatch(event, NS_DISPATCH_NORMAL);
+
+ // Release any observer of the thread here.
+ self->SetObserver(nullptr);
+
+#ifdef MOZ_TASK_TRACER
+ FreeTraceInfo();
+#endif
+
+ NS_RELEASE(self);
+}
+
+//-----------------------------------------------------------------------------
+
+#ifdef MOZ_CRASHREPORTER
+// Tell the crash reporter to save a memory report if our heuristics determine
+// that an OOM failure is likely to occur soon.
+// Memory usage will not be checked more than every 30 seconds or saved more
+// than every 3 minutes
+// If |aShouldSave == kForceReport|, a report will be saved regardless of
+// whether the process is low on memory or not. However, it will still not be
+// saved if a report was saved less than 3 minutes ago.
+bool
+nsThread::SaveMemoryReportNearOOM(ShouldSaveMemoryReport aShouldSave)
+{
+ // Keep an eye on memory usage (cheap, ~7ms) somewhat frequently,
+ // but save memory reports (expensive, ~75ms) less frequently.
+ const size_t kLowMemoryCheckSeconds = 30;
+ const size_t kLowMemorySaveSeconds = 3 * 60;
+
+ static TimeStamp nextCheck = TimeStamp::NowLoRes()
+ + TimeDuration::FromSeconds(kLowMemoryCheckSeconds);
+ static bool recentlySavedReport = false; // Keeps track of whether a report
+ // was saved last time we checked
+
+ // Are we checking again too soon?
+ TimeStamp now = TimeStamp::NowLoRes();
+ if ((aShouldSave == ShouldSaveMemoryReport::kMaybeReport ||
+ recentlySavedReport) && now < nextCheck) {
+ return false;
+ }
+
+ bool needMemoryReport = (aShouldSave == ShouldSaveMemoryReport::kForceReport);
+#ifdef XP_WIN // XXX implement on other platforms as needed
+ // If the report is forced there is no need to check whether it is necessary
+ if (aShouldSave != ShouldSaveMemoryReport::kForceReport) {
+ const size_t LOWMEM_THRESHOLD_VIRTUAL = 200 * 1024 * 1024;
+ MEMORYSTATUSEX statex;
+ statex.dwLength = sizeof(statex);
+ if (GlobalMemoryStatusEx(&statex)) {
+ if (statex.ullAvailVirtual < LOWMEM_THRESHOLD_VIRTUAL) {
+ needMemoryReport = true;
+ }
+ }
+ }
+#endif
+
+ if (needMemoryReport) {
+ if (XRE_IsContentProcess()) {
+ dom::ContentChild* cc = dom::ContentChild::GetSingleton();
+ if (cc) {
+ cc->SendNotifyLowMemory();
+ }
+ } else {
+ nsCOMPtr<nsICrashReporter> cr =
+ do_GetService("@mozilla.org/toolkit/crash-reporter;1");
+ if (cr) {
+ cr->SaveMemoryReport();
+ }
+ }
+ recentlySavedReport = true;
+ nextCheck = now + TimeDuration::FromSeconds(kLowMemorySaveSeconds);
+ } else {
+ recentlySavedReport = false;
+ nextCheck = now + TimeDuration::FromSeconds(kLowMemoryCheckSeconds);
+ }
+
+ return recentlySavedReport;
+}
+#endif
+
+#ifdef MOZ_CANARY
+int sCanaryOutputFD = -1;
+#endif
+
+nsThread::nsThread(MainThreadFlag aMainThread, uint32_t aStackSize)
+ : mLock("nsThread.mLock")
+ , mScriptObserver(nullptr)
+ , mEvents(WrapNotNull(&mEventsRoot))
+ , mEventsRoot(mLock)
+ , mIdleEventsAvailable(mLock, "[nsThread.mEventsAvailable]")
+ , mIdleEvents(mIdleEventsAvailable, nsEventQueue::eNormalQueue)
+ , mPriority(PRIORITY_NORMAL)
+ , mThread(nullptr)
+ , mNestedEventLoopDepth(0)
+ , mStackSize(aStackSize)
+ , mShutdownContext(nullptr)
+ , mShutdownRequired(false)
+ , mEventsAreDoomed(false)
+ , mIsMainThread(aMainThread)
+ , mCanInvokeJS(false)
+{
+}
+
+nsThread::~nsThread()
+{
+ NS_ASSERTION(mRequestedShutdownContexts.IsEmpty(),
+ "shouldn't be waiting on other threads to shutdown");
+#ifdef DEBUG
+ // We deliberately leak these so they can be tracked by the leak checker.
+ // If you're having nsThreadShutdownContext leaks, you can set:
+ // XPCOM_MEM_LOG_CLASSES=nsThreadShutdownContext
+ // during a test run and that will at least tell you what thread is
+ // requesting shutdown on another, which can be helpful for diagnosing
+ // the leak.
+ for (size_t i = 0; i < mRequestedShutdownContexts.Length(); ++i) {
+ Unused << mRequestedShutdownContexts[i].forget();
+ }
+#endif
+}
+
+nsresult
+nsThread::Init()
+{
+ // spawn thread and wait until it is fully setup
+ RefPtr<nsThreadStartupEvent> startup = new nsThreadStartupEvent();
+
+ NS_ADDREF_THIS();
+
+ mIdlePeriod = new IdlePeriod();
+
+ mShutdownRequired = true;
+
+ // ThreadFunc is responsible for setting mThread
+ if (!PR_CreateThread(PR_USER_THREAD, ThreadFunc, this,
+ PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
+ PR_JOINABLE_THREAD, mStackSize)) {
+ NS_RELEASE_THIS();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // ThreadFunc will wait for this event to be run before it tries to access
+ // mThread. By delaying insertion of this event into the queue, we ensure
+ // that mThread is set properly.
+ {
+ MutexAutoLock lock(mLock);
+ mEventsRoot.PutEvent(startup, lock); // retain a reference
+ }
+
+ // Wait for thread to call ThreadManager::SetupCurrentThread, which completes
+ // initialization of ThreadFunc.
+ startup->Wait();
+ return NS_OK;
+}
+
+nsresult
+nsThread::InitCurrentThread()
+{
+ mThread = PR_GetCurrentThread();
+ SetupCurrentThreadForChaosMode();
+
+ mIdlePeriod = new IdlePeriod();
+
+ nsThreadManager::get().RegisterCurrentThread(*this);
+ return NS_OK;
+}
+
+nsresult
+nsThread::PutEvent(nsIRunnable* aEvent, nsNestedEventTarget* aTarget)
+{
+ nsCOMPtr<nsIRunnable> event(aEvent);
+ return PutEvent(event.forget(), aTarget);
+}
+
+nsresult
+nsThread::PutEvent(already_AddRefed<nsIRunnable> aEvent, nsNestedEventTarget* aTarget)
+{
+ // We want to leak the reference when we fail to dispatch it, so that
+ // we won't release the event in a wrong thread.
+ LeakRefPtr<nsIRunnable> event(Move(aEvent));
+ nsCOMPtr<nsIThreadObserver> obs;
+
+ {
+ MutexAutoLock lock(mLock);
+ nsChainedEventQueue* queue = aTarget ? aTarget->mQueue : &mEventsRoot;
+ if (!queue || (queue == &mEventsRoot && mEventsAreDoomed)) {
+ NS_WARNING("An event was posted to a thread that will never run it (rejected)");
+ return NS_ERROR_UNEXPECTED;
+ }
+ queue->PutEvent(event.take(), lock);
+
+ // Make sure to grab the observer before dropping the lock, otherwise the
+ // event that we just placed into the queue could run and eventually delete
+ // this nsThread before the calling thread is scheduled again. We would then
+ // crash while trying to access a dead nsThread.
+ obs = mObserver;
+ }
+
+ if (obs) {
+ obs->OnDispatchedEvent(this);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsThread::DispatchInternal(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags,
+ nsNestedEventTarget* aTarget)
+{
+ // We want to leak the reference when we fail to dispatch it, so that
+ // we won't release the event in a wrong thread.
+ LeakRefPtr<nsIRunnable> event(Move(aEvent));
+ if (NS_WARN_IF(!event)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (gXPCOMThreadsShutDown && MAIN_THREAD != mIsMainThread && !aTarget) {
+ NS_ASSERTION(false, "Failed Dispatch after xpcom-shutdown-threads");
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+#ifdef MOZ_TASK_TRACER
+ nsCOMPtr<nsIRunnable> tracedRunnable = CreateTracedRunnable(event.take());
+ (static_cast<TracedRunnable*>(tracedRunnable.get()))->DispatchTask();
+ // XXX tracedRunnable will always leaked when we fail to disptch.
+ event = tracedRunnable.forget();
+#endif
+
+ if (aFlags & DISPATCH_SYNC) {
+ nsThread* thread = nsThreadManager::get().GetCurrentThread();
+ if (NS_WARN_IF(!thread)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // XXX we should be able to do something better here... we should
+ // be able to monitor the slot occupied by this event and use
+ // that to tell us when the event has been processed.
+
+ RefPtr<nsThreadSyncDispatch> wrapper =
+ new nsThreadSyncDispatch(thread, event.take());
+ nsresult rv = PutEvent(wrapper, aTarget); // hold a ref
+ // Don't wait for the event to finish if we didn't dispatch it...
+ if (NS_FAILED(rv)) {
+ // PutEvent leaked the wrapper runnable object on failure, so we
+ // explicitly release this object once for that. Note that this
+ // object will be released again soon because it exits the scope.
+ wrapper.get()->Release();
+ return rv;
+ }
+
+ // Allows waiting; ensure no locks are held that would deadlock us!
+ while (wrapper->IsPending()) {
+ NS_ProcessNextEvent(thread, true);
+ }
+ return NS_OK;
+ }
+
+ NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL ||
+ aFlags == NS_DISPATCH_AT_END, "unexpected dispatch flags");
+ return PutEvent(event.take(), aTarget);
+}
+
+bool
+nsThread::nsChainedEventQueue::GetEvent(bool aMayWait, nsIRunnable** aEvent,
+ mozilla::MutexAutoLock& aProofOfLock)
+{
+ bool retVal = false;
+ do {
+ if (mProcessSecondaryQueueRunnable) {
+ MOZ_ASSERT(mSecondaryQueue->HasPendingEvent(aProofOfLock));
+ retVal = mSecondaryQueue->GetEvent(aMayWait, aEvent, aProofOfLock);
+ MOZ_ASSERT(*aEvent);
+ mProcessSecondaryQueueRunnable = false;
+ return retVal;
+ }
+
+ // We don't want to wait if mSecondaryQueue has some events.
+ bool reallyMayWait =
+ aMayWait && !mSecondaryQueue->HasPendingEvent(aProofOfLock);
+ retVal =
+ mNormalQueue->GetEvent(reallyMayWait, aEvent, aProofOfLock);
+
+ // Let's see if we should next time process an event from the secondary
+ // queue.
+ mProcessSecondaryQueueRunnable =
+ mSecondaryQueue->HasPendingEvent(aProofOfLock);
+
+ if (*aEvent) {
+ // We got an event, return early.
+ return retVal;
+ }
+ } while(aMayWait || mProcessSecondaryQueueRunnable);
+
+ return retVal;
+}
+
+//-----------------------------------------------------------------------------
+// nsIEventTarget
+
+NS_IMETHODIMP
+nsThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags)
+{
+ nsCOMPtr<nsIRunnable> event(aEvent);
+ return Dispatch(event.forget(), aFlags);
+}
+
+NS_IMETHODIMP
+nsThread::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags)
+{
+ LOG(("THRD(%p) Dispatch [%p %x]\n", this, /* XXX aEvent */nullptr, aFlags));
+
+ return DispatchInternal(Move(aEvent), aFlags, nullptr);
+}
+
+NS_IMETHODIMP
+nsThread::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aDelayMs)
+{
+ NS_ENSURE_TRUE(!!aDelayMs, NS_ERROR_UNEXPECTED);
+
+ RefPtr<DelayedRunnable> r = new DelayedRunnable(Move(do_AddRef(this)),
+ Move(aEvent),
+ aDelayMs);
+ nsresult rv = r->Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return DispatchInternal(r.forget(), 0, nullptr);
+}
+
+NS_IMETHODIMP
+nsThread::IsOnCurrentThread(bool* aResult)
+{
+ *aResult = (PR_GetCurrentThread() == mThread);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIThread
+
+NS_IMETHODIMP
+nsThread::GetPRThread(PRThread** aResult)
+{
+ *aResult = mThread;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThread::GetCanInvokeJS(bool* aResult)
+{
+ *aResult = mCanInvokeJS;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThread::SetCanInvokeJS(bool aCanInvokeJS)
+{
+ mCanInvokeJS = aCanInvokeJS;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThread::AsyncShutdown()
+{
+ LOG(("THRD(%p) async shutdown\n", this));
+
+ // XXX If we make this warn, then we hit that warning at xpcom shutdown while
+ // shutting down a thread in a thread pool. That happens b/c the thread
+ // in the thread pool is already shutdown by the thread manager.
+ if (!mThread) {
+ return NS_OK;
+ }
+
+ return !!ShutdownInternal(/* aSync = */ false) ? NS_OK : NS_ERROR_UNEXPECTED;
+}
+
+nsThreadShutdownContext*
+nsThread::ShutdownInternal(bool aSync)
+{
+ MOZ_ASSERT(mThread);
+ MOZ_ASSERT(mThread != PR_GetCurrentThread());
+ if (NS_WARN_IF(mThread == PR_GetCurrentThread())) {
+ return nullptr;
+ }
+
+ // Prevent multiple calls to this method
+ {
+ MutexAutoLock lock(mLock);
+ if (!mShutdownRequired) {
+ return nullptr;
+ }
+ mShutdownRequired = false;
+ }
+
+ NotNull<nsThread*> currentThread =
+ WrapNotNull(nsThreadManager::get().GetCurrentThread());
+
+ nsAutoPtr<nsThreadShutdownContext>& context =
+ *currentThread->mRequestedShutdownContexts.AppendElement();
+ context = new nsThreadShutdownContext(WrapNotNull(this), currentThread, aSync);
+
+ // Set mShutdownContext and wake up the thread in case it is waiting for
+ // events to process.
+ nsCOMPtr<nsIRunnable> event =
+ new nsThreadShutdownEvent(WrapNotNull(this), WrapNotNull(context.get()));
+ // XXXroc What if posting the event fails due to OOM?
+ PutEvent(event.forget(), nullptr);
+
+ // We could still end up with other events being added after the shutdown
+ // task, but that's okay because we process pending events in ThreadFunc
+ // after setting mShutdownContext just before exiting.
+ return context;
+}
+
+void
+nsThread::ShutdownComplete(NotNull<nsThreadShutdownContext*> aContext)
+{
+ MOZ_ASSERT(mThread);
+ MOZ_ASSERT(aContext->mTerminatingThread == this);
+
+ if (aContext->mAwaitingShutdownAck) {
+ // We're in a synchronous shutdown, so tell whatever is up the stack that
+ // we're done and unwind the stack so it can call us again.
+ aContext->mAwaitingShutdownAck = false;
+ return;
+ }
+
+ // Now, it should be safe to join without fear of dead-locking.
+
+ PR_JoinThread(mThread);
+ mThread = nullptr;
+
+ // We hold strong references to our event observers, and once the thread is
+ // shut down the observers can't easily unregister themselves. Do it here
+ // to avoid leaking.
+ ClearObservers();
+
+#ifdef DEBUG
+ {
+ MutexAutoLock lock(mLock);
+ MOZ_ASSERT(!mObserver, "Should have been cleared at shutdown!");
+ }
+#endif
+
+ // Delete aContext.
+ MOZ_ALWAYS_TRUE(
+ aContext->mJoiningThread->mRequestedShutdownContexts.RemoveElement(aContext));
+}
+
+void
+nsThread::WaitForAllAsynchronousShutdowns()
+{
+ while (mRequestedShutdownContexts.Length()) {
+ NS_ProcessNextEvent(this, true);
+ }
+}
+
+NS_IMETHODIMP
+nsThread::Shutdown()
+{
+ LOG(("THRD(%p) sync shutdown\n", this));
+
+ // XXX If we make this warn, then we hit that warning at xpcom shutdown while
+ // shutting down a thread in a thread pool. That happens b/c the thread
+ // in the thread pool is already shutdown by the thread manager.
+ if (!mThread) {
+ return NS_OK;
+ }
+
+ nsThreadShutdownContext* maybeContext = ShutdownInternal(/* aSync = */ true);
+ NS_ENSURE_TRUE(maybeContext, NS_ERROR_UNEXPECTED);
+ NotNull<nsThreadShutdownContext*> context = WrapNotNull(maybeContext);
+
+ // Process events on the current thread until we receive a shutdown ACK.
+ // Allows waiting; ensure no locks are held that would deadlock us!
+ while (context->mAwaitingShutdownAck) {
+ NS_ProcessNextEvent(context->mJoiningThread, true);
+ }
+
+ ShutdownComplete(context);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThread::HasPendingEvents(bool* aResult)
+{
+ if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+
+ {
+ MutexAutoLock lock(mLock);
+ *aResult = mEvents->HasPendingEvent(lock);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThread::RegisterIdlePeriod(already_AddRefed<nsIIdlePeriod> aIdlePeriod)
+{
+ if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+
+ MutexAutoLock lock(mLock);
+ mIdlePeriod = aIdlePeriod;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThread::IdleDispatch(already_AddRefed<nsIRunnable> aEvent)
+{
+ // Currently the only supported idle dispatch is from the same
+ // thread. To support idle dispatch from another thread we need to
+ // support waking threads that are waiting for an event queue that
+ // isn't mIdleEvents.
+ MOZ_ASSERT(PR_GetCurrentThread() == mThread);
+
+ MutexAutoLock lock(mLock);
+ LeakRefPtr<nsIRunnable> event(Move(aEvent));
+
+ if (NS_WARN_IF(!event)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (mEventsAreDoomed) {
+ NS_WARNING("An idle event was posted to a thread that will never run it (rejected)");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mIdleEvents.PutEvent(event.take(), lock);
+ return NS_OK;
+}
+
+#ifdef MOZ_CANARY
+void canary_alarm_handler(int signum);
+
+class Canary
+{
+ //XXX ToDo: support nested loops
+public:
+ Canary()
+ {
+ if (sCanaryOutputFD > 0 && EventLatencyIsImportant()) {
+ signal(SIGALRM, canary_alarm_handler);
+ ualarm(15000, 0);
+ }
+ }
+
+ ~Canary()
+ {
+ if (sCanaryOutputFD != 0 && EventLatencyIsImportant()) {
+ ualarm(0, 0);
+ }
+ }
+
+ static bool EventLatencyIsImportant()
+ {
+ return NS_IsMainThread() && XRE_IsParentProcess();
+ }
+};
+
+void canary_alarm_handler(int signum)
+{
+ void* array[30];
+ const char msg[29] = "event took too long to run:\n";
+ // use write to be safe in the signal handler
+ write(sCanaryOutputFD, msg, sizeof(msg));
+ backtrace_symbols_fd(array, backtrace(array, 30), sCanaryOutputFD);
+}
+
+#endif
+
+#define NOTIFY_EVENT_OBSERVERS(func_, params_) \
+ PR_BEGIN_MACRO \
+ if (!mEventObservers.IsEmpty()) { \
+ nsAutoTObserverArray<NotNull<nsCOMPtr<nsIThreadObserver>>, 2>::ForwardIterator \
+ iter_(mEventObservers); \
+ nsCOMPtr<nsIThreadObserver> obs_; \
+ while (iter_.HasMore()) { \
+ obs_ = iter_.GetNext(); \
+ obs_ -> func_ params_ ; \
+ } \
+ } \
+ PR_END_MACRO
+
+void
+nsThread::GetIdleEvent(nsIRunnable** aEvent, MutexAutoLock& aProofOfLock)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == mThread);
+ MOZ_ASSERT(aEvent);
+
+ TimeStamp idleDeadline;
+ {
+ MutexAutoUnlock unlock(mLock);
+ mIdlePeriod->GetIdlePeriodHint(&idleDeadline);
+ }
+
+ if (!idleDeadline || idleDeadline < TimeStamp::Now()) {
+ aEvent = nullptr;
+ return;
+ }
+
+ mIdleEvents.GetEvent(false, aEvent, aProofOfLock);
+
+ if (*aEvent) {
+ nsCOMPtr<nsIIncrementalRunnable> incrementalEvent(do_QueryInterface(*aEvent));
+ if (incrementalEvent) {
+ incrementalEvent->SetDeadline(idleDeadline);
+ }
+ }
+}
+
+void
+nsThread::GetEvent(bool aWait, nsIRunnable** aEvent, MutexAutoLock& aProofOfLock)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == mThread);
+ MOZ_ASSERT(aEvent);
+
+ // We'll try to get an event to execute in three stages.
+ // [1] First we just try to get it from the regular queue without waiting.
+ mEvents->GetEvent(false, aEvent, aProofOfLock);
+
+ // [2] If we didn't get an event from the regular queue, try to
+ // get one from the idle queue
+ if (!*aEvent) {
+ // Since events in mEvents have higher priority than idle
+ // events, we will only consider idle events when there are no
+ // pending events in mEvents. We will for the same reason never
+ // wait for an idle event, since a higher priority event might
+ // appear at any time.
+ GetIdleEvent(aEvent, aProofOfLock);
+ }
+
+ // [3] If we neither got an event from the regular queue nor the
+ // idle queue, then if we should wait for events we block on the
+ // main queue until an event is available.
+ // If we are shutting down, then do not wait for new events.
+ if (!*aEvent && aWait) {
+ mEvents->GetEvent(aWait, aEvent, aProofOfLock);
+ }
+}
+
+NS_IMETHODIMP
+nsThread::ProcessNextEvent(bool aMayWait, bool* aResult)
+{
+ LOG(("THRD(%p) ProcessNextEvent [%u %u]\n", this, aMayWait,
+ mNestedEventLoopDepth));
+
+ if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+
+ // The toplevel event loop normally blocks waiting for the next event, but
+ // if we're trying to shut this thread down, we must exit the event loop when
+ // the event queue is empty.
+ // This only applys to the toplevel event loop! Nested event loops (e.g.
+ // during sync dispatch) are waiting for some state change and must be able
+ // to block even if something has requested shutdown of the thread. Otherwise
+ // we'll just busywait as we endlessly look for an event, fail to find one,
+ // and repeat the nested event loop since its state change hasn't happened yet.
+ bool reallyWait = aMayWait && (mNestedEventLoopDepth > 0 || !ShuttingDown());
+
+ if (mIsMainThread == MAIN_THREAD) {
+ DoMainThreadSpecificProcessing(reallyWait);
+ }
+
+ ++mNestedEventLoopDepth;
+
+ // We only want to create an AutoNoJSAPI on threads that actually do DOM stuff
+ // (including workers). Those are exactly the threads that have an
+ // mScriptObserver.
+ Maybe<dom::AutoNoJSAPI> noJSAPI;
+ bool callScriptObserver = !!mScriptObserver;
+ if (callScriptObserver) {
+ noJSAPI.emplace();
+ mScriptObserver->BeforeProcessTask(reallyWait);
+ }
+
+ nsCOMPtr<nsIThreadObserver> obs = mObserver;
+ if (obs) {
+ obs->OnProcessNextEvent(this, reallyWait);
+ }
+
+ NOTIFY_EVENT_OBSERVERS(OnProcessNextEvent, (this, reallyWait));
+
+#ifdef MOZ_CANARY
+ Canary canary;
+#endif
+ nsresult rv = NS_OK;
+
+ {
+ // Scope for |event| to make sure that its destructor fires while
+ // mNestedEventLoopDepth has been incremented, since that destructor can
+ // also do work.
+ nsCOMPtr<nsIRunnable> event;
+ {
+ MutexAutoLock lock(mLock);
+ GetEvent(reallyWait, getter_AddRefs(event), lock);
+ }
+
+ *aResult = (event.get() != nullptr);
+
+ if (event) {
+ LOG(("THRD(%p) running [%p]\n", this, event.get()));
+ if (MAIN_THREAD == mIsMainThread) {
+ HangMonitor::NotifyActivity();
+ }
+ event->Run();
+ } else if (aMayWait) {
+ MOZ_ASSERT(ShuttingDown(),
+ "This should only happen when shutting down");
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ NOTIFY_EVENT_OBSERVERS(AfterProcessNextEvent, (this, *aResult));
+
+ if (obs) {
+ obs->AfterProcessNextEvent(this, *aResult);
+ }
+
+ if (callScriptObserver) {
+ if (mScriptObserver) {
+ mScriptObserver->AfterProcessTask(mNestedEventLoopDepth);
+ }
+ noJSAPI.reset();
+ }
+
+ --mNestedEventLoopDepth;
+
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// nsISupportsPriority
+
+NS_IMETHODIMP
+nsThread::GetPriority(int32_t* aPriority)
+{
+ *aPriority = mPriority;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThread::SetPriority(int32_t aPriority)
+{
+ if (NS_WARN_IF(!mThread)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ // NSPR defines the following four thread priorities:
+ // PR_PRIORITY_LOW
+ // PR_PRIORITY_NORMAL
+ // PR_PRIORITY_HIGH
+ // PR_PRIORITY_URGENT
+ // We map the priority values defined on nsISupportsPriority to these values.
+
+ mPriority = aPriority;
+
+ PRThreadPriority pri;
+ if (mPriority <= PRIORITY_HIGHEST) {
+ pri = PR_PRIORITY_URGENT;
+ } else if (mPriority < PRIORITY_NORMAL) {
+ pri = PR_PRIORITY_HIGH;
+ } else if (mPriority > PRIORITY_NORMAL) {
+ pri = PR_PRIORITY_LOW;
+ } else {
+ pri = PR_PRIORITY_NORMAL;
+ }
+ // If chaos mode is active, retain the randomly chosen priority
+ if (!ChaosMode::isActive(ChaosFeature::ThreadScheduling)) {
+ PR_SetThreadPriority(mThread, pri);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThread::AdjustPriority(int32_t aDelta)
+{
+ return SetPriority(mPriority + aDelta);
+}
+
+//-----------------------------------------------------------------------------
+// nsIThreadInternal
+
+NS_IMETHODIMP
+nsThread::GetObserver(nsIThreadObserver** aObs)
+{
+ MutexAutoLock lock(mLock);
+ NS_IF_ADDREF(*aObs = mObserver);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThread::SetObserver(nsIThreadObserver* aObs)
+{
+ if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+
+ MutexAutoLock lock(mLock);
+ mObserver = aObs;
+ return NS_OK;
+}
+
+uint32_t
+nsThread::RecursionDepth() const
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == mThread);
+ return mNestedEventLoopDepth;
+}
+
+NS_IMETHODIMP
+nsThread::AddObserver(nsIThreadObserver* aObserver)
+{
+ if (NS_WARN_IF(!aObserver)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+
+ NS_WARNING_ASSERTION(!mEventObservers.Contains(aObserver),
+ "Adding an observer twice!");
+
+ if (!mEventObservers.AppendElement(WrapNotNull(aObserver))) {
+ NS_WARNING("Out of memory!");
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThread::RemoveObserver(nsIThreadObserver* aObserver)
+{
+ if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+
+ if (aObserver && !mEventObservers.RemoveElement(aObserver)) {
+ NS_WARNING("Removing an observer that was never added!");
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThread::PushEventQueue(nsIEventTarget** aResult)
+{
+ if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+
+ NotNull<nsChainedEventQueue*> queue =
+ WrapNotNull(new nsChainedEventQueue(mLock));
+ queue->mEventTarget = new nsNestedEventTarget(WrapNotNull(this), queue);
+
+ {
+ MutexAutoLock lock(mLock);
+ queue->mNext = mEvents;
+ mEvents = queue;
+ }
+
+ NS_ADDREF(*aResult = queue->mEventTarget);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThread::PopEventQueue(nsIEventTarget* aInnermostTarget)
+{
+ if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+
+ if (NS_WARN_IF(!aInnermostTarget)) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ // Don't delete or release anything while holding the lock.
+ nsAutoPtr<nsChainedEventQueue> queue;
+ RefPtr<nsNestedEventTarget> target;
+
+ {
+ MutexAutoLock lock(mLock);
+
+ // Make sure we're popping the innermost event target.
+ if (NS_WARN_IF(mEvents->mEventTarget != aInnermostTarget)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ MOZ_ASSERT(mEvents != &mEventsRoot);
+
+ queue = mEvents;
+ mEvents = WrapNotNull(mEvents->mNext);
+
+ nsCOMPtr<nsIRunnable> event;
+ while (queue->GetEvent(false, getter_AddRefs(event), lock)) {
+ mEvents->PutEvent(event.forget(), lock);
+ }
+
+ // Don't let the event target post any more events.
+ queue->mEventTarget.swap(target);
+ target->mQueue = nullptr;
+ }
+
+ return NS_OK;
+}
+
+void
+nsThread::SetScriptObserver(mozilla::CycleCollectedJSContext* aScriptObserver)
+{
+ if (!aScriptObserver) {
+ mScriptObserver = nullptr;
+ return;
+ }
+
+ MOZ_ASSERT(!mScriptObserver);
+ mScriptObserver = aScriptObserver;
+}
+
+void
+nsThread::DoMainThreadSpecificProcessing(bool aReallyWait)
+{
+ MOZ_ASSERT(mIsMainThread == MAIN_THREAD);
+
+ ipc::CancelCPOWs();
+
+ if (aReallyWait) {
+ HangMonitor::Suspend();
+ }
+
+ // Fire a memory pressure notification, if one is pending.
+ if (!ShuttingDown()) {
+ MemoryPressureState mpPending = NS_GetPendingMemoryPressure();
+ if (mpPending != MemPressure_None) {
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+
+ // Use no-forward to prevent the notifications from being transferred to
+ // the children of this process.
+ NS_NAMED_LITERAL_STRING(lowMem, "low-memory-no-forward");
+ NS_NAMED_LITERAL_STRING(lowMemOngoing, "low-memory-ongoing-no-forward");
+
+ if (os) {
+ os->NotifyObservers(nullptr, "memory-pressure",
+ mpPending == MemPressure_New ? lowMem.get() :
+ lowMemOngoing.get());
+ } else {
+ NS_WARNING("Can't get observer service!");
+ }
+ }
+ }
+
+#ifdef MOZ_CRASHREPORTER
+ if (!ShuttingDown()) {
+ SaveMemoryReportNearOOM(ShouldSaveMemoryReport::kMaybeReport);
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsThread::nsNestedEventTarget, nsIEventTarget)
+
+NS_IMETHODIMP
+nsThread::nsNestedEventTarget::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags)
+{
+ nsCOMPtr<nsIRunnable> event(aEvent);
+ return Dispatch(event.forget(), aFlags);
+}
+
+NS_IMETHODIMP
+nsThread::nsNestedEventTarget::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags)
+{
+ LOG(("THRD(%p) Dispatch [%p %x] to nested loop %p\n", mThread.get().get(),
+ /*XXX aEvent*/ nullptr, aFlags, this));
+
+ return mThread->DispatchInternal(Move(aEvent), aFlags, this);
+}
+
+NS_IMETHODIMP
+nsThread::nsNestedEventTarget::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsThread::nsNestedEventTarget::IsOnCurrentThread(bool* aResult)
+{
+ return mThread->IsOnCurrentThread(aResult);
+}