gecko-dev/xpcom/threads/nsThread.cpp

1877 строки
55 KiB
C++

/* -*- 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/SchedulerGroup.h"
#include "mozilla/Services.h"
#include "nsXPCOMPrivate.h"
#include "mozilla/ChaosMode.h"
#include "mozilla/Telemetry.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/ScriptSettings.h"
#include "nsIIdlePeriod.h"
#include "nsIIdleRunnable.h"
#include "nsThreadSyncDispatch.h"
#include "LeakRefPtr.h"
#include "GeckoProfiler.h"
#include "InputEventStatistics.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)
Array<char, nsThread::kRunnableNameBufSize> nsThread::sMainThreadRunnableName;
//-----------------------------------------------------------------------------
// 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(nsISerialEventTarget)
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()
: Runnable("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)
: mozilla::Runnable("DelayedRunnable")
, 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*> MOZ_UNSAFE_REF("Thread manager is holding reference to joining thread")
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)
: CancelableRunnable("nsThreadShutdownAckEvent")
, 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)
: Runnable("nsThreadShutdownEvent")
, 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);
}
}
namespace {
struct ThreadInitData {
nsThread* thread;
const nsACString& name;
};
}
/*static*/ void
nsThread::ThreadFunc(void* aArg)
{
using mozilla::ipc::BackgroundChild;
char stackTop;
ThreadInitData* initData = static_cast<ThreadInitData*>(aArg);
nsThread* self = initData->thread; // strong reference
self->mThread = PR_GetCurrentThread();
self->mVirtualThread = GetCurrentVirtualThread();
SetupCurrentThreadForChaosMode();
if (!initData->name.IsEmpty()) {
NS_SetCurrentThreadName(initData->name.BeginReading());
}
// Inform the ThreadManager
nsThreadManager::get().RegisterCurrentThread(*self);
mozilla::IOInterposer::RegisterCurrentThread();
// This must come after the call to nsThreadManager::RegisterCurrentThread(),
// because that call is needed to properly set up this thread as an nsThread,
// which profiler_register_thread() requires. See bug 1347007.
if (!initData->name.IsEmpty()) {
profiler_register_thread(initData->name.BeginReading(), &stackTop);
}
// Wait for and process startup event
nsCOMPtr<nsIRunnable> event;
{
MutexAutoLock lock(self->mLock);
if (!self->mEvents->GetEvent(true, getter_AddRefs(event), nullptr, lock)) {
NS_WARNING("failed waiting for thread startup event");
return;
}
}
initData = nullptr; // clear before unblocking nsThread::Init
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);
profiler_unregister_thread();
// 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)
, mLastUnlabeledRunnable(TimeStamp::Now())
, mCanInvokeJS(false)
, mHasPendingEventsPromisedIdleEvent(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(const nsACString& aName)
{
// spawn thread and wait until it is fully setup
RefPtr<nsThreadStartupEvent> startup = new nsThreadStartupEvent();
NS_ADDREF_THIS();
mIdlePeriod = new IdlePeriod();
mShutdownRequired = true;
ThreadInitData initData = { this, aName };
// ThreadFunc is responsible for setting mThread
if (!PR_CreateThread(PR_USER_THREAD, ThreadFunc, &initData,
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();
mVirtualThread = GetCurrentVirtualThread();
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();
}
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!
SpinEventLoopUntil([&, wrapper]() -> bool {
return !wrapper->IsPending();
}, thread);
return NS_OK;
}
NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL ||
aFlags == NS_DISPATCH_AT_END, "unexpected dispatch flags");
return PutEvent(event.take(), aTarget);
}
NS_IMPL_ISUPPORTS(nsThread::nsChainedEventQueue::EnablePrioritizationRunnable,
nsIRunnable)
void
nsThread::nsChainedEventQueue::EnablePrioritization(MutexAutoLock& aProofOfLock)
{
MOZ_ASSERT(!mIsInputPrioritizationEnabled);
// When enabling event prioritization, there may be some pending events with
// different priorities in the normal queue. Create an event in the normal
// queue to consume all pending events in the time order to make sure we won't
// preempt a pending event (e.g. input) in the normal queue by another newly
// created event with the same priority.
mNormalQueue->PutEvent(new EnablePrioritizationRunnable(this), aProofOfLock);
mInputHandlingStartTime = TimeStamp();
mIsInputPrioritizationEnabled = true;
}
bool
nsThread::nsChainedEventQueue::
GetNormalOrInputOrHighPriorityEvent(bool aMayWait, nsIRunnable** aEvent,
unsigned short* aPriority,
MutexAutoLock& aProofOfLock)
{
bool retVal = false;
do {
// Use mProcessHighPriorityQueueRunnable to prevent the high priority events
// from consuming all cpu time and causing starvation.
if (mProcessHighPriorityQueueRunnable) {
MOZ_ASSERT(mHighQueue->HasPendingEvent(aProofOfLock));
retVal = mHighQueue->GetEvent(false, aEvent, aProofOfLock);
MOZ_ASSERT(*aEvent);
SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_HIGH);
mInputHandlingStartTime = TimeStamp();
mProcessHighPriorityQueueRunnable = false;
return retVal;
}
mProcessHighPriorityQueueRunnable =
mHighQueue->HasPendingEvent(aProofOfLock);
uint32_t pendingInputCount = mInputQueue->Count(aProofOfLock);
if (pendingInputCount > 0) {
if (mInputHandlingStartTime.IsNull()) {
mInputHandlingStartTime =
InputEventStatistics::Get()
.GetInputHandlingStartTime(mInputQueue->Count(aProofOfLock));
}
if (TimeStamp::Now() > mInputHandlingStartTime) {
retVal = mInputQueue->GetEvent(false, aEvent, aProofOfLock);
MOZ_ASSERT(*aEvent);
SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_INPUT);
return retVal;
}
}
// We don't want to wait if there are some high priority events or input
// events in the queues.
bool reallyMayWait = aMayWait && !mProcessHighPriorityQueueRunnable &&
pendingInputCount == 0;
retVal = mNormalQueue->GetEvent(reallyMayWait, aEvent, aProofOfLock);
if (*aEvent) {
// We got an event, return early.
SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_NORMAL);
return retVal;
}
if (pendingInputCount > 0 && !mProcessHighPriorityQueueRunnable) {
// Handle input events if we have time for them.
MOZ_ASSERT(mInputQueue->HasPendingEvent(aProofOfLock));
retVal = mInputQueue->GetEvent(false, aEvent, aProofOfLock);
MOZ_ASSERT(*aEvent);
SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_INPUT);
return retVal;
}
} while (aMayWait || mProcessHighPriorityQueueRunnable);
return retVal;
}
bool
nsThread::nsChainedEventQueue::
GetNormalOrHighPriorityEvent(bool aMayWait, nsIRunnable** aEvent,
unsigned short* aPriority,
MutexAutoLock& aProofOfLock)
{
bool retVal = false;
do {
// Use mProcessHighPriorityQueueRunnable to prevent the high priority events
// from consuming all cpu time and causing starvation.
if (mProcessHighPriorityQueueRunnable) {
MOZ_ASSERT(mHighQueue->HasPendingEvent(aProofOfLock));
retVal = mHighQueue->GetEvent(false, aEvent, aProofOfLock);
MOZ_ASSERT(*aEvent);
SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_HIGH);
mProcessHighPriorityQueueRunnable = false;
return retVal;
}
mProcessHighPriorityQueueRunnable =
mHighQueue->HasPendingEvent(aProofOfLock);
// We don't want to wait if there are some events in the high priority
// queue.
bool reallyMayWait = aMayWait && !mProcessHighPriorityQueueRunnable;
retVal = mNormalQueue->GetEvent(reallyMayWait, aEvent, aProofOfLock);
if (*aEvent) {
// We got an event, return early.
SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_NORMAL);
return retVal;
}
} while (aMayWait || mProcessHighPriorityQueueRunnable);
return retVal;
}
void
nsThread::nsChainedEventQueue::PutEvent(already_AddRefed<nsIRunnable> aEvent,
MutexAutoLock& aProofOfLock)
{
RefPtr<nsIRunnable> event(aEvent);
nsCOMPtr<nsIRunnablePriority> runnablePrio(do_QueryInterface(event));
uint32_t prio = nsIRunnablePriority::PRIORITY_NORMAL;
if (runnablePrio) {
runnablePrio->GetPriority(&prio);
}
switch (prio) {
case nsIRunnablePriority::PRIORITY_NORMAL:
mNormalQueue->PutEvent(event.forget(), aProofOfLock);
break;
case nsIRunnablePriority::PRIORITY_INPUT:
if (mIsInputPrioritizationEnabled) {
mInputQueue->PutEvent(event.forget(), aProofOfLock);
} else {
mNormalQueue->PutEvent(event.forget(), aProofOfLock);
}
break;
case nsIRunnablePriority::PRIORITY_HIGH:
mHighQueue->PutEvent(event.forget(), aProofOfLock);
break;
default:
MOZ_ASSERT(false);
break;
}
}
//-----------------------------------------------------------------------------
// 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;
}
NS_IMETHODIMP_(bool)
nsThread::IsOnCurrentThreadInfallible()
{
// Rely on mVirtualThread being correct.
MOZ_CRASH("IsOnCurrentThreadInfallible should never be called on nsIThread");
}
//-----------------------------------------------------------------------------
// 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()
{
// This is the motivating example for why SpinEventLoop has the template
// parameter we are providing here.
SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>([&]() {
return mRequestedShutdownContexts.IsEmpty();
}, this);
}
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!
SpinEventLoopUntil([&, context]() {
return !context->mAwaitingShutdownAck;
}, context->mJoiningThread);
ShutdownComplete(context);
return NS_OK;
}
TimeStamp
nsThread::GetIdleDeadline()
{
// If we are shutting down, we won't honor the idle period, and we will
// always process idle runnables. This will ensure that the idle queue
// gets exhausted at shutdown time to prevent intermittently leaking
// some runnables inside that queue and even worse potentially leaving
// some important cleanup work unfinished.
// Note that we need to check both of these conditions since ShuttingDown()
// will never return true on the main thread, where gXPCOMThreadsShutDown
// performs a similar function.
if (gXPCOMThreadsShutDown || ShuttingDown()) {
return TimeStamp::Now();
}
TimeStamp idleDeadline;
{
// Releasing the lock temporarily since getting the idle period
// might need to lock the timer thread. Unlocking here might make
// us receive an event on the main queue, but we've committed to
// run an idle event anyhow.
MutexAutoUnlock unlock(mLock);
mIdlePeriod->GetIdlePeriodHint(&idleDeadline);
}
// If HasPendingEvents() has been called and it has returned true because of
// pending idle events, there is a risk that we may decide here that we aren't
// idle and return null, in which case HasPendingEvents() has effectively
// lied. Since we can't go back and fix the past, we have to adjust what we
// do here and forcefully pick the idle queue task here. Note that this means
// that we are choosing to run a task from the idle queue when we would
// normally decide that we aren't in an idle period, but this can only happen
// if we fall out of the idle period in between the call to HasPendingEvents()
// and here, which should hopefully be quite rare. We are effectively
// choosing to prioritize the sanity of our API semantics over the optimal
// scheduling.
if (!mHasPendingEventsPromisedIdleEvent &&
(!idleDeadline || idleDeadline < TimeStamp::Now())) {
return TimeStamp();
}
if (mHasPendingEventsPromisedIdleEvent && !idleDeadline) {
// If HasPendingEvents() has been called and it has returned true, but we're no
// longer in the idle period, we must return a valid timestamp to pretend that
// we are still in the idle period.
return TimeStamp::Now();
}
return idleDeadline;
}
NS_IMETHODIMP
nsThread::HasPendingEvents(bool* aResult)
{
if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
return NS_ERROR_NOT_SAME_THREAD;
}
{
MutexAutoLock lock(mLock);
mHasPendingEventsPromisedIdleEvent = false;
bool hasPendingEvent = mEvents->HasPendingEvent(lock);
bool hasPendingIdleEvent = false;
if (!hasPendingEvent) {
// Note that GetIdleDeadline() checks mHasPendingEventsPromisedIdleEvent,
// but that's OK since we set it to false in the beginning of this method!
TimeStamp idleDeadline = GetIdleDeadline();
// Only examine the idle queue if we are in an idle period.
if (idleDeadline) {
hasPendingIdleEvent = mIdleEvents.HasPendingEvent(lock);
mHasPendingEventsPromisedIdleEvent = hasPendingIdleEvent;
}
}
*aResult = hasPendingEvent || hasPendingIdleEvent;
}
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;
}
NS_IMETHODIMP
nsThread::EnableEventPrioritization()
{
MOZ_ASSERT(NS_IsMainThread());
MutexAutoLock lock(mLock);
// Only support event prioritization for main event queue.
mEventsRoot.EnablePrioritization(lock);
return NS_OK;
}
NS_IMETHODIMP
nsThread::IsEventPrioritizationEnabled(bool* aResult)
{
MOZ_ASSERT(NS_IsMainThread());
*aResult = mEventsRoot.IsPrioritizationEnabled();
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_) \
do { \
if (!mEventObservers.IsEmpty()) { \
nsAutoTObserverArray<NotNull<nsCOMPtr<nsIThreadObserver>>, 2>::ForwardIterator \
iter_(mEventObservers); \
nsCOMPtr<nsIThreadObserver> obs_; \
while (iter_.HasMore()) { \
obs_ = iter_.GetNext(); \
obs_ -> func_ params_ ; \
} \
} \
} while(0)
void
nsThread::GetIdleEvent(nsIRunnable** aEvent, MutexAutoLock& aProofOfLock)
{
MOZ_ASSERT(PR_GetCurrentThread() == mThread);
MOZ_ASSERT(aEvent);
if (!mIdleEvents.HasPendingEvent(aProofOfLock)) {
MOZ_ASSERT(!mHasPendingEventsPromisedIdleEvent);
aEvent = nullptr;
return;
}
TimeStamp idleDeadline = GetIdleDeadline();
if (!idleDeadline) {
aEvent = nullptr;
return;
}
mIdleEvents.GetEvent(false, aEvent, aProofOfLock);
if (*aEvent) {
nsCOMPtr<nsIIdleRunnable> idleEvent(do_QueryInterface(*aEvent));
if (idleEvent) {
idleEvent->SetDeadline(idleDeadline);
}
#ifndef RELEASE_OR_BETA
// Store the next idle deadline to be able to determine budget use
// in ProcessNextEvent.
mNextIdleDeadline = idleDeadline;
#endif
}
}
void
nsThread::GetEvent(bool aWait, nsIRunnable** aEvent,
unsigned short* aPriority,
MutexAutoLock& aProofOfLock)
{
MOZ_ASSERT(PR_GetCurrentThread() == mThread);
MOZ_ASSERT(aEvent);
MakeScopeExit([&] {
mHasPendingEventsPromisedIdleEvent = false;
});
#ifndef RELEASE_OR_BETA
// Clear mNextIdleDeadline so that it is possible to determine that
// we're running an idle runnable in ProcessNextEvent.
mNextIdleDeadline = TimeStamp();
#endif
// 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, aPriority, 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);
if (*aEvent && aPriority) {
// Idle events count as normal priority.
*aPriority = nsIRunnablePriority::PRIORITY_NORMAL;
}
}
// [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, aPriority, aProofOfLock);
}
}
#ifndef RELEASE_OR_BETA
static bool
GetLabeledRunnableName(nsIRunnable* aEvent, nsACString& aName)
{
bool labeled = false;
if (RefPtr<SchedulerGroup::Runnable> groupRunnable = do_QueryObject(aEvent)) {
labeled = true;
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(groupRunnable->GetName(aName)));
} else if (nsCOMPtr<nsINamed> named = do_QueryInterface(aEvent)) {
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(named->GetName(aName)));
} else {
aName.AssignLiteral("non-nsINamed runnable");
}
if (aName.IsEmpty()) {
aName.AssignLiteral("anonymous runnable");
}
return labeled;
}
#endif
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());
Maybe<SchedulerGroup::AutoProcessEvent> ape;
if (mIsMainThread == MAIN_THREAD) {
DoMainThreadSpecificProcessing(reallyWait);
ape.emplace();
}
++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;
unsigned short priority;
{
MutexAutoLock lock(mLock);
GetEvent(reallyWait, getter_AddRefs(event), &priority, lock);
}
*aResult = (event.get() != nullptr);
if (event) {
LOG(("THRD(%p) running [%p]\n", this, event.get()));
if (MAIN_THREAD == mIsMainThread) {
HangMonitor::NotifyActivity();
}
#ifndef RELEASE_OR_BETA
Maybe<Telemetry::AutoTimer<Telemetry::MAIN_THREAD_RUNNABLE_MS>> timer;
Maybe<Telemetry::AutoTimer<Telemetry::IDLE_RUNNABLE_BUDGET_OVERUSE_MS>> idleTimer;
nsAutoCString name;
if ((MAIN_THREAD == mIsMainThread) || mNextIdleDeadline) {
bool labeled = GetLabeledRunnableName(event, name);
if (MAIN_THREAD == mIsMainThread) {
timer.emplace(name);
// High-priority runnables are ignored here since they'll run right away
// even with the cooperative scheduler.
if (!labeled && priority == nsIRunnablePriority::PRIORITY_NORMAL) {
TimeStamp now = TimeStamp::Now();
double diff = (now - mLastUnlabeledRunnable).ToMilliseconds();
Telemetry::Accumulate(Telemetry::TIME_BETWEEN_UNLABELED_RUNNABLES_MS, diff);
mLastUnlabeledRunnable = now;
}
}
if (mNextIdleDeadline) {
// If we construct the AutoTimer with the deadline, then we'll
// compute TimeStamp::Now() - mNextIdleDeadline when
// accumulating telemetry. If that is positive we've
// overdrawn our idle budget, if it's negative it will go in
// the 0 bucket of the histogram.
idleTimer.emplace(name, mNextIdleDeadline);
}
}
// If we're on the main thread, we want to record our current runnable's
// name in a static so that BHR can record it.
Array<char, kRunnableNameBufSize> restoreRunnableName;
restoreRunnableName[0] = '\0';
auto clear = MakeScopeExit([&] {
if (MAIN_THREAD == mIsMainThread) {
MOZ_ASSERT(NS_IsMainThread());
sMainThreadRunnableName = restoreRunnableName;
}
});
if (MAIN_THREAD == mIsMainThread) {
MOZ_ASSERT(NS_IsMainThread());
restoreRunnableName = sMainThreadRunnableName;
// Copy the name into sMainThreadRunnableName's buffer, and append a
// terminating null.
uint32_t length = std::min((uint32_t) kRunnableNameBufSize - 1,
(uint32_t) name.Length());
memcpy(sMainThreadRunnableName.begin(), name.BeginReading(), length);
sMainThreadRunnableName[length] = '\0';
}
#endif
Maybe<AutoTimeDurationHelper> timeDurationHelper;
if (priority == nsIRunnablePriority::PRIORITY_INPUT) {
timeDurationHelper.emplace();
}
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), nullptr, 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();
if (os) {
// Use no-forward to prevent the notifications from being transferred to
// the children of this process.
os->NotifyObservers(nullptr, "memory-pressure",
mpPending == MemPressure_New ? u"low-memory-no-forward" :
u"low-memory-ongoing-no-forward");
} else {
NS_WARNING("Can't get observer service!");
}
}
}
#ifdef MOZ_CRASHREPORTER
if (!ShuttingDown()) {
SaveMemoryReportNearOOM(ShouldSaveMemoryReport::kMaybeReport);
}
#endif
}
NS_IMETHODIMP
nsThread::GetEventTarget(nsIEventTarget** aEventTarget)
{
nsCOMPtr<nsIEventTarget> target = this;
target.forget(aEventTarget);
return NS_OK;
}
nsIEventTarget*
nsThread::EventTarget()
{
return this;
}
nsISerialEventTarget*
nsThread::SerialEventTarget()
{
return this;
}
//-----------------------------------------------------------------------------
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);
}
NS_IMETHODIMP_(bool)
nsThread::nsNestedEventTarget::IsOnCurrentThreadInfallible()
{
return mThread->IsOnCurrentThread();
}