diff -r 000000000000 -r 6474c204b198 xpcom/threads/nsThread.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/xpcom/threads/nsThread.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,952 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "nsIProgrammingLanguage.h" +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "pratom.h" +#include "prlog.h" +#include "nsIObserverService.h" +#include "mozilla/HangMonitor.h" +#include "mozilla/IOInterposer.h" +#include "mozilla/Services.h" +#include "nsXPCOMPrivate.h" +#include "mozilla/ChaosMode.h" + +#ifdef XP_LINUX +#include +#include +#include +#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 MOZ_CANARY +# include +# include +# include +# include +# include "nsXULAppAPI.h" +#endif + +#if defined(NS_FUNCTION_TIMER) && defined(_MSC_VER) +#include "nsTimerImpl.h" +#include "nsStackWalk.h" +#endif +#ifdef NS_FUNCTION_TIMER +#include "nsCRT.h" +#endif + +#ifdef MOZ_TASK_TRACER +#include "GeckoTaskTracer.h" +using namespace mozilla::tasktracer; +#endif + +using namespace mozilla; + +#ifdef PR_LOGGING +static PRLogModuleInfo * +GetThreadLog() +{ + static PRLogModuleInfo *sLog; + if (!sLog) + sLog = PR_NewLogModule("nsThread"); + return sLog; +} +#endif +#ifdef LOG +#undef LOG +#endif +#define LOG(args) PR_LOG(GetThreadLog(), PR_LOG_DEBUG, args) + +NS_DECL_CI_INTERFACE_GETTER(nsThread) + +nsIThreadObserver* nsThread::sMainThreadObserver = nullptr; + +//----------------------------------------------------------------------------- +// 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 *count, nsIID ***array) +{ + return NS_CI_INTERFACE_GETTER_NAME(nsThread)(count, array); +} + +NS_IMETHODIMP +nsThreadClassInfo::GetHelperForLanguage(uint32_t lang, nsISupports **result) +{ + *result = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadClassInfo::GetContractID(char **result) +{ + *result = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadClassInfo::GetClassDescription(char **result) +{ + *result = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadClassInfo::GetClassID(nsCID **result) +{ + *result = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadClassInfo::GetImplementationLanguage(uint32_t *result) +{ + *result = nsIProgrammingLanguage::CPLUSPLUS; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadClassInfo::GetFlags(uint32_t *result) +{ + *result = THREADSAFE; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadClassInfo::GetClassIDNoAlloc(nsCID *result) +{ + 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(&sThreadClassInfo); + } else +NS_INTERFACE_MAP_END +NS_IMPL_CI_INTERFACE_GETTER(nsThread, nsIThread, nsIThreadInternal, + nsIEventTarget, nsISupportsPriority) + +//----------------------------------------------------------------------------- + +class nsThreadStartupEvent : public nsRunnable { +public: + nsThreadStartupEvent() + : mMon("nsThreadStartupEvent.mMon") + , mInitialized(false) { + } + + // This method does not return until the thread startup object is in the + // completion state. + void Wait() { + if (mInitialized) // Maybe avoid locking... + return; + 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() { + ReentrantMonitorAutoEnter mon(mMon); + mInitialized = true; + mon.Notify(); + return NS_OK; + } + + ReentrantMonitor mMon; + bool mInitialized; +}; + +//----------------------------------------------------------------------------- + +struct nsThreadShutdownContext { + nsThread *joiningThread; + bool shutdownAck; +}; + +// This event is responsible for notifying nsThread::Shutdown that it is time +// to call PR_JoinThread. +class nsThreadShutdownAckEvent : public nsRunnable { +public: + nsThreadShutdownAckEvent(nsThreadShutdownContext *ctx) + : mShutdownContext(ctx) { + } + NS_IMETHOD Run() { + mShutdownContext->shutdownAck = true; + return NS_OK; + } +private: + nsThreadShutdownContext *mShutdownContext; +}; + +// This event is responsible for setting mShutdownContext +class nsThreadShutdownEvent : public nsRunnable { +public: + nsThreadShutdownEvent(nsThread *thr, nsThreadShutdownContext *ctx) + : mThread(thr), mShutdownContext(ctx) { + } + NS_IMETHOD Run() { + mThread->mShutdownContext = mShutdownContext; + MessageLoop::current()->Quit(); + return NS_OK; + } +private: + nsRefPtr mThread; + nsThreadShutdownContext *mShutdownContext; +}; + +//----------------------------------------------------------------------------- + +static void +SetupCurrentThreadForChaosMode() +{ + if (!ChaosMode::isActive()) { + 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. + PR_SetThreadPriority(PR_GetCurrentThread(), + PRThreadPriority(ChaosMode::randomUint32LessThan(PR_PRIORITY_LAST + 1))); +#endif + +#ifdef HAVE_SCHED_SETAFFINITY + // Force half the threads to CPU 0 so they compete for CPU + if (ChaosMode::randomUint32LessThan(2)) { + cpu_set_t cpus; + CPU_ZERO(&cpus); + CPU_SET(0, &cpus); + sched_setaffinity(0, sizeof(cpus), &cpus); + } +#endif +} + +/*static*/ void +nsThread::ThreadFunc(void *arg) +{ + nsThread *self = static_cast(arg); // strong reference + self->mThread = PR_GetCurrentThread(); + SetupCurrentThreadForChaosMode(); + + // Inform the ThreadManager + nsThreadManager::get()->RegisterCurrentThread(self); + + mozilla::IOInterposer::RegisterCurrentThread(); + + // Wait for and process startup event + nsCOMPtr event; + if (!self->GetEvent(true, getter_AddRefs(event))) { + NS_WARNING("failed waiting for thread startup event"); + return; + } + event->Run(); // unblocks nsThread::Init + event = nullptr; + + { // Scope for MessageLoop. + nsAutoPtr loop( + new MessageLoop(MessageLoop::TYPE_MOZILLA_NONMAINTHREAD)); + + // Now, process incoming events... + loop->Run(); + + // 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. + while (true) { + { + MutexAutoLock lock(self->mLock); + if (!self->mEvents->HasPendingEvent()) { + // 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 + event = new nsThreadShutdownAckEvent(self->mShutdownContext); + self->mShutdownContext->joiningThread->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_CANARY +int sCanaryOutputFD = -1; +#endif + +nsThread::nsThread(MainThreadFlag aMainThread, uint32_t aStackSize) + : mLock("nsThread.mLock") + , mEvents(&mEventsRoot) + , mPriority(PRIORITY_NORMAL) + , mThread(nullptr) + , mRunningEvent(0) + , mStackSize(aStackSize) + , mShutdownContext(nullptr) + , mShutdownRequired(false) + , mEventsAreDoomed(false) + , mIsMainThread(aMainThread) +{ +} + +nsThread::~nsThread() +{ +} + +nsresult +nsThread::Init() +{ + // spawn thread and wait until it is fully setup + nsRefPtr startup = new nsThreadStartupEvent(); + + NS_ADDREF_THIS(); + + mShutdownRequired = true; + + // ThreadFunc is responsible for setting mThread + PRThread *thr = PR_CreateThread(PR_USER_THREAD, ThreadFunc, this, + PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, + PR_JOINABLE_THREAD, mStackSize); + if (!thr) { + 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); + } + + // Wait for thread to call ThreadManager::SetupCurrentThread, which completes + // initialization of ThreadFunc. + startup->Wait(); + return NS_OK; +} + +nsresult +nsThread::InitCurrentThread() +{ + mThread = PR_GetCurrentThread(); + SetupCurrentThreadForChaosMode(); + + nsThreadManager::get()->RegisterCurrentThread(this); + return NS_OK; +} + +nsresult +nsThread::PutEvent(nsIRunnable *event, nsNestedEventTarget *target) +{ + { + MutexAutoLock lock(mLock); + nsChainedEventQueue *queue = target ? target->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; + } + if (!queue->PutEvent(event)) + return NS_ERROR_OUT_OF_MEMORY; + } + + nsCOMPtr obs = GetObserver(); + if (obs) + obs->OnDispatchedEvent(this); + + return NS_OK; +} + +nsresult +nsThread::DispatchInternal(nsIRunnable *event, uint32_t flags, + nsNestedEventTarget *target) +{ + if (NS_WARN_IF(!event)) + return NS_ERROR_INVALID_ARG; + + if (gXPCOMThreadsShutDown && MAIN_THREAD != mIsMainThread && !target) { + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + } + +#ifdef MOZ_TASK_TRACER + nsRefPtr tracedRunnable = CreateTracedRunnable(event); + event = tracedRunnable; +#endif + + if (flags & 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. + + nsRefPtr wrapper = + new nsThreadSyncDispatch(thread, event); + if (!wrapper) + return NS_ERROR_OUT_OF_MEMORY; + nsresult rv = PutEvent(wrapper, target); + // Don't wait for the event to finish if we didn't dispatch it... + if (NS_FAILED(rv)) + return rv; + + // Allows waiting; ensure no locks are held that would deadlock us! + while (wrapper->IsPending()) + NS_ProcessNextEvent(thread, true); + return wrapper->Result(); + } + + NS_ASSERTION(flags == NS_DISPATCH_NORMAL, "unexpected dispatch flags"); + return PutEvent(event, target); +} + +//----------------------------------------------------------------------------- +// nsIEventTarget + +NS_IMETHODIMP +nsThread::Dispatch(nsIRunnable *event, uint32_t flags) +{ + LOG(("THRD(%p) Dispatch [%p %x]\n", this, event, flags)); + + return DispatchInternal(event, flags, nullptr); +} + +NS_IMETHODIMP +nsThread::IsOnCurrentThread(bool *result) +{ + *result = (PR_GetCurrentThread() == mThread); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsIThread + +NS_IMETHODIMP +nsThread::GetPRThread(PRThread **result) +{ + *result = mThread; + return NS_OK; +} + +NS_IMETHODIMP +nsThread::Shutdown() +{ + LOG(("THRD(%p) 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; + + if (NS_WARN_IF(mThread == PR_GetCurrentThread())) + return NS_ERROR_UNEXPECTED; + + // Prevent multiple calls to this method + { + MutexAutoLock lock(mLock); + if (!mShutdownRequired) + return NS_ERROR_UNEXPECTED; + mShutdownRequired = false; + } + + nsThreadShutdownContext context; + context.joiningThread = nsThreadManager::get()->GetCurrentThread(); + context.shutdownAck = false; + + // Set mShutdownContext and wake up the thread in case it is waiting for + // events to process. + nsCOMPtr event = new nsThreadShutdownEvent(this, &context); + if (!event) + return NS_ERROR_OUT_OF_MEMORY; + // XXXroc What if posting the event fails due to OOM? + PutEvent(event, 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. + + // 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.shutdownAck) + NS_ProcessNextEvent(context.joiningThread, true); + + // 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 + + return NS_OK; +} + +NS_IMETHODIMP +nsThread::HasPendingEvents(bool *result) +{ + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) + return NS_ERROR_NOT_SAME_THREAD; + + *result = mEvents->GetEvent(false, nullptr); + 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_GetProcessType() == GeckoProcessType_Default; + } +}; + +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, 2>::ForwardIterator \ + iter_(mEventObservers); \ + nsCOMPtr obs_; \ + while (iter_.HasMore()) { \ + obs_ = iter_.GetNext(); \ + obs_ -> func_ params_ ; \ + } \ + } \ + PR_END_MACRO + +NS_IMETHODIMP +nsThread::ProcessNextEvent(bool mayWait, bool *result) +{ + LOG(("THRD(%p) ProcessNextEvent [%u %u]\n", this, mayWait, mRunningEvent)); + + 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 = mayWait && (mRunningEvent > 0 || !ShuttingDown()); + + if (MAIN_THREAD == mIsMainThread && reallyWait) + HangMonitor::Suspend(); + + // Fire a memory pressure notification, if we're the main thread and one is + // pending. + if (MAIN_THREAD == mIsMainThread && !ShuttingDown()) { + MemoryPressureState mpPending = NS_GetPendingMemoryPressure(); + if (mpPending != MemPressure_None) { + nsCOMPtr 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!"); + } + } + } + + bool notifyMainThreadObserver = + (MAIN_THREAD == mIsMainThread) && sMainThreadObserver; + if (notifyMainThreadObserver) + sMainThreadObserver->OnProcessNextEvent(this, reallyWait, mRunningEvent); + + nsCOMPtr obs = mObserver; + if (obs) + obs->OnProcessNextEvent(this, reallyWait, mRunningEvent); + + NOTIFY_EVENT_OBSERVERS(OnProcessNextEvent, + (this, reallyWait, mRunningEvent)); + + ++mRunningEvent; + +#ifdef MOZ_CANARY + Canary canary; +#endif + nsresult rv = NS_OK; + + { + // Scope for |event| to make sure that its destructor fires while + // mRunningEvent has been incremented, since that destructor can + // also do work. + + // If we are shutting down, then do not wait for new events. + nsCOMPtr event; + mEvents->GetEvent(reallyWait, getter_AddRefs(event)); + + *result = (event.get() != nullptr); + + if (event) { + LOG(("THRD(%p) running [%p]\n", this, event.get())); + if (MAIN_THREAD == mIsMainThread) + HangMonitor::NotifyActivity(); + event->Run(); + } else if (mayWait) { + MOZ_ASSERT(ShuttingDown(), + "This should only happen when shutting down"); + rv = NS_ERROR_UNEXPECTED; + } + } + + --mRunningEvent; + + NOTIFY_EVENT_OBSERVERS(AfterProcessNextEvent, + (this, mRunningEvent, *result)); + + if (obs) + obs->AfterProcessNextEvent(this, mRunningEvent, *result); + + if (notifyMainThreadObserver && sMainThreadObserver) + sMainThreadObserver->AfterProcessNextEvent(this, mRunningEvent, *result); + + return rv; +} + +//----------------------------------------------------------------------------- +// nsISupportsPriority + +NS_IMETHODIMP +nsThread::GetPriority(int32_t *priority) +{ + *priority = mPriority; + return NS_OK; +} + +NS_IMETHODIMP +nsThread::SetPriority(int32_t priority) +{ + 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 = priority; + + 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()) { + PR_SetThreadPriority(mThread, pri); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsThread::AdjustPriority(int32_t delta) +{ + return SetPriority(mPriority + delta); +} + +//----------------------------------------------------------------------------- +// nsIThreadInternal + +NS_IMETHODIMP +nsThread::GetObserver(nsIThreadObserver **obs) +{ + MutexAutoLock lock(mLock); + NS_IF_ADDREF(*obs = mObserver); + return NS_OK; +} + +NS_IMETHODIMP +nsThread::SetObserver(nsIThreadObserver *obs) +{ + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) + return NS_ERROR_NOT_SAME_THREAD; + + MutexAutoLock lock(mLock); + mObserver = obs; + return NS_OK; +} + +NS_IMETHODIMP +nsThread::GetRecursionDepth(uint32_t *depth) +{ + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) + return NS_ERROR_NOT_SAME_THREAD; + + *depth = mRunningEvent; + return NS_OK; +} + +NS_IMETHODIMP +nsThread::AddObserver(nsIThreadObserver *observer) +{ + if (NS_WARN_IF(!observer)) + return NS_ERROR_INVALID_ARG; + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) + return NS_ERROR_NOT_SAME_THREAD; + + NS_WARN_IF_FALSE(!mEventObservers.Contains(observer), + "Adding an observer twice!"); + + if (!mEventObservers.AppendElement(observer)) { + NS_WARNING("Out of memory!"); + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsThread::RemoveObserver(nsIThreadObserver *observer) +{ + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) + return NS_ERROR_NOT_SAME_THREAD; + + if (observer && !mEventObservers.RemoveElement(observer)) { + NS_WARNING("Removing an observer that was never added!"); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsThread::PushEventQueue(nsIEventTarget **result) +{ + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) + return NS_ERROR_NOT_SAME_THREAD; + + nsChainedEventQueue *queue = new nsChainedEventQueue(); + queue->mEventTarget = new nsNestedEventTarget(this, queue); + + { + MutexAutoLock lock(mLock); + queue->mNext = mEvents; + mEvents = queue; + } + + NS_ADDREF(*result = queue->mEventTarget); + return NS_OK; +} + +NS_IMETHODIMP +nsThread::PopEventQueue(nsIEventTarget *innermostTarget) +{ + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) + return NS_ERROR_NOT_SAME_THREAD; + + if (NS_WARN_IF(!innermostTarget)) + return NS_ERROR_NULL_POINTER; + + // Don't delete or release anything while holding the lock. + nsAutoPtr queue; + nsRefPtr target; + + { + MutexAutoLock lock(mLock); + + // Make sure we're popping the innermost event target. + if (NS_WARN_IF(mEvents->mEventTarget != innermostTarget)) + return NS_ERROR_UNEXPECTED; + + MOZ_ASSERT(mEvents != &mEventsRoot); + + queue = mEvents; + mEvents = mEvents->mNext; + + nsCOMPtr event; + while (queue->GetEvent(false, getter_AddRefs(event))) + mEvents->PutEvent(event); + + // Don't let the event target post any more events. + queue->mEventTarget.swap(target); + target->mQueue = nullptr; + } + + return NS_OK; +} + +nsresult +nsThread::SetMainThreadObserver(nsIThreadObserver* aObserver) +{ + if (aObserver && nsThread::sMainThreadObserver) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (!NS_IsMainThread()) { + return NS_ERROR_UNEXPECTED; + } + + nsThread::sMainThreadObserver = aObserver; + return NS_OK; +} + +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsThreadSyncDispatch::Run() +{ + if (mSyncTask) { + mResult = mSyncTask->Run(); + mSyncTask = nullptr; + // unblock the origin thread + mOrigin->Dispatch(this, NS_DISPATCH_NORMAL); + } + return NS_OK; +} + +//----------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS(nsThread::nsNestedEventTarget, nsIEventTarget) + +NS_IMETHODIMP +nsThread::nsNestedEventTarget::Dispatch(nsIRunnable *event, uint32_t flags) +{ + LOG(("THRD(%p) Dispatch [%p %x] to nested loop %p\n", mThread.get(), event, + flags, this)); + + return mThread->DispatchInternal(event, flags, this); +} + +NS_IMETHODIMP +nsThread::nsNestedEventTarget::IsOnCurrentThread(bool *result) +{ + return mThread->IsOnCurrentThread(result); +}