michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 sts=2 et cindent: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsThread.h" michael@0: michael@0: #include "base/message_loop.h" michael@0: michael@0: // Chromium's logging can sometimes leak through... michael@0: #ifdef LOG michael@0: #undef LOG michael@0: #endif michael@0: michael@0: #include "mozilla/ReentrantMonitor.h" michael@0: #include "nsMemoryPressure.h" michael@0: #include "nsThreadManager.h" michael@0: #include "nsIClassInfoImpl.h" michael@0: #include "nsIProgrammingLanguage.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "pratom.h" michael@0: #include "prlog.h" michael@0: #include "nsIObserverService.h" michael@0: #include "mozilla/HangMonitor.h" michael@0: #include "mozilla/IOInterposer.h" michael@0: #include "mozilla/Services.h" michael@0: #include "nsXPCOMPrivate.h" michael@0: #include "mozilla/ChaosMode.h" michael@0: michael@0: #ifdef XP_LINUX michael@0: #include michael@0: #include michael@0: #include michael@0: #endif michael@0: michael@0: #define HAVE_UALARM _BSD_SOURCE || (_XOPEN_SOURCE >= 500 || \ michael@0: _XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED) && \ michael@0: !(_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700) michael@0: michael@0: #if defined(XP_LINUX) && !defined(ANDROID) && defined(_GNU_SOURCE) michael@0: #define HAVE_SCHED_SETAFFINITY michael@0: #endif michael@0: michael@0: #ifdef MOZ_CANARY michael@0: # include michael@0: # include michael@0: # include michael@0: # include michael@0: # include "nsXULAppAPI.h" michael@0: #endif michael@0: michael@0: #if defined(NS_FUNCTION_TIMER) && defined(_MSC_VER) michael@0: #include "nsTimerImpl.h" michael@0: #include "nsStackWalk.h" michael@0: #endif michael@0: #ifdef NS_FUNCTION_TIMER michael@0: #include "nsCRT.h" michael@0: #endif michael@0: michael@0: #ifdef MOZ_TASK_TRACER michael@0: #include "GeckoTaskTracer.h" michael@0: using namespace mozilla::tasktracer; michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: michael@0: #ifdef PR_LOGGING michael@0: static PRLogModuleInfo * michael@0: GetThreadLog() michael@0: { michael@0: static PRLogModuleInfo *sLog; michael@0: if (!sLog) michael@0: sLog = PR_NewLogModule("nsThread"); michael@0: return sLog; michael@0: } michael@0: #endif michael@0: #ifdef LOG michael@0: #undef LOG michael@0: #endif michael@0: #define LOG(args) PR_LOG(GetThreadLog(), PR_LOG_DEBUG, args) michael@0: michael@0: NS_DECL_CI_INTERFACE_GETTER(nsThread) michael@0: michael@0: nsIThreadObserver* nsThread::sMainThreadObserver = nullptr; michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // Because we do not have our own nsIFactory, we have to implement nsIClassInfo michael@0: // somewhat manually. michael@0: michael@0: class nsThreadClassInfo : public nsIClassInfo { michael@0: public: michael@0: NS_DECL_ISUPPORTS_INHERITED // no mRefCnt michael@0: NS_DECL_NSICLASSINFO michael@0: michael@0: nsThreadClassInfo() {} michael@0: }; michael@0: michael@0: NS_IMETHODIMP_(MozExternalRefCountType) nsThreadClassInfo::AddRef() { return 2; } michael@0: NS_IMETHODIMP_(MozExternalRefCountType) nsThreadClassInfo::Release() { return 1; } michael@0: NS_IMPL_QUERY_INTERFACE(nsThreadClassInfo, nsIClassInfo) michael@0: michael@0: NS_IMETHODIMP michael@0: nsThreadClassInfo::GetInterfaces(uint32_t *count, nsIID ***array) michael@0: { michael@0: return NS_CI_INTERFACE_GETTER_NAME(nsThread)(count, array); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThreadClassInfo::GetHelperForLanguage(uint32_t lang, nsISupports **result) michael@0: { michael@0: *result = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThreadClassInfo::GetContractID(char **result) michael@0: { michael@0: *result = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThreadClassInfo::GetClassDescription(char **result) michael@0: { michael@0: *result = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThreadClassInfo::GetClassID(nsCID **result) michael@0: { michael@0: *result = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThreadClassInfo::GetImplementationLanguage(uint32_t *result) michael@0: { michael@0: *result = nsIProgrammingLanguage::CPLUSPLUS; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThreadClassInfo::GetFlags(uint32_t *result) michael@0: { michael@0: *result = THREADSAFE; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThreadClassInfo::GetClassIDNoAlloc(nsCID *result) michael@0: { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMPL_ADDREF(nsThread) michael@0: NS_IMPL_RELEASE(nsThread) michael@0: NS_INTERFACE_MAP_BEGIN(nsThread) michael@0: NS_INTERFACE_MAP_ENTRY(nsIThread) michael@0: NS_INTERFACE_MAP_ENTRY(nsIThreadInternal) michael@0: NS_INTERFACE_MAP_ENTRY(nsIEventTarget) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupportsPriority) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIThread) michael@0: if (aIID.Equals(NS_GET_IID(nsIClassInfo))) { michael@0: static nsThreadClassInfo sThreadClassInfo; michael@0: foundInterface = static_cast(&sThreadClassInfo); michael@0: } else michael@0: NS_INTERFACE_MAP_END michael@0: NS_IMPL_CI_INTERFACE_GETTER(nsThread, nsIThread, nsIThreadInternal, michael@0: nsIEventTarget, nsISupportsPriority) michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: class nsThreadStartupEvent : public nsRunnable { michael@0: public: michael@0: nsThreadStartupEvent() michael@0: : mMon("nsThreadStartupEvent.mMon") michael@0: , mInitialized(false) { michael@0: } michael@0: michael@0: // This method does not return until the thread startup object is in the michael@0: // completion state. michael@0: void Wait() { michael@0: if (mInitialized) // Maybe avoid locking... michael@0: return; michael@0: ReentrantMonitorAutoEnter mon(mMon); michael@0: while (!mInitialized) michael@0: mon.Wait(); michael@0: } michael@0: michael@0: // This method needs to be public to support older compilers (xlC_r on AIX). michael@0: // It should be called directly as this class type is reference counted. michael@0: virtual ~nsThreadStartupEvent() { michael@0: } michael@0: michael@0: private: michael@0: NS_IMETHOD Run() { michael@0: ReentrantMonitorAutoEnter mon(mMon); michael@0: mInitialized = true; michael@0: mon.Notify(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: ReentrantMonitor mMon; michael@0: bool mInitialized; michael@0: }; michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: struct nsThreadShutdownContext { michael@0: nsThread *joiningThread; michael@0: bool shutdownAck; michael@0: }; michael@0: michael@0: // This event is responsible for notifying nsThread::Shutdown that it is time michael@0: // to call PR_JoinThread. michael@0: class nsThreadShutdownAckEvent : public nsRunnable { michael@0: public: michael@0: nsThreadShutdownAckEvent(nsThreadShutdownContext *ctx) michael@0: : mShutdownContext(ctx) { michael@0: } michael@0: NS_IMETHOD Run() { michael@0: mShutdownContext->shutdownAck = true; michael@0: return NS_OK; michael@0: } michael@0: private: michael@0: nsThreadShutdownContext *mShutdownContext; michael@0: }; michael@0: michael@0: // This event is responsible for setting mShutdownContext michael@0: class nsThreadShutdownEvent : public nsRunnable { michael@0: public: michael@0: nsThreadShutdownEvent(nsThread *thr, nsThreadShutdownContext *ctx) michael@0: : mThread(thr), mShutdownContext(ctx) { michael@0: } michael@0: NS_IMETHOD Run() { michael@0: mThread->mShutdownContext = mShutdownContext; michael@0: MessageLoop::current()->Quit(); michael@0: return NS_OK; michael@0: } michael@0: private: michael@0: nsRefPtr mThread; michael@0: nsThreadShutdownContext *mShutdownContext; michael@0: }; michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: static void michael@0: SetupCurrentThreadForChaosMode() michael@0: { michael@0: if (!ChaosMode::isActive()) { michael@0: return; michael@0: } michael@0: michael@0: #ifdef XP_LINUX michael@0: // PR_SetThreadPriority doesn't really work since priorities > michael@0: // PR_PRIORITY_NORMAL can't be set by non-root users. Instead we'll just use michael@0: // setpriority(2) to set random 'nice values'. In regular Linux this is only michael@0: // a dynamic adjustment so it still doesn't really do what we want, but tools michael@0: // like 'rr' can be more aggressive about honoring these values. michael@0: // Some of these calls may fail due to trying to lower the priority michael@0: // (e.g. something may have already called setpriority() for this thread). michael@0: // This makes it hard to have non-main threads with higher priority than the michael@0: // main thread, but that's hard to fix. Tools like rr can choose to honor the michael@0: // requested values anyway. michael@0: // Use just 4 priorities so there's a reasonable chance of any two threads michael@0: // having equal priority. michael@0: setpriority(PRIO_PROCESS, 0, ChaosMode::randomUint32LessThan(4)); michael@0: #else michael@0: // We should set the affinity here but NSPR doesn't provide a way to expose it. michael@0: PR_SetThreadPriority(PR_GetCurrentThread(), michael@0: PRThreadPriority(ChaosMode::randomUint32LessThan(PR_PRIORITY_LAST + 1))); michael@0: #endif michael@0: michael@0: #ifdef HAVE_SCHED_SETAFFINITY michael@0: // Force half the threads to CPU 0 so they compete for CPU michael@0: if (ChaosMode::randomUint32LessThan(2)) { michael@0: cpu_set_t cpus; michael@0: CPU_ZERO(&cpus); michael@0: CPU_SET(0, &cpus); michael@0: sched_setaffinity(0, sizeof(cpus), &cpus); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: /*static*/ void michael@0: nsThread::ThreadFunc(void *arg) michael@0: { michael@0: nsThread *self = static_cast(arg); // strong reference michael@0: self->mThread = PR_GetCurrentThread(); michael@0: SetupCurrentThreadForChaosMode(); michael@0: michael@0: // Inform the ThreadManager michael@0: nsThreadManager::get()->RegisterCurrentThread(self); michael@0: michael@0: mozilla::IOInterposer::RegisterCurrentThread(); michael@0: michael@0: // Wait for and process startup event michael@0: nsCOMPtr event; michael@0: if (!self->GetEvent(true, getter_AddRefs(event))) { michael@0: NS_WARNING("failed waiting for thread startup event"); michael@0: return; michael@0: } michael@0: event->Run(); // unblocks nsThread::Init michael@0: event = nullptr; michael@0: michael@0: { // Scope for MessageLoop. michael@0: nsAutoPtr loop( michael@0: new MessageLoop(MessageLoop::TYPE_MOZILLA_NONMAINTHREAD)); michael@0: michael@0: // Now, process incoming events... michael@0: loop->Run(); michael@0: michael@0: // Do NS_ProcessPendingEvents but with special handling to set michael@0: // mEventsAreDoomed atomically with the removal of the last event. The key michael@0: // invariant here is that we will never permit PutEvent to succeed if the michael@0: // event would be left in the queue after our final call to michael@0: // NS_ProcessPendingEvents. michael@0: while (true) { michael@0: { michael@0: MutexAutoLock lock(self->mLock); michael@0: if (!self->mEvents->HasPendingEvent()) { michael@0: // No events in the queue, so we will stop now. Don't let any more michael@0: // events be added, since they won't be processed. It is critical michael@0: // that no PutEvent can occur between testing that the event queue is michael@0: // empty and setting mEventsAreDoomed! michael@0: self->mEventsAreDoomed = true; michael@0: break; michael@0: } michael@0: } michael@0: NS_ProcessPendingEvents(self); michael@0: } michael@0: } michael@0: michael@0: mozilla::IOInterposer::UnregisterCurrentThread(); michael@0: michael@0: // Inform the threadmanager that this thread is going away michael@0: nsThreadManager::get()->UnregisterCurrentThread(self); michael@0: michael@0: // Dispatch shutdown ACK michael@0: event = new nsThreadShutdownAckEvent(self->mShutdownContext); michael@0: self->mShutdownContext->joiningThread->Dispatch(event, NS_DISPATCH_NORMAL); michael@0: michael@0: // Release any observer of the thread here. michael@0: self->SetObserver(nullptr); michael@0: michael@0: #ifdef MOZ_TASK_TRACER michael@0: FreeTraceInfo(); michael@0: #endif michael@0: michael@0: NS_RELEASE(self); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: #ifdef MOZ_CANARY michael@0: int sCanaryOutputFD = -1; michael@0: #endif michael@0: michael@0: nsThread::nsThread(MainThreadFlag aMainThread, uint32_t aStackSize) michael@0: : mLock("nsThread.mLock") michael@0: , mEvents(&mEventsRoot) michael@0: , mPriority(PRIORITY_NORMAL) michael@0: , mThread(nullptr) michael@0: , mRunningEvent(0) michael@0: , mStackSize(aStackSize) michael@0: , mShutdownContext(nullptr) michael@0: , mShutdownRequired(false) michael@0: , mEventsAreDoomed(false) michael@0: , mIsMainThread(aMainThread) michael@0: { michael@0: } michael@0: michael@0: nsThread::~nsThread() michael@0: { michael@0: } michael@0: michael@0: nsresult michael@0: nsThread::Init() michael@0: { michael@0: // spawn thread and wait until it is fully setup michael@0: nsRefPtr startup = new nsThreadStartupEvent(); michael@0: michael@0: NS_ADDREF_THIS(); michael@0: michael@0: mShutdownRequired = true; michael@0: michael@0: // ThreadFunc is responsible for setting mThread michael@0: PRThread *thr = PR_CreateThread(PR_USER_THREAD, ThreadFunc, this, michael@0: PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, michael@0: PR_JOINABLE_THREAD, mStackSize); michael@0: if (!thr) { michael@0: NS_RELEASE_THIS(); michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: // ThreadFunc will wait for this event to be run before it tries to access michael@0: // mThread. By delaying insertion of this event into the queue, we ensure michael@0: // that mThread is set properly. michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: mEventsRoot.PutEvent(startup); michael@0: } michael@0: michael@0: // Wait for thread to call ThreadManager::SetupCurrentThread, which completes michael@0: // initialization of ThreadFunc. michael@0: startup->Wait(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsThread::InitCurrentThread() michael@0: { michael@0: mThread = PR_GetCurrentThread(); michael@0: SetupCurrentThreadForChaosMode(); michael@0: michael@0: nsThreadManager::get()->RegisterCurrentThread(this); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsThread::PutEvent(nsIRunnable *event, nsNestedEventTarget *target) michael@0: { michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: nsChainedEventQueue *queue = target ? target->mQueue : &mEventsRoot; michael@0: if (!queue || (queue == &mEventsRoot && mEventsAreDoomed)) { michael@0: NS_WARNING("An event was posted to a thread that will never run it (rejected)"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: if (!queue->PutEvent(event)) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: nsCOMPtr obs = GetObserver(); michael@0: if (obs) michael@0: obs->OnDispatchedEvent(this); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsThread::DispatchInternal(nsIRunnable *event, uint32_t flags, michael@0: nsNestedEventTarget *target) michael@0: { michael@0: if (NS_WARN_IF(!event)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: if (gXPCOMThreadsShutDown && MAIN_THREAD != mIsMainThread && !target) { michael@0: return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; michael@0: } michael@0: michael@0: #ifdef MOZ_TASK_TRACER michael@0: nsRefPtr tracedRunnable = CreateTracedRunnable(event); michael@0: event = tracedRunnable; michael@0: #endif michael@0: michael@0: if (flags & DISPATCH_SYNC) { michael@0: nsThread *thread = nsThreadManager::get()->GetCurrentThread(); michael@0: if (NS_WARN_IF(!thread)) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: // XXX we should be able to do something better here... we should michael@0: // be able to monitor the slot occupied by this event and use michael@0: // that to tell us when the event has been processed. michael@0: michael@0: nsRefPtr wrapper = michael@0: new nsThreadSyncDispatch(thread, event); michael@0: if (!wrapper) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: nsresult rv = PutEvent(wrapper, target); michael@0: // Don't wait for the event to finish if we didn't dispatch it... michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // Allows waiting; ensure no locks are held that would deadlock us! michael@0: while (wrapper->IsPending()) michael@0: NS_ProcessNextEvent(thread, true); michael@0: return wrapper->Result(); michael@0: } michael@0: michael@0: NS_ASSERTION(flags == NS_DISPATCH_NORMAL, "unexpected dispatch flags"); michael@0: return PutEvent(event, target); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsIEventTarget michael@0: michael@0: NS_IMETHODIMP michael@0: nsThread::Dispatch(nsIRunnable *event, uint32_t flags) michael@0: { michael@0: LOG(("THRD(%p) Dispatch [%p %x]\n", this, event, flags)); michael@0: michael@0: return DispatchInternal(event, flags, nullptr); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThread::IsOnCurrentThread(bool *result) michael@0: { michael@0: *result = (PR_GetCurrentThread() == mThread); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsIThread michael@0: michael@0: NS_IMETHODIMP michael@0: nsThread::GetPRThread(PRThread **result) michael@0: { michael@0: *result = mThread; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThread::Shutdown() michael@0: { michael@0: LOG(("THRD(%p) shutdown\n", this)); michael@0: michael@0: // XXX If we make this warn, then we hit that warning at xpcom shutdown while michael@0: // shutting down a thread in a thread pool. That happens b/c the thread michael@0: // in the thread pool is already shutdown by the thread manager. michael@0: if (!mThread) michael@0: return NS_OK; michael@0: michael@0: if (NS_WARN_IF(mThread == PR_GetCurrentThread())) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: // Prevent multiple calls to this method michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: if (!mShutdownRequired) michael@0: return NS_ERROR_UNEXPECTED; michael@0: mShutdownRequired = false; michael@0: } michael@0: michael@0: nsThreadShutdownContext context; michael@0: context.joiningThread = nsThreadManager::get()->GetCurrentThread(); michael@0: context.shutdownAck = false; michael@0: michael@0: // Set mShutdownContext and wake up the thread in case it is waiting for michael@0: // events to process. michael@0: nsCOMPtr event = new nsThreadShutdownEvent(this, &context); michael@0: if (!event) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: // XXXroc What if posting the event fails due to OOM? michael@0: PutEvent(event, nullptr); michael@0: michael@0: // We could still end up with other events being added after the shutdown michael@0: // task, but that's okay because we process pending events in ThreadFunc michael@0: // after setting mShutdownContext just before exiting. michael@0: michael@0: // Process events on the current thread until we receive a shutdown ACK. michael@0: // Allows waiting; ensure no locks are held that would deadlock us! michael@0: while (!context.shutdownAck) michael@0: NS_ProcessNextEvent(context.joiningThread, true); michael@0: michael@0: // Now, it should be safe to join without fear of dead-locking. michael@0: michael@0: PR_JoinThread(mThread); michael@0: mThread = nullptr; michael@0: michael@0: // We hold strong references to our event observers, and once the thread is michael@0: // shut down the observers can't easily unregister themselves. Do it here michael@0: // to avoid leaking. michael@0: ClearObservers(); michael@0: michael@0: #ifdef DEBUG michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: MOZ_ASSERT(!mObserver, "Should have been cleared at shutdown!"); michael@0: } michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThread::HasPendingEvents(bool *result) michael@0: { michael@0: if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) michael@0: return NS_ERROR_NOT_SAME_THREAD; michael@0: michael@0: *result = mEvents->GetEvent(false, nullptr); michael@0: return NS_OK; michael@0: } michael@0: michael@0: #ifdef MOZ_CANARY michael@0: void canary_alarm_handler (int signum); michael@0: michael@0: class Canary { michael@0: //XXX ToDo: support nested loops michael@0: public: michael@0: Canary() { michael@0: if (sCanaryOutputFD > 0 && EventLatencyIsImportant()) { michael@0: signal(SIGALRM, canary_alarm_handler); michael@0: ualarm(15000, 0); michael@0: } michael@0: } michael@0: michael@0: ~Canary() { michael@0: if (sCanaryOutputFD != 0 && EventLatencyIsImportant()) michael@0: ualarm(0, 0); michael@0: } michael@0: michael@0: static bool EventLatencyIsImportant() { michael@0: return NS_IsMainThread() && XRE_GetProcessType() == GeckoProcessType_Default; michael@0: } michael@0: }; michael@0: michael@0: void canary_alarm_handler (int signum) michael@0: { michael@0: void *array[30]; michael@0: const char msg[29] = "event took too long to run:\n"; michael@0: // use write to be safe in the signal handler michael@0: write(sCanaryOutputFD, msg, sizeof(msg)); michael@0: backtrace_symbols_fd(array, backtrace(array, 30), sCanaryOutputFD); michael@0: } michael@0: michael@0: #endif michael@0: michael@0: #define NOTIFY_EVENT_OBSERVERS(func_, params_) \ michael@0: PR_BEGIN_MACRO \ michael@0: if (!mEventObservers.IsEmpty()) { \ michael@0: nsAutoTObserverArray, 2>::ForwardIterator \ michael@0: iter_(mEventObservers); \ michael@0: nsCOMPtr obs_; \ michael@0: while (iter_.HasMore()) { \ michael@0: obs_ = iter_.GetNext(); \ michael@0: obs_ -> func_ params_ ; \ michael@0: } \ michael@0: } \ michael@0: PR_END_MACRO michael@0: michael@0: NS_IMETHODIMP michael@0: nsThread::ProcessNextEvent(bool mayWait, bool *result) michael@0: { michael@0: LOG(("THRD(%p) ProcessNextEvent [%u %u]\n", this, mayWait, mRunningEvent)); michael@0: michael@0: if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) michael@0: return NS_ERROR_NOT_SAME_THREAD; michael@0: michael@0: // The toplevel event loop normally blocks waiting for the next event, but michael@0: // if we're trying to shut this thread down, we must exit the event loop when michael@0: // the event queue is empty. michael@0: // This only applys to the toplevel event loop! Nested event loops (e.g. michael@0: // during sync dispatch) are waiting for some state change and must be able michael@0: // to block even if something has requested shutdown of the thread. Otherwise michael@0: // we'll just busywait as we endlessly look for an event, fail to find one, michael@0: // and repeat the nested event loop since its state change hasn't happened yet. michael@0: bool reallyWait = mayWait && (mRunningEvent > 0 || !ShuttingDown()); michael@0: michael@0: if (MAIN_THREAD == mIsMainThread && reallyWait) michael@0: HangMonitor::Suspend(); michael@0: michael@0: // Fire a memory pressure notification, if we're the main thread and one is michael@0: // pending. michael@0: if (MAIN_THREAD == mIsMainThread && !ShuttingDown()) { michael@0: MemoryPressureState mpPending = NS_GetPendingMemoryPressure(); michael@0: if (mpPending != MemPressure_None) { michael@0: nsCOMPtr os = services::GetObserverService(); michael@0: michael@0: // Use no-forward to prevent the notifications from being transferred to michael@0: // the children of this process. michael@0: NS_NAMED_LITERAL_STRING(lowMem, "low-memory-no-forward"); michael@0: NS_NAMED_LITERAL_STRING(lowMemOngoing, "low-memory-ongoing-no-forward"); michael@0: michael@0: if (os) { michael@0: os->NotifyObservers(nullptr, "memory-pressure", michael@0: mpPending == MemPressure_New ? lowMem.get() : michael@0: lowMemOngoing.get()); michael@0: } else { michael@0: NS_WARNING("Can't get observer service!"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool notifyMainThreadObserver = michael@0: (MAIN_THREAD == mIsMainThread) && sMainThreadObserver; michael@0: if (notifyMainThreadObserver) michael@0: sMainThreadObserver->OnProcessNextEvent(this, reallyWait, mRunningEvent); michael@0: michael@0: nsCOMPtr obs = mObserver; michael@0: if (obs) michael@0: obs->OnProcessNextEvent(this, reallyWait, mRunningEvent); michael@0: michael@0: NOTIFY_EVENT_OBSERVERS(OnProcessNextEvent, michael@0: (this, reallyWait, mRunningEvent)); michael@0: michael@0: ++mRunningEvent; michael@0: michael@0: #ifdef MOZ_CANARY michael@0: Canary canary; michael@0: #endif michael@0: nsresult rv = NS_OK; michael@0: michael@0: { michael@0: // Scope for |event| to make sure that its destructor fires while michael@0: // mRunningEvent has been incremented, since that destructor can michael@0: // also do work. michael@0: michael@0: // If we are shutting down, then do not wait for new events. michael@0: nsCOMPtr event; michael@0: mEvents->GetEvent(reallyWait, getter_AddRefs(event)); michael@0: michael@0: *result = (event.get() != nullptr); michael@0: michael@0: if (event) { michael@0: LOG(("THRD(%p) running [%p]\n", this, event.get())); michael@0: if (MAIN_THREAD == mIsMainThread) michael@0: HangMonitor::NotifyActivity(); michael@0: event->Run(); michael@0: } else if (mayWait) { michael@0: MOZ_ASSERT(ShuttingDown(), michael@0: "This should only happen when shutting down"); michael@0: rv = NS_ERROR_UNEXPECTED; michael@0: } michael@0: } michael@0: michael@0: --mRunningEvent; michael@0: michael@0: NOTIFY_EVENT_OBSERVERS(AfterProcessNextEvent, michael@0: (this, mRunningEvent, *result)); michael@0: michael@0: if (obs) michael@0: obs->AfterProcessNextEvent(this, mRunningEvent, *result); michael@0: michael@0: if (notifyMainThreadObserver && sMainThreadObserver) michael@0: sMainThreadObserver->AfterProcessNextEvent(this, mRunningEvent, *result); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsISupportsPriority michael@0: michael@0: NS_IMETHODIMP michael@0: nsThread::GetPriority(int32_t *priority) michael@0: { michael@0: *priority = mPriority; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThread::SetPriority(int32_t priority) michael@0: { michael@0: if (NS_WARN_IF(!mThread)) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: // NSPR defines the following four thread priorities: michael@0: // PR_PRIORITY_LOW michael@0: // PR_PRIORITY_NORMAL michael@0: // PR_PRIORITY_HIGH michael@0: // PR_PRIORITY_URGENT michael@0: // We map the priority values defined on nsISupportsPriority to these values. michael@0: michael@0: mPriority = priority; michael@0: michael@0: PRThreadPriority pri; michael@0: if (mPriority <= PRIORITY_HIGHEST) { michael@0: pri = PR_PRIORITY_URGENT; michael@0: } else if (mPriority < PRIORITY_NORMAL) { michael@0: pri = PR_PRIORITY_HIGH; michael@0: } else if (mPriority > PRIORITY_NORMAL) { michael@0: pri = PR_PRIORITY_LOW; michael@0: } else { michael@0: pri = PR_PRIORITY_NORMAL; michael@0: } michael@0: // If chaos mode is active, retain the randomly chosen priority michael@0: if (!ChaosMode::isActive()) { michael@0: PR_SetThreadPriority(mThread, pri); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThread::AdjustPriority(int32_t delta) michael@0: { michael@0: return SetPriority(mPriority + delta); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsIThreadInternal michael@0: michael@0: NS_IMETHODIMP michael@0: nsThread::GetObserver(nsIThreadObserver **obs) michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: NS_IF_ADDREF(*obs = mObserver); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThread::SetObserver(nsIThreadObserver *obs) michael@0: { michael@0: if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) michael@0: return NS_ERROR_NOT_SAME_THREAD; michael@0: michael@0: MutexAutoLock lock(mLock); michael@0: mObserver = obs; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThread::GetRecursionDepth(uint32_t *depth) michael@0: { michael@0: if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) michael@0: return NS_ERROR_NOT_SAME_THREAD; michael@0: michael@0: *depth = mRunningEvent; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThread::AddObserver(nsIThreadObserver *observer) michael@0: { michael@0: if (NS_WARN_IF(!observer)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) michael@0: return NS_ERROR_NOT_SAME_THREAD; michael@0: michael@0: NS_WARN_IF_FALSE(!mEventObservers.Contains(observer), michael@0: "Adding an observer twice!"); michael@0: michael@0: if (!mEventObservers.AppendElement(observer)) { michael@0: NS_WARNING("Out of memory!"); michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThread::RemoveObserver(nsIThreadObserver *observer) michael@0: { michael@0: if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) michael@0: return NS_ERROR_NOT_SAME_THREAD; michael@0: michael@0: if (observer && !mEventObservers.RemoveElement(observer)) { michael@0: NS_WARNING("Removing an observer that was never added!"); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThread::PushEventQueue(nsIEventTarget **result) michael@0: { michael@0: if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) michael@0: return NS_ERROR_NOT_SAME_THREAD; michael@0: michael@0: nsChainedEventQueue *queue = new nsChainedEventQueue(); michael@0: queue->mEventTarget = new nsNestedEventTarget(this, queue); michael@0: michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: queue->mNext = mEvents; michael@0: mEvents = queue; michael@0: } michael@0: michael@0: NS_ADDREF(*result = queue->mEventTarget); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThread::PopEventQueue(nsIEventTarget *innermostTarget) michael@0: { michael@0: if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) michael@0: return NS_ERROR_NOT_SAME_THREAD; michael@0: michael@0: if (NS_WARN_IF(!innermostTarget)) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: // Don't delete or release anything while holding the lock. michael@0: nsAutoPtr queue; michael@0: nsRefPtr target; michael@0: michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: // Make sure we're popping the innermost event target. michael@0: if (NS_WARN_IF(mEvents->mEventTarget != innermostTarget)) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: MOZ_ASSERT(mEvents != &mEventsRoot); michael@0: michael@0: queue = mEvents; michael@0: mEvents = mEvents->mNext; michael@0: michael@0: nsCOMPtr event; michael@0: while (queue->GetEvent(false, getter_AddRefs(event))) michael@0: mEvents->PutEvent(event); michael@0: michael@0: // Don't let the event target post any more events. michael@0: queue->mEventTarget.swap(target); michael@0: target->mQueue = nullptr; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsThread::SetMainThreadObserver(nsIThreadObserver* aObserver) michael@0: { michael@0: if (aObserver && nsThread::sMainThreadObserver) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: if (!NS_IsMainThread()) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsThread::sMainThreadObserver = aObserver; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsThreadSyncDispatch::Run() michael@0: { michael@0: if (mSyncTask) { michael@0: mResult = mSyncTask->Run(); michael@0: mSyncTask = nullptr; michael@0: // unblock the origin thread michael@0: mOrigin->Dispatch(this, NS_DISPATCH_NORMAL); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMPL_ISUPPORTS(nsThread::nsNestedEventTarget, nsIEventTarget) michael@0: michael@0: NS_IMETHODIMP michael@0: nsThread::nsNestedEventTarget::Dispatch(nsIRunnable *event, uint32_t flags) michael@0: { michael@0: LOG(("THRD(%p) Dispatch [%p %x] to nested loop %p\n", mThread.get(), event, michael@0: flags, this)); michael@0: michael@0: return mThread->DispatchInternal(event, flags, this); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsThread::nsNestedEventTarget::IsOnCurrentThread(bool *result) michael@0: { michael@0: return mThread->IsOnCurrentThread(result); michael@0: }