From 3d8ce1a11a7347cc94a937719c4bc8df46fb8d14 Mon Sep 17 00:00:00 2001 From: Pale Moon Date: Thu, 1 Sep 2016 13:39:08 +0200 Subject: Base import of Tycho code (warning: huge commit) --- xpcom/threads/BackgroundHangMonitor.cpp | 601 +++++++++++++++++++++ xpcom/threads/BackgroundHangMonitor.h | 197 +++++++ xpcom/threads/HangMonitor.cpp | 436 ++++++++++++++-- xpcom/threads/HangMonitor.h | 166 ++++-- xpcom/threads/LazyIdleThread.cpp | 144 +++-- xpcom/threads/LazyIdleThread.h | 21 +- xpcom/threads/Makefile.in | 26 - xpcom/threads/SyncRunnable.h | 52 +- xpcom/threads/TimerThread.cpp | 273 +++++++--- xpcom/threads/TimerThread.h | 70 +-- xpcom/threads/moz.build | 27 +- xpcom/threads/nsEnvironment.cpp | 204 ++++---- xpcom/threads/nsEnvironment.h | 20 +- xpcom/threads/nsEventQueue.cpp | 109 ++-- xpcom/threads/nsEventQueue.h | 61 ++- xpcom/threads/nsIProcess.idl | 2 +- xpcom/threads/nsIThreadInternal.idl | 54 +- xpcom/threads/nsIThreadManager.idl | 8 +- xpcom/threads/nsIThreadPool.idl | 12 +- xpcom/threads/nsITimer.idl | 38 +- xpcom/threads/nsMemoryPressure.cpp | 54 ++ xpcom/threads/nsMemoryPressure.h | 77 +++ xpcom/threads/nsProcess.h | 30 +- xpcom/threads/nsProcessCommon.cpp | 897 +++++++++++++++++--------------- xpcom/threads/nsThread.cpp | 757 +++++++++++++++++++-------- xpcom/threads/nsThread.h | 168 ++++-- xpcom/threads/nsThreadManager.cpp | 470 ++++++++++++++--- xpcom/threads/nsThreadManager.h | 102 +++- xpcom/threads/nsThreadPool.cpp | 182 ++++--- xpcom/threads/nsThreadPool.h | 16 +- xpcom/threads/nsTimerImpl.cpp | 354 ++++++++----- xpcom/threads/nsTimerImpl.h | 66 ++- 32 files changed, 4158 insertions(+), 1536 deletions(-) create mode 100644 xpcom/threads/BackgroundHangMonitor.cpp create mode 100644 xpcom/threads/BackgroundHangMonitor.h delete mode 100644 xpcom/threads/Makefile.in create mode 100644 xpcom/threads/nsMemoryPressure.cpp create mode 100644 xpcom/threads/nsMemoryPressure.h (limited to 'xpcom/threads') diff --git a/xpcom/threads/BackgroundHangMonitor.cpp b/xpcom/threads/BackgroundHangMonitor.cpp new file mode 100644 index 000000000..a0ed08813 --- /dev/null +++ b/xpcom/threads/BackgroundHangMonitor.cpp @@ -0,0 +1,601 @@ +/* -*- 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 "mozilla/ArrayUtils.h" +#include "mozilla/BackgroundHangMonitor.h" +#include "mozilla/LinkedList.h" +#include "mozilla/Monitor.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/ThreadLocal.h" +#ifdef MOZ_NUWA_PROCESS +#include "ipc/Nuwa.h" +#endif + +#include "prinrval.h" +#include "prthread.h" +#include "nsIObserverService.h" +#include "nsIObserver.h" +#include "mozilla/Services.h" +#include "nsXULAppAPI.h" + +#include + +// Activate BHR only for one every BHR_BETA_MOD users. +#define BHR_BETA_MOD 100; + +namespace mozilla { + +/** + * BackgroundHangManager is the global object that + * manages all instances of BackgroundHangThread. + */ +class BackgroundHangManager : public nsIObserver +{ +private: + // Background hang monitor thread function + static void MonitorThread(void* aData) + { + PR_SetCurrentThreadName("BgHangManager"); + +#ifdef MOZ_NUWA_PROCESS + if (IsNuwaProcess()) { + NuwaMarkCurrentThread(nullptr, nullptr); + } +#endif + + /* We do not hold a reference to BackgroundHangManager here + because the monitor thread only exists as long as the + BackgroundHangManager instance exists. We stop the monitor + thread in the BackgroundHangManager destructor, and we can + only get to the destructor if we don't hold a reference here. */ + static_cast(aData)->RunMonitorThread(); + } + + // Hang monitor thread + PRThread* mHangMonitorThread; + // Stop hang monitoring + bool mShutdown; + + BackgroundHangManager(const BackgroundHangManager&); + BackgroundHangManager& operator=(const BackgroundHangManager&); + void RunMonitorThread(); + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOBSERVER + static StaticRefPtr sInstance; + static bool sProhibited; + static bool sDisabled; + + // Lock for access to members of this class + Monitor mLock; + // Current time as seen by hang monitors + PRIntervalTime mIntervalNow; + // List of BackgroundHangThread instances associated with each thread + LinkedList mHangThreads; + + void Shutdown() + { + MonitorAutoLock autoLock(mLock); + mShutdown = true; + autoLock.Notify(); + } + + void Wakeup() + { + // PR_CreateThread could have failed earlier + if (mHangMonitorThread) { + // Use PR_Interrupt to avoid potentially taking a lock + PR_Interrupt(mHangMonitorThread); + } + } + + BackgroundHangManager(); +private: + virtual ~BackgroundHangManager(); +}; + +NS_IMPL_ISUPPORTS(BackgroundHangManager, nsIObserver) + +NS_IMETHODIMP +BackgroundHangManager::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { + NS_ENSURE_TRUE(!strcmp(aTopic, "profile-after-change"), NS_ERROR_UNEXPECTED); + BackgroundHangMonitor::DisableOnBeta(); + + nsCOMPtr observerService = mozilla::services::GetObserverService(); + MOZ_ASSERT(observerService); + observerService->RemoveObserver(this, "profile-after-change"); + + return NS_OK; +} + +/** + * BackgroundHangThread is a per-thread object that is used + * by all instances of BackgroundHangMonitor to monitor hangs. + */ +class BackgroundHangThread : public LinkedListElement +{ +private: + static ThreadLocal sTlsKey; + + BackgroundHangThread(const BackgroundHangThread&); + BackgroundHangThread& operator=(const BackgroundHangThread&); + ~BackgroundHangThread(); + + /* Keep a reference to the manager, so we can keep going even + after BackgroundHangManager::Shutdown is called. */ + const RefPtr mManager; + // Unique thread ID for identification + const PRThread* mThreadID; + +public: + NS_INLINE_DECL_REFCOUNTING(BackgroundHangThread) + static BackgroundHangThread* FindThread(); + + static void Startup() + { + /* We can tolerate init() failing. */ + (void)!sTlsKey.init(); + } + + // Name of the thread + const nsAutoCString mThreadName; + // Hang timeout in ticks + const PRIntervalTime mTimeout; + // PermaHang timeout in ticks + const PRIntervalTime mMaxTimeout; + // Time at last activity + PRIntervalTime mInterval; + // Time when a hang started + PRIntervalTime mHangStart; + // Is the thread in a hang + bool mHanging; + // Is the thread in a waiting state + bool mWaiting; + + BackgroundHangThread(const char* aName, + uint32_t aTimeoutMs, + uint32_t aMaxTimeoutMs); + + // Report a hang; aManager->mLock IS locked + void ReportHang(PRIntervalTime aHangTime) const; + // Report a permanent hang; aManager->mLock IS locked + void ReportPermaHang(); + // Called by BackgroundHangMonitor::NotifyActivity + void NotifyActivity(); + // Called by BackgroundHangMonitor::NotifyWait + void NotifyWait() + { + NotifyActivity(); + mWaiting = true; + } +}; + + +StaticRefPtr BackgroundHangManager::sInstance; +bool BackgroundHangManager::sProhibited = false; +bool BackgroundHangManager::sDisabled = false; + +ThreadLocal BackgroundHangThread::sTlsKey; + + +BackgroundHangManager::BackgroundHangManager() + : mShutdown(false) + , mLock("BackgroundHangManager") + , mIntervalNow(0) +{ + // Lock so we don't race against the new monitor thread + MonitorAutoLock autoLock(mLock); + mHangMonitorThread = PR_CreateThread( + PR_USER_THREAD, MonitorThread, this, + PR_PRIORITY_LOW, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0); + + MOZ_ASSERT(mHangMonitorThread, "Failed to create monitor thread"); +} + +BackgroundHangManager::~BackgroundHangManager() +{ + MOZ_ASSERT(mShutdown, "Destruction without Shutdown call"); + MOZ_ASSERT(mHangThreads.isEmpty(), "Destruction with outstanding monitors"); + MOZ_ASSERT(mHangMonitorThread, "No monitor thread"); + + // PR_CreateThread could have failed above due to resource limitation + if (mHangMonitorThread) { + // The monitor thread can only live as long as the instance lives + PR_JoinThread(mHangMonitorThread); + } +} + +void +BackgroundHangManager::RunMonitorThread() +{ + // Keep us locked except when waiting + MonitorAutoLock autoLock(mLock); + + /* mIntervalNow is updated at various intervals determined by waitTime. + However, if an update latency is too long (due to CPU scheduling, system + sleep, etc.), we don't update mIntervalNow at all. This is done so that + long latencies in our timing are not detected as hangs. systemTime is + used to track PR_IntervalNow() and determine our latency. */ + + PRIntervalTime systemTime = PR_IntervalNow(); + // Default values for the first iteration of thread loop + PRIntervalTime waitTime = PR_INTERVAL_NO_WAIT; + PRIntervalTime recheckTimeout = PR_INTERVAL_NO_WAIT; + + while (!mShutdown) { + + PR_ClearInterrupt(); + nsresult rv = autoLock.Wait(waitTime); + + PRIntervalTime newTime = PR_IntervalNow(); + PRIntervalTime systemInterval = newTime - systemTime; + systemTime = newTime; + + /* waitTime is a quarter of the shortest timeout value; If our timing + latency is low enough (less than half the shortest timeout value), + we can update mIntervalNow. */ + if (MOZ_LIKELY(waitTime != PR_INTERVAL_NO_TIMEOUT && + systemInterval < 2 * waitTime)) { + mIntervalNow += systemInterval; + } + + /* If it's before the next recheck timeout, and our wait did not + get interrupted (either through Notify or PR_Interrupt), we can + keep the current waitTime and skip iterating through hang monitors. */ + if (MOZ_LIKELY(systemInterval < recheckTimeout && + systemInterval >= waitTime && + rv == NS_OK)) { + recheckTimeout -= systemInterval; + continue; + } + + /* We are in one of the following scenarios, + - Hang or permahang recheck timeout + - Thread added/removed + - Thread wait or hang ended + In all cases, we want to go through our list of hang + monitors and update waitTime and recheckTimeout. */ + waitTime = PR_INTERVAL_NO_TIMEOUT; + recheckTimeout = PR_INTERVAL_NO_TIMEOUT; + + // Locally hold mIntervalNow + PRIntervalTime intervalNow = mIntervalNow; + + // iterate through hang monitors + for (BackgroundHangThread* currentThread = mHangThreads.getFirst(); + currentThread; currentThread = currentThread->getNext()) { + + if (currentThread->mWaiting) { + // Thread is waiting, not hanging + continue; + } + PRIntervalTime interval = currentThread->mInterval; + PRIntervalTime hangTime = intervalNow - interval; + if (MOZ_UNLIKELY(hangTime >= currentThread->mMaxTimeout)) { + // A permahang started + // Skip subsequent iterations and tolerate a race on mWaiting here + currentThread->mWaiting = true; + currentThread->mHanging = false; + currentThread->ReportPermaHang(); + continue; + } + + if (MOZ_LIKELY(!currentThread->mHanging)) { + if (MOZ_UNLIKELY(hangTime >= currentThread->mTimeout)) { + // A hang started + currentThread->mHangStart = interval; + currentThread->mHanging = true; + } + } else { + if (MOZ_LIKELY(interval != currentThread->mHangStart)) { + // A hang ended + currentThread->ReportHang(intervalNow - currentThread->mHangStart); + currentThread->mHanging = false; + } + } + + /* If we are hanging, the next time we check for hang status is when + the hang turns into a permahang. If we're not hanging, the next + recheck timeout is when we may be entering a hang. */ + PRIntervalTime nextRecheck; + if (currentThread->mHanging) { + nextRecheck = currentThread->mMaxTimeout; + } else { + nextRecheck = currentThread->mTimeout; + } + recheckTimeout = std::min(recheckTimeout, nextRecheck - hangTime); + + /* We wait for a quarter of the shortest timeout + value to give mIntervalNow enough granularity. */ + waitTime = std::min(waitTime, currentThread->mTimeout / 4); + } + } + + /* We are shutting down now. + Wait for all outstanding monitors to unregister. */ + while (!mHangThreads.isEmpty()) { + autoLock.Wait(PR_INTERVAL_NO_TIMEOUT); + } +} + + +BackgroundHangThread::BackgroundHangThread(const char* aName, + uint32_t aTimeoutMs, + uint32_t aMaxTimeoutMs) + : mManager(BackgroundHangManager::sInstance) + , mThreadID(PR_GetCurrentThread()) + , mThreadName(aName) + , mTimeout(aTimeoutMs == BackgroundHangMonitor::kNoTimeout + ? PR_INTERVAL_NO_TIMEOUT + : PR_MillisecondsToInterval(aTimeoutMs)) + , mMaxTimeout(aMaxTimeoutMs == BackgroundHangMonitor::kNoTimeout + ? PR_INTERVAL_NO_TIMEOUT + : PR_MillisecondsToInterval(aMaxTimeoutMs)) + , mInterval(mManager->mIntervalNow) + , mHangStart(mInterval) + , mHanging(false) + , mWaiting(true) +{ + if (sTlsKey.initialized()) { + sTlsKey.set(this); + } + // Lock here because LinkedList is not thread-safe + MonitorAutoLock autoLock(mManager->mLock); + // Add to thread list + mManager->mHangThreads.insertBack(this); + // Wake up monitor thread to process new thread + autoLock.Notify(); +} + +BackgroundHangThread::~BackgroundHangThread() +{ + // Lock here because LinkedList is not thread-safe + MonitorAutoLock autoLock(mManager->mLock); + // Remove from thread list + remove(); + // Wake up monitor thread to process removed thread + autoLock.Notify(); + + // We no longer have a thread + if (sTlsKey.initialized()) { + sTlsKey.set(nullptr); + } +} + +void +BackgroundHangThread::ReportHang(PRIntervalTime aHangTime) const +{ + // Recovered from a hang; called on the monitor thread + // mManager->mLock IS locked +} + +void +BackgroundHangThread::ReportPermaHang() +{ + // Permanently hanged; called on the monitor thread + // mManager->mLock IS locked + + // TODO: Add more detailed analysis for perma-hangs + ReportHang(mMaxTimeout); +} + +MOZ_ALWAYS_INLINE void +BackgroundHangThread::NotifyActivity() +{ + PRIntervalTime intervalNow = mManager->mIntervalNow; + if (mWaiting) { + mInterval = intervalNow; + mWaiting = false; + /* We have to wake up the manager thread because when all threads + are waiting, the manager thread waits indefinitely as well. */ + mManager->Wakeup(); + } else { + PRIntervalTime duration = intervalNow - mInterval; + if (MOZ_UNLIKELY(duration >= mTimeout)) { + /* Wake up the manager thread to tell it that a hang ended */ + mManager->Wakeup(); + } + mInterval = intervalNow; + } +} + +BackgroundHangThread* +BackgroundHangThread::FindThread() +{ +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + if (BackgroundHangManager::sInstance == nullptr) { + MOZ_ASSERT(BackgroundHangManager::sProhibited || BackgroundHangManager::sDisabled, + "BackgroundHandleManager is not initialized"); + return nullptr; + } + + if (sTlsKey.initialized()) { + // Use TLS if available + MOZ_ASSERT(!BackgroundHangManager::sProhibited, + "BackgroundHandleManager is not initialized"); + return sTlsKey.get(); + } + // If TLS is unavailable, we can search through the thread list + RefPtr manager(BackgroundHangManager::sInstance); + MOZ_ASSERT(manager, "Creating BackgroundHangMonitor after shutdown"); + + PRThread* threadID = PR_GetCurrentThread(); + // Lock thread list for traversal + MonitorAutoLock autoLock(manager->mLock); + for (BackgroundHangThread* thread = manager->mHangThreads.getFirst(); + thread; thread = thread->getNext()) { + if (thread->mThreadID == threadID) { + return thread; + } + } +#endif + // Current thread is not initialized + return nullptr; +} + +bool +BackgroundHangMonitor::ShouldDisableOnBeta(const nsCString &clientID) { + MOZ_ASSERT(clientID.Length() == 36, "clientID is invalid"); + const char *suffix = clientID.get() + clientID.Length() - 4; + return strtol(suffix, NULL, 16) % BHR_BETA_MOD; +} + +bool +BackgroundHangMonitor::IsDisabled() { +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + return BackgroundHangManager::sDisabled; +#else + return true; +#endif +} + +bool +BackgroundHangMonitor::DisableOnBeta() { + nsAdoptingCString clientID = Preferences::GetCString("toolkit.telemetry.cachedClientID"); + bool telemetryEnabled = Preferences::GetBool("toolkit.telemetry.enabled"); + + if (!telemetryEnabled || !clientID || BackgroundHangMonitor::ShouldDisableOnBeta(clientID)) { + if (XRE_IsParentProcess()) { + BackgroundHangMonitor::Shutdown(); + } else { + BackgroundHangManager::sDisabled = true; + } + return true; + } + + return false; +} + +void +BackgroundHangMonitor::Startup() +{ +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + MOZ_ASSERT(!BackgroundHangManager::sProhibited, "Prohibited"); + MOZ_ASSERT(!BackgroundHangManager::sInstance, "Already initialized"); + + if (!strcmp(NS_STRINGIFY(MOZ_UPDATE_CHANNEL), "beta")) { + if (XRE_IsParentProcess()) { // cached ClientID hasn't been read yet + BackgroundHangThread::Startup(); + BackgroundHangManager::sInstance = new BackgroundHangManager(); + + nsCOMPtr observerService = mozilla::services::GetObserverService(); + MOZ_ASSERT(observerService); + + observerService->AddObserver(BackgroundHangManager::sInstance, "profile-after-change", false); + return; + } else if(DisableOnBeta()){ + return; + } + } + + BackgroundHangThread::Startup(); + BackgroundHangManager::sInstance = new BackgroundHangManager(); +#endif +} + +void +BackgroundHangMonitor::Shutdown() +{ +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + if (BackgroundHangManager::sDisabled) { + MOZ_ASSERT(!BackgroundHangManager::sInstance, "Initialized"); + return; + } + + MOZ_ASSERT(!BackgroundHangManager::sProhibited, "Prohibited"); + MOZ_ASSERT(BackgroundHangManager::sInstance, "Not initialized"); + /* Scope our lock inside Shutdown() because the sInstance object can + be destroyed as soon as we set sInstance to nullptr below, and + we don't want to hold the lock when it's being destroyed. */ + BackgroundHangManager::sInstance->Shutdown(); + BackgroundHangManager::sInstance = nullptr; + BackgroundHangManager::sDisabled = true; +#endif +} + +BackgroundHangMonitor::BackgroundHangMonitor(const char* aName, + uint32_t aTimeoutMs, + uint32_t aMaxTimeoutMs) + : mThread(BackgroundHangThread::FindThread()) +{ +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + if (!BackgroundHangManager::sDisabled && !BackgroundHangManager::sProhibited && !mThread) { + // If sProhibit is true, mThread would be null, and no monitoring. + mThread = new BackgroundHangThread(aName, aTimeoutMs, aMaxTimeoutMs); + } +#endif +} + +BackgroundHangMonitor::BackgroundHangMonitor() + : mThread(BackgroundHangThread::FindThread()) +{ +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + if (BackgroundHangManager::sDisabled) { + return; + } + + MOZ_ASSERT(!BackgroundHangManager::sProhibited || mThread, + "This thread is not initialized for hang monitoring"); +#endif +} + +BackgroundHangMonitor::~BackgroundHangMonitor() +{ +} + +void +BackgroundHangMonitor::NotifyActivity() +{ +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + if (mThread == nullptr) { + MOZ_ASSERT(BackgroundHangManager::sProhibited || + BackgroundHangManager::sDisabled, + "This thread is not initialized for hang monitoring"); + return; + } + + mThread->NotifyActivity(); +#endif +} + +void +BackgroundHangMonitor::NotifyWait() +{ +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + if (mThread == nullptr) { + MOZ_ASSERT(BackgroundHangManager::sProhibited || + BackgroundHangManager::sDisabled, + "This thread is not initialized for hang monitoring"); + return; + } + + mThread->NotifyWait(); +#endif +} + +void +BackgroundHangMonitor::Prohibit() +{ +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + MOZ_ASSERT(BackgroundHangManager::sInstance == nullptr, + "The background hang monitor is already initialized"); + BackgroundHangManager::sProhibited = true; +#endif +} + +void +BackgroundHangMonitor::Allow() +{ +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + MOZ_ASSERT(BackgroundHangManager::sInstance == nullptr, + "The background hang monitor is already initialized"); + BackgroundHangManager::sProhibited = false; +#endif +} + +} // namespace mozilla diff --git a/xpcom/threads/BackgroundHangMonitor.h b/xpcom/threads/BackgroundHangMonitor.h new file mode 100644 index 000000000..e523fe0ee --- /dev/null +++ b/xpcom/threads/BackgroundHangMonitor.h @@ -0,0 +1,197 @@ +/* -*- 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/. */ + +#ifndef mozilla_BackgroundHangMonitor_h +#define mozilla_BackgroundHangMonitor_h + +#include "mozilla/RefPtr.h" + +#include "nsString.h" + +#include + +namespace mozilla { + +class BackgroundHangThread; +class BackgroundHangManager; + +/** + * The background hang monitor is responsible for detecting and reporting + * hangs in background (non-main) threads. A thread registers itself using + * the BackgroundHangMonitor object and periodically calls its methods to + * inform the hang monitor of the thread's activity. Each thread is given + * a thread name, a timeout, and a maximum timeout. If one of the thread's + * tasks runs for longer than the timeout duration but shorter than the + * maximum timeout, a (transient) hang is reported. On the other hand, if + * a task runs for longer than the maximum timeout duration or never + * finishes (e.g. in a deadlock), a permahang is reported. + * + * Tasks are defined arbitrarily, but are typically represented by events + * in an event loop -- processing one event is equivalent to running one + * task. To ensure responsiveness, tasks in a thread often have a target + * running time. This is a good starting point for determining the timeout + * and maximum timeout values. For example, the Compositor thread has a + * responsiveness goal of 60Hz or 17ms, so a starting timeout could be + * 100ms. Considering some platforms (e.g. Android) can terminate the app + * when a critical thread hangs for longer than a few seconds, a good + * starting maximum timeout is 4 or 5 seconds. + * + * A thread registers itself through the BackgroundHangMonitor constructor. + * Multiple BackgroundHangMonitor objects can be used in one thread. The + * constructor without arguments can be used when it is known that the thread + * already has a BackgroundHangMonitor registered. When all instances of + * BackgroundHangMonitor are destroyed, the thread is unregistered. + * + * The thread then uses two methods to inform BackgroundHangMonitor of the + * thread's activity: + * + * > BackgroundHangMonitor::NotifyActivity should be called *before* + * starting a task. The task run time is determined by the interval + * between this call and the next NotifyActivity call. + * + * > BackgroundHangMonitor::NotifyWait should be called *before* the + * thread enters a wait state (e.g. to wait for a new event). This + * prevents a waiting thread from being detected as hanging. The wait + * state is automatically cleared at the next NotifyActivity call. + * + * The following example shows hang monitoring in a simple event loop: + * + * void thread_main() + * { + * mozilla::BackgroundHangMonitor hangMonitor("example1", 100, 1000); + * while (!exiting) { + * hangMonitor.NotifyActivity(); + * process_next_event(); + * hangMonitor.NotifyWait(); + * wait_for_next_event(); + * } + * } + * + * The following example shows reentrancy in nested event loops: + * + * void thread_main() + * { + * mozilla::BackgroundHangMonitor hangMonitor("example2", 100, 1000); + * while (!exiting) { + * hangMonitor.NotifyActivity(); + * process_next_event(); + * hangMonitor.NotifyWait(); + * wait_for_next_event(); + * } + * } + * + * void process_next_event() + * { + * mozilla::BackgroundHangMonitor hangMonitor(); + * if (is_sync_event) { + * while (!finished_event) { + * hangMonitor.NotifyActivity(); + * process_next_event(); + * hangMonitor.NotifyWait(); + * wait_for_next_event(); + * } + * } else { + * process_nonsync_event(); + * } + * } + * + * Prohibit() and Allow() make the background hang monitor work safely + * before Startup(). + */ +class BackgroundHangMonitor +{ +private: + friend BackgroundHangManager; + + RefPtr mThread; + + static bool ShouldDisableOnBeta(const nsCString &); + static bool DisableOnBeta(); + +public: + static const uint32_t kNoTimeout = 0; + + /** + * Enable hang monitoring. + * Must return before using BackgroundHangMonitor. + */ + static void Startup(); + + /** + * Disable hang monitoring. + * Can be called without destroying all BackgroundHangMonitors first. + */ + static void Shutdown(); + + /** + * Returns true if BHR is disabled. + */ + static bool IsDisabled(); + + /** + * Start monitoring hangs for the current thread. + * + * @param aName Name to identify the thread with + * @param aTimeoutMs Amount of time in milliseconds without + * activity before registering a hang + * @param aMaxTimeoutMs Amount of time in milliseconds without + * activity before registering a permanent hang + */ + BackgroundHangMonitor(const char* aName, + uint32_t aTimeoutMs, + uint32_t aMaxTimeoutMs); + + /** + * Monitor hangs using an existing monitor + * associated with the current thread. + */ + BackgroundHangMonitor(); + + /** + * Destroys the hang monitor; hang monitoring for a thread stops + * when all monitors associated with the thread are destroyed. + */ + ~BackgroundHangMonitor(); + + /** + * Notify the hang monitor of pending current thread activity. + * Call this method before starting an "activity" or after + * exiting from a wait state. + */ + void NotifyActivity(); + + /** + * Notify the hang monitor of current thread wait. + * Call this method before entering a wait state; call + * NotifyActivity when subsequently exiting the wait state. + */ + void NotifyWait(); + + /** + * Prohibit the hang monitor from activating. + * + * Startup() should not be called between Prohibit() and Allow(). + * This function makes the background hang monitor stop monitoring + * threads. + * + * Prohibit() and Allow() can be called before XPCOM is ready. If + * we don't stop monitoring threads it could case errors. + */ + static void Prohibit(); + + /** + * Allow the hang monitor to run. + * + * Allow() and Prohibit() should be called in pair. + * + * \see Prohibit() + */ + static void Allow(); +}; + +} // namespace mozilla + +#endif // mozilla_BackgroundHangMonitor_h diff --git a/xpcom/threads/HangMonitor.cpp b/xpcom/threads/HangMonitor.cpp index 70ebb2433..6f6340d45 100644 --- a/xpcom/threads/HangMonitor.cpp +++ b/xpcom/threads/HangMonitor.cpp @@ -1,20 +1,32 @@ /* -*- 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 "mozilla/HangMonitor.h" + +#include + +#include "mozilla/Atomics.h" +#include "mozilla/BackgroundHangMonitor.h" #include "mozilla/Monitor.h" #include "mozilla/Preferences.h" -#include "nsXULAppAPI.h" -#include "nsThreadUtils.h" +#include "mozilla/ProcessedStack.h" +#include "mozilla/Telemetry.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/UniquePtr.h" +#include "nsReadableUtils.h" #include "nsStackWalk.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" #ifdef XP_WIN #include #endif -namespace mozilla { namespace HangMonitor { +namespace mozilla { +namespace HangMonitor { /** * A flag which may be set from within a debugger to disable the hang @@ -24,6 +36,10 @@ volatile bool gDebugDisableHangMonitor = false; const char kHangMonitorPrefName[] = "hangmonitor.timeout"; +#ifdef REPORT_CHROME_HANGS +const char kTelemetryPrefName[] = "toolkit.telemetry.enabled"; +#endif + // Monitor protects gShutdown and gTimeout, but not gTimestamp which rely on // being atomically set by the processor; synchronization doesn't really matter // in this use case. @@ -39,20 +55,41 @@ bool gShutdown; // The timestamp of the last event notification, or PR_INTERVAL_NO_WAIT if // we're currently not processing events. -volatile PRIntervalTime gTimestamp = PR_INTERVAL_NO_WAIT; +Atomic gTimestamp(PR_INTERVAL_NO_WAIT); + +#ifdef REPORT_CHROME_HANGS +// Main thread ID used in reporting chrome hangs under Windows +static HANDLE winMainThreadHandle = nullptr; + +// Default timeout for reporting chrome hangs to Telemetry (5 seconds) +static const int32_t DEFAULT_CHROME_HANG_INTERVAL = 5; + +// Maximum number of PCs to gather from the stack +static const int32_t MAX_CALL_STACK_PCS = 400; + +// Chrome hang annotators +static StaticAutoPtr> gAnnotators; +#endif // PrefChangedFunc -int +void PrefChanged(const char*, void*) { int32_t newval = Preferences::GetInt(kHangMonitorPrefName); +#ifdef REPORT_CHROME_HANGS + // Monitor chrome hangs on the profiling branch if Telemetry enabled + if (newval == 0) { + bool telemetryEnabled = Preferences::GetBool(kTelemetryPrefName); + if (telemetryEnabled) { + newval = DEFAULT_CHROME_HANG_INTERVAL; + } + } +#endif MonitorAutoLock lock(*gMonitor); if (newval != gTimeout) { gTimeout = newval; lock.Notify(); } - - return 0; } void @@ -71,6 +108,231 @@ Crash() NS_RUNTIMEABORT("HangMonitor triggered"); } +#ifdef REPORT_CHROME_HANGS +class ChromeHangAnnotations : public HangAnnotations +{ +public: + ChromeHangAnnotations(); + ~ChromeHangAnnotations(); + + void AddAnnotation(const nsAString& aName, const int32_t aData) override; + void AddAnnotation(const nsAString& aName, const double aData) override; + void AddAnnotation(const nsAString& aName, const nsAString& aData) override; + void AddAnnotation(const nsAString& aName, const nsACString& aData) override; + void AddAnnotation(const nsAString& aName, const bool aData) override; + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override; + bool IsEmpty() const override; + bool GetEnumerator(Enumerator** aOutEnum) override; + + typedef std::pair AnnotationType; + typedef std::vector VectorType; + typedef VectorType::const_iterator IteratorType; + +private: + VectorType mAnnotations; +}; + +ChromeHangAnnotations::ChromeHangAnnotations() +{ + MOZ_COUNT_CTOR(ChromeHangAnnotations); +} + +ChromeHangAnnotations::~ChromeHangAnnotations() +{ + MOZ_COUNT_DTOR(ChromeHangAnnotations); +} + +void +ChromeHangAnnotations::AddAnnotation(const nsAString& aName, const int32_t aData) +{ + nsString dataString; + dataString.AppendInt(aData); + AnnotationType annotation = std::make_pair(nsString(aName), dataString); + mAnnotations.push_back(annotation); +} + +void +ChromeHangAnnotations::AddAnnotation(const nsAString& aName, const double aData) +{ + nsString dataString; + dataString.AppendFloat(aData); + AnnotationType annotation = std::make_pair(nsString(aName), dataString); + mAnnotations.push_back(annotation); +} + +void +ChromeHangAnnotations::AddAnnotation(const nsAString& aName, const nsAString& aData) +{ + AnnotationType annotation = std::make_pair(nsString(aName), nsString(aData)); + mAnnotations.push_back(annotation); +} + +void +ChromeHangAnnotations::AddAnnotation(const nsAString& aName, const nsACString& aData) +{ + nsString dataString; + AppendUTF8toUTF16(aData, dataString); + AnnotationType annotation = std::make_pair(nsString(aName), dataString); + mAnnotations.push_back(annotation); +} + +void +ChromeHangAnnotations::AddAnnotation(const nsAString& aName, const bool aData) +{ + nsString dataString; + dataString += aData ? NS_LITERAL_STRING("true") : NS_LITERAL_STRING("false"); + AnnotationType annotation = std::make_pair(nsString(aName), dataString); + mAnnotations.push_back(annotation); +} + +/** + * This class itself does not use synchronization but it (and its parent object) + * should be protected by mutual exclusion in some way. In Telemetry the chrome + * hang data is protected via TelemetryImpl::mHangReportsMutex. + */ +class ChromeHangAnnotationEnumerator : public HangAnnotations::Enumerator +{ +public: + ChromeHangAnnotationEnumerator(const ChromeHangAnnotations::VectorType& aAnnotations); + ~ChromeHangAnnotationEnumerator(); + + virtual bool Next(nsAString& aOutName, nsAString& aOutValue); + +private: + ChromeHangAnnotations::IteratorType mIterator; + ChromeHangAnnotations::IteratorType mEnd; +}; + +ChromeHangAnnotationEnumerator::ChromeHangAnnotationEnumerator( + const ChromeHangAnnotations::VectorType& aAnnotations) + : mIterator(aAnnotations.begin()) + , mEnd(aAnnotations.end()) +{ + MOZ_COUNT_CTOR(ChromeHangAnnotationEnumerator); +} + +ChromeHangAnnotationEnumerator::~ChromeHangAnnotationEnumerator() +{ + MOZ_COUNT_DTOR(ChromeHangAnnotationEnumerator); +} + +bool +ChromeHangAnnotationEnumerator::Next(nsAString& aOutName, nsAString& aOutValue) +{ + aOutName.Truncate(); + aOutValue.Truncate(); + if (mIterator == mEnd) { + return false; + } + aOutName = mIterator->first; + aOutValue = mIterator->second; + ++mIterator; + return true; +} + +bool +ChromeHangAnnotations::IsEmpty() const +{ + return mAnnotations.empty(); +} + +size_t +ChromeHangAnnotations::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const +{ + size_t result = sizeof(mAnnotations) + + mAnnotations.capacity() * sizeof(AnnotationType); + for (IteratorType i = mAnnotations.begin(), e = mAnnotations.end(); i != e; + ++i) { + result += i->first.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + result += i->second.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } + + return result; +} + +bool +ChromeHangAnnotations::GetEnumerator(HangAnnotations::Enumerator** aOutEnum) +{ + if (!aOutEnum) { + return false; + } + *aOutEnum = nullptr; + if (mAnnotations.empty()) { + return false; + } + *aOutEnum = new ChromeHangAnnotationEnumerator(mAnnotations); + return true; +} + +static void +ChromeStackWalker(uint32_t aFrameNumber, void* aPC, void* aSP, void* aClosure) +{ + MOZ_ASSERT(aClosure); + std::vector* stack = + static_cast*>(aClosure); + if (stack->size() == MAX_CALL_STACK_PCS) { + return; + } + MOZ_ASSERT(stack->size() < MAX_CALL_STACK_PCS); + stack->push_back(reinterpret_cast(aPC)); +} + +static void +GetChromeHangReport(Telemetry::ProcessedStack& aStack, + int32_t& aSystemUptime, + int32_t& aFirefoxUptime) +{ + MOZ_ASSERT(winMainThreadHandle); + + // The thread we're about to suspend might have the alloc lock + // so allocate ahead of time + std::vector rawStack; + rawStack.reserve(MAX_CALL_STACK_PCS); + DWORD ret = ::SuspendThread(winMainThreadHandle); + if (ret == -1) { + return; + } + NS_StackWalk(ChromeStackWalker, /* skipFrames */ 0, /* maxFrames */ 0, + reinterpret_cast(&rawStack), + reinterpret_cast(winMainThreadHandle), nullptr); + ret = ::ResumeThread(winMainThreadHandle); + if (ret == -1) { + return; + } + aStack = Telemetry::GetStackAndModules(rawStack); + + // Record system uptime (in minutes) at the time of the hang + aSystemUptime = ((GetTickCount() / 1000) - (gTimeout * 2)) / 60; + + // Record Firefox uptime (in minutes) at the time of the hang + bool error; + TimeStamp processCreation = TimeStamp::ProcessCreation(error); + if (!error) { + TimeDuration td = TimeStamp::Now() - processCreation; + aFirefoxUptime = (static_cast(td.ToSeconds()) - (gTimeout * 2)) / 60; + } else { + aFirefoxUptime = -1; + } +} + +static void +ChromeHangAnnotatorCallout(ChromeHangAnnotations& aAnnotations) +{ + gMonitor->AssertCurrentThreadOwns(); + MOZ_ASSERT(gAnnotators); + if (!gAnnotators) { + return; + } + for (std::set::iterator i = gAnnotators->begin(), + e = gAnnotators->end(); + i != e; ++i) { + (*i)->AnnotateHang(aAnnotations); + } +} + +#endif + void ThreadMain(void*) { @@ -84,6 +346,13 @@ ThreadMain(void*) PRIntervalTime lastTimestamp = 0; int waitCount = 0; +#ifdef REPORT_CHROME_HANGS + Telemetry::ProcessedStack stack; + int32_t systemUptime = -1; + int32_t firefoxUptime = -1; + auto annotations = MakeUnique(); +#endif + while (true) { if (gShutdown) { return; // Exit the thread @@ -104,6 +373,16 @@ ThreadMain(void*) timestamp == lastTimestamp && gTimeout > 0) { ++waitCount; +#ifdef REPORT_CHROME_HANGS + // Capture the chrome-hang stack + Firefox & system uptimes after + // the minimum hang duration has been reached (not when the hang ends) + if (waitCount == 2) { + GetChromeHangReport(stack, systemUptime, firefoxUptime); + ChromeHangAnnotatorCallout(*annotations); + } +#else + // This is the crash-on-hang feature. + // See bug 867313 for the quirk in the waitCount comparison if (waitCount >= 2) { int32_t delay = int32_t(PR_IntervalToSeconds(now - timestamp)); @@ -112,8 +391,17 @@ ThreadMain(void*) Crash(); } } - } - else { +#endif + } else { +#ifdef REPORT_CHROME_HANGS + if (waitCount >= 2) { + uint32_t hangDuration = PR_IntervalToSeconds(now - lastTimestamp); + Telemetry::RecordChromeHang(hangDuration, stack, systemUptime, + firefoxUptime, Move(annotations)); + stack.Clear(); + annotations = MakeUnique(); + } +#endif lastTimestamp = timestamp; waitCount = 0; } @@ -121,8 +409,7 @@ ThreadMain(void*) PRIntervalTime timeout; if (gTimeout <= 0) { timeout = PR_INTERVAL_NO_TIMEOUT; - } - else { + } else { timeout = PR_MillisecondsToInterval(gTimeout * 500); } lock.Wait(timeout); @@ -135,14 +422,25 @@ Startup() // The hang detector only runs in chrome processes. If you change this, // you must also deal with the threadsafety of AnnotateCrashReport in // non-chrome processes! - if (GoannaProcessType_Default != XRE_GetProcessType()) + if (GoannaProcessType_Default != XRE_GetProcessType()) { return; + } MOZ_ASSERT(!gMonitor, "Hang monitor already initialized"); gMonitor = new Monitor("HangMonitor"); - Preferences::RegisterCallback(PrefChanged, kHangMonitorPrefName, NULL); - PrefChanged(NULL, NULL); + Preferences::RegisterCallback(PrefChanged, kHangMonitorPrefName, nullptr); + PrefChanged(nullptr, nullptr); + +#ifdef REPORT_CHROME_HANGS + Preferences::RegisterCallback(PrefChanged, kTelemetryPrefName, nullptr); + winMainThreadHandle = + OpenThread(THREAD_ALL_ACCESS, FALSE, GetCurrentThreadId()); + if (!winMainThreadHandle) { + return; + } + gAnnotators = new std::set(); +#endif // Don't actually start measuring hangs until we hit the main event loop. // This potentially misses a small class of really early startup hangs, @@ -152,19 +450,21 @@ Startup() gThread = PR_CreateThread(PR_USER_THREAD, ThreadMain, - NULL, PR_PRIORITY_LOW, PR_GLOBAL_THREAD, + nullptr, PR_PRIORITY_LOW, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0); } void Shutdown() { - if (GoannaProcessType_Default != XRE_GetProcessType()) + if (GoannaProcessType_Default != XRE_GetProcessType()) { return; + } MOZ_ASSERT(gMonitor, "Hang monitor not started"); - { // Scope the lock we're going to delete later + { + // Scope the lock we're going to delete later MonitorAutoLock lock(*gMonitor); gShutdown = true; lock.Notify(); @@ -173,11 +473,16 @@ Shutdown() // thread creation could theoretically fail if (gThread) { PR_JoinThread(gThread); - gThread = NULL; + gThread = nullptr; } delete gMonitor; - gMonitor = NULL; + gMonitor = nullptr; + +#ifdef REPORT_CHROME_HANGS + // gAnnotators is a StaticAutoPtr, so we just need to null it out. + gAnnotators = nullptr; +#endif } static bool @@ -186,47 +491,47 @@ IsUIMessageWaiting() #ifndef XP_WIN return false; #else - #define NS_WM_IMEFIRST WM_IME_SETCONTEXT - #define NS_WM_IMELAST WM_IME_KEYUP +#define NS_WM_IMEFIRST WM_IME_SETCONTEXT +#define NS_WM_IMELAST WM_IME_KEYUP BOOL haveUIMessageWaiting = FALSE; MSG msg; - haveUIMessageWaiting |= ::PeekMessageW(&msg, NULL, WM_KEYFIRST, + haveUIMessageWaiting |= ::PeekMessageW(&msg, nullptr, WM_KEYFIRST, WM_IME_KEYLAST, PM_NOREMOVE); - haveUIMessageWaiting |= ::PeekMessageW(&msg, NULL, NS_WM_IMEFIRST, + haveUIMessageWaiting |= ::PeekMessageW(&msg, nullptr, NS_WM_IMEFIRST, NS_WM_IMELAST, PM_NOREMOVE); - haveUIMessageWaiting |= ::PeekMessageW(&msg, NULL, WM_MOUSEFIRST, + haveUIMessageWaiting |= ::PeekMessageW(&msg, nullptr, WM_MOUSEFIRST, WM_MOUSELAST, PM_NOREMOVE); return haveUIMessageWaiting; #endif } void -NotifyActivity(ActivityType activityType) +NotifyActivity(ActivityType aActivityType) { MOZ_ASSERT(NS_IsMainThread(), "HangMonitor::Notify called from off the main thread."); // Determine the activity type more specifically - if (activityType == kGeneralActivity) { - activityType = IsUIMessageWaiting() ? kActivityUIAVail : - kActivityNoUIAVail; + if (aActivityType == kGeneralActivity) { + aActivityType = IsUIMessageWaiting() ? kActivityUIAVail : + kActivityNoUIAVail; } // Calculate the cumulative amount of lag time since the last UI message static uint32_t cumulativeUILagMS = 0; - switch(activityType) { - case kActivityNoUIAVail: - cumulativeUILagMS = 0; - break; - case kActivityUIAVail: - case kUIActivity: - if (gTimestamp != PR_INTERVAL_NO_WAIT) { - cumulativeUILagMS += PR_IntervalToMilliseconds(PR_IntervalNow() - - gTimestamp); - } - break; - default: - break; + switch (aActivityType) { + case kActivityNoUIAVail: + cumulativeUILagMS = 0; + break; + case kActivityUIAVail: + case kUIActivity: + if (gTimestamp != PR_INTERVAL_NO_WAIT) { + cumulativeUILagMS += PR_IntervalToMilliseconds(PR_IntervalNow() - + gTimestamp); + } + break; + default: + break; } // This is not a locked activity because PRTimeStamp is a 32-bit quantity @@ -234,10 +539,22 @@ NotifyActivity(ActivityType activityType) // penalties here. gTimestamp = PR_IntervalNow(); - // If we have UI activity we should reset the timer. - if (activityType == kUIActivity) { + // If we have UI activity we should reset the timer and report it if it is + // significant enough. + if (aActivityType == kUIActivity) { + // The minimum amount of lag time that we should report for telemetry data. + // Mozilla's UI responsiveness goal is 50ms + static const uint32_t kUIResponsivenessThresholdMS = 50; + if (cumulativeUILagMS > kUIResponsivenessThresholdMS) { + mozilla::Telemetry::Accumulate(mozilla::Telemetry::EVENTLOOP_UI_LAG_EXP_MS, + cumulativeUILagMS); + } cumulativeUILagMS = 0; } + + if (gThread && !gShutdown) { + mozilla::BackgroundHangMonitor().NotifyActivity(); + } } void @@ -248,6 +565,37 @@ Suspend() // Because gTimestamp changes this resets the wait count. gTimestamp = PR_INTERVAL_NO_WAIT; + + if (gThread && !gShutdown) { + mozilla::BackgroundHangMonitor().NotifyWait(); + } +} + +void +RegisterAnnotator(Annotator& aAnnotator) +{ +#ifdef REPORT_CHROME_HANGS + if (GoannaProcessType_Default != XRE_GetProcessType()) { + return; + } + MonitorAutoLock lock(*gMonitor); + MOZ_ASSERT(gAnnotators); + gAnnotators->insert(&aAnnotator); +#endif +} + +void +UnregisterAnnotator(Annotator& aAnnotator) +{ +#ifdef REPORT_CHROME_HANGS + if (GoannaProcessType_Default != XRE_GetProcessType()) { + return; + } + MonitorAutoLock lock(*gMonitor); + MOZ_ASSERT(gAnnotators); + gAnnotators->erase(&aAnnotator); +#endif } -} } // namespace mozilla::HangMonitor +} // namespace HangMonitor +} // namespace mozilla diff --git a/xpcom/threads/HangMonitor.h b/xpcom/threads/HangMonitor.h index 65cbb0758..b8b2291e1 100644 --- a/xpcom/threads/HangMonitor.h +++ b/xpcom/threads/HangMonitor.h @@ -1,54 +1,112 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* 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/. */ - -#ifndef mozilla_HangMonitor_h -#define mozilla_HangMonitor_h - -namespace mozilla { namespace HangMonitor { - -/** - * Signifies the type of activity in question -*/ -enum ActivityType { - /* There is activity and it is known to be UI related activity. */ - kUIActivity, - - /* There is non UI activity and no UI activity is pending */ - kActivityNoUIAVail, - - /* There is non UI activity and UI activity is known to be pending */ - kActivityUIAVail, - - /* There is non UI activity and UI activity pending is unknown */ - kGeneralActivity -}; - -/** - * Start monitoring hangs. Should be called by the XPCOM startup process only. - */ -void Startup(); - -/** - * Stop monitoring hangs and join the thread. - */ -void Shutdown(); - -/** - * Notify the hang monitor of activity which will reset its internal timer. - * - * @param activityType The type of activity being reported. - * @see ActivityType - */ -void NotifyActivity(ActivityType activityType = kGeneralActivity); - -/* - * Notify the hang monitor that the browser is now idle and no detection should - * be done. - */ -void Suspend(); - -} } // namespace mozilla::HangMonitor - -#endif // mozilla_HangMonitor_h +/* -*- 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/. */ + +#ifndef mozilla_HangMonitor_h +#define mozilla_HangMonitor_h + +#include "mozilla/MemoryReporting.h" +#include "nsString.h" + +namespace mozilla { +namespace HangMonitor { + +/** + * Signifies the type of activity in question +*/ +enum ActivityType +{ + /* There is activity and it is known to be UI related activity. */ + kUIActivity, + + /* There is non UI activity and no UI activity is pending */ + kActivityNoUIAVail, + + /* There is non UI activity and UI activity is known to be pending */ + kActivityUIAVail, + + /* There is non UI activity and UI activity pending is unknown */ + kGeneralActivity +}; + +/** + * Start monitoring hangs. Should be called by the XPCOM startup process only. + */ +void Startup(); + +/** + * Stop monitoring hangs and join the thread. + */ +void Shutdown(); + +/** + * This class declares an abstraction for a data type that encapsulates all + * of the annotations being reported by a registered hang Annotator. + */ +class HangAnnotations +{ +public: + virtual ~HangAnnotations() {} + + virtual void AddAnnotation(const nsAString& aName, const int32_t aData) = 0; + virtual void AddAnnotation(const nsAString& aName, const double aData) = 0; + virtual void AddAnnotation(const nsAString& aName, const nsAString& aData) = 0; + virtual void AddAnnotation(const nsAString& aName, const nsACString& aData) = 0; + virtual void AddAnnotation(const nsAString& aName, const bool aData) = 0; + + class Enumerator + { + public: + virtual ~Enumerator() {} + virtual bool Next(nsAString& aOutName, nsAString& aOutValue) = 0; + }; + + virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const = 0; + virtual bool IsEmpty() const = 0; + virtual bool GetEnumerator(Enumerator **aOutEnum) = 0; +}; + +class Annotator +{ +public: + /** + * NB: This function is always called by the HangMonitor thread. + * Plan accordingly. + */ + virtual void AnnotateHang(HangAnnotations& aAnnotations) = 0; +}; + +/** + * Registers an Annotator to be called when a hang is detected. + * @param aAnnotator Reference to an object that implements the + * HangMonitor::Annotator interface. + */ +void RegisterAnnotator(Annotator& aAnnotator); + +/** + * Registers an Annotator that was previously registered via RegisterAnnotator. + * @param aAnnotator Reference to an object that implements the + * HangMonitor::Annotator interface. + */ +void UnregisterAnnotator(Annotator& aAnnotator); + +/** + * Notify the hang monitor of activity which will reset its internal timer. + * + * @param activityType The type of activity being reported. + * @see ActivityType + */ +void NotifyActivity(ActivityType activityType = kGeneralActivity); + +/* + * Notify the hang monitor that the browser is now idle and no detection should + * be done. + */ +void Suspend(); + +} // namespace HangMonitor +} // namespace mozilla + +#endif // mozilla_HangMonitor_h diff --git a/xpcom/threads/LazyIdleThread.cpp b/xpcom/threads/LazyIdleThread.cpp index 4ff4e38ca..647af3696 100644 --- a/xpcom/threads/LazyIdleThread.cpp +++ b/xpcom/threads/LazyIdleThread.cpp @@ -1,5 +1,5 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ +/* 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/. */ @@ -34,18 +34,18 @@ LazyIdleThread::LazyIdleThread(uint32_t aIdleTimeoutMS, const nsCSubstring& aName, ShutdownMethod aShutdownMethod, nsIObserver* aIdleObserver) -: mMutex("LazyIdleThread::mMutex"), - mOwningThread(NS_GetCurrentThread()), - mIdleObserver(aIdleObserver), - mQueuedRunnables(nullptr), - mIdleTimeoutMS(aIdleTimeoutMS), - mPendingEventCount(0), - mIdleNotificationCount(0), - mShutdownMethod(aShutdownMethod), - mShutdown(false), - mThreadIsShuttingDown(false), - mIdleTimeoutEnabled(true), - mName(aName) + : mMutex("LazyIdleThread::mMutex") + , mOwningThread(NS_GetCurrentThread()) + , mIdleObserver(aIdleObserver) + , mQueuedRunnables(nullptr) + , mIdleTimeoutMS(aIdleTimeoutMS) + , mPendingEventCount(0) + , mIdleNotificationCount(0) + , mShutdownMethod(aShutdownMethod) + , mShutdown(false) + , mThreadIsShuttingDown(false) + , mIdleTimeoutEnabled(true) + , mName(aName) { MOZ_ASSERT(mOwningThread, "Need owning thread!"); } @@ -147,21 +147,31 @@ LazyIdleThread::EnsureThread() if (mShutdownMethod == AutomaticShutdown && NS_IsMainThread()) { nsCOMPtr obs = do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv); - NS_ENSURE_SUCCESS(rv, rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } rv = obs->AddObserver(this, "xpcom-shutdown-threads", false); - NS_ENSURE_SUCCESS(rv, rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } } mIdleTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); - NS_ENSURE_TRUE(mIdleTimer, NS_ERROR_FAILURE); + if (NS_WARN_IF(!mIdleTimer)) { + return NS_ERROR_UNEXPECTED; + } nsCOMPtr runnable = NS_NewRunnableMethod(this, &LazyIdleThread::InitThread); - NS_ENSURE_TRUE(runnable, NS_ERROR_FAILURE); + if (NS_WARN_IF(!runnable)) { + return NS_ERROR_UNEXPECTED; + } rv = NS_NewThread(getter_AddRefs(mThread), runnable); - NS_ENSURE_SUCCESS(rv, rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } return NS_OK; } @@ -219,14 +229,16 @@ LazyIdleThread::ScheduleTimer() shouldSchedule = !mIdleNotificationCount && !mPendingEventCount; } - if (NS_FAILED(mIdleTimer->Cancel())) { - NS_WARNING("Failed to cancel timer!"); - } + if (mIdleTimer) { + if (NS_FAILED(mIdleTimer->Cancel())) { + NS_WARNING("Failed to cancel timer!"); + } - if (shouldSchedule && - NS_FAILED(mIdleTimer->InitWithCallback(this, mIdleTimeoutMS, - nsITimer::TYPE_ONE_SHOT))) { - NS_WARNING("Failed to schedule timer!"); + if (shouldSchedule && + NS_FAILED(mIdleTimer->InitWithCallback(this, mIdleTimeoutMS, + nsITimer::TYPE_ONE_SHOT))) { + NS_WARNING("Failed to schedule timer!"); + } } } @@ -242,6 +254,18 @@ LazyIdleThread::ShutdownThread() nsresult rv; + // Make sure to cancel the shutdown timer before spinning the event loop + // during |mThread->Shutdown()| below. Otherwise the timer might fire and we + // could reenter here. + if (mIdleTimer) { + rv = mIdleTimer->Cancel(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mIdleTimer = nullptr; + } + if (mThread) { if (mShutdownMethod == AutomaticShutdown && NS_IsMainThread()) { nsCOMPtr obs = @@ -268,12 +292,16 @@ LazyIdleThread::ShutdownThread() nsCOMPtr runnable = NS_NewRunnableMethod(this, &LazyIdleThread::CleanupThread); - NS_ENSURE_TRUE(runnable, NS_ERROR_FAILURE); + if (NS_WARN_IF(!runnable)) { + return NS_ERROR_UNEXPECTED; + } PreDispatch(); rv = mThread->Dispatch(runnable, NS_DISPATCH_NORMAL); - NS_ENSURE_SUCCESS(rv, rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } // Put the temporary queue in place before calling Shutdown(). mQueuedRunnables = &queuedRunnables; @@ -297,13 +325,6 @@ LazyIdleThread::ShutdownThread() } } - if (mIdleTimer) { - rv = mIdleTimer->Cancel(); - NS_ENSURE_SUCCESS(rv, rv); - - mIdleTimer = nullptr; - } - // If our temporary queue has any runnables then we need to dispatch them. if (queuedRunnables.Length()) { // If the thread manager has gone away then these runnables will never run. @@ -334,12 +355,12 @@ LazyIdleThread::SelfDestruct() delete this; } -NS_IMPL_THREADSAFE_ADDREF(LazyIdleThread) +NS_IMPL_ADDREF(LazyIdleThread) -NS_IMETHODIMP_(nsrefcnt) +NS_IMETHODIMP_(MozExternalRefCountType) LazyIdleThread::Release() { - nsrefcnt count = NS_AtomicDecrementRefcnt(mRefCnt); + nsrefcnt count = --mRefCnt; NS_LOG_RELEASE(this, count, "LazyIdleThread"); if (!count) { @@ -363,11 +384,11 @@ LazyIdleThread::Release() return count; } -NS_IMPL_THREADSAFE_QUERY_INTERFACE5(LazyIdleThread, nsIThread, - nsIEventTarget, - nsITimerCallback, - nsIThreadObserver, - nsIObserver) +NS_IMPL_QUERY_INTERFACE(LazyIdleThread, nsIThread, + nsIEventTarget, + nsITimerCallback, + nsIThreadObserver, + nsIObserver) NS_IMETHODIMP LazyIdleThread::Dispatch(nsIRunnable* aEvent, @@ -376,7 +397,13 @@ LazyIdleThread::Dispatch(nsIRunnable* aEvent, ASSERT_OWNING_THREAD(); // LazyIdleThread can't always support synchronous dispatch currently. - NS_ENSURE_TRUE(aFlags == NS_DISPATCH_NORMAL, NS_ERROR_NOT_IMPLEMENTED); + if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + if (NS_WARN_IF(mShutdown)) { + return NS_ERROR_UNEXPECTED; + } // If our thread is shutting down then we can't actually dispatch right now. // Queue this runnable for later. @@ -386,7 +413,9 @@ LazyIdleThread::Dispatch(nsIRunnable* aEvent, } nsresult rv = EnsureThread(); - NS_ENSURE_SUCCESS(rv, rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } PreDispatch(); @@ -427,7 +456,9 @@ LazyIdleThread::Shutdown() mIdleObserver = nullptr; - NS_ENSURE_SUCCESS(rv, rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } return NS_OK; } @@ -467,7 +498,9 @@ LazyIdleThread::Notify(nsITimer* aTimer) } nsresult rv = ShutdownThread(); - NS_ENSURE_SUCCESS(rv, rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } return NS_OK; } @@ -489,14 +522,17 @@ LazyIdleThread::OnProcessNextEvent(nsIThreadInternal* /* aThread */, NS_IMETHODIMP LazyIdleThread::AfterProcessNextEvent(nsIThreadInternal* /* aThread */, - uint32_t /* aRecursionDepth */) + uint32_t /* aRecursionDepth */, + bool aEventWasProcessed) { bool shouldNotifyIdle; { MutexAutoLock lock(mMutex); - MOZ_ASSERT(mPendingEventCount, "Mismatched calls to observer methods!"); - --mPendingEventCount; + if (aEventWasProcessed) { + MOZ_ASSERT(mPendingEventCount, "Mismatched calls to observer methods!"); + --mPendingEventCount; + } if (mThreadIsShuttingDown) { // We're shutting down, no need to fire any timer. @@ -513,10 +549,14 @@ LazyIdleThread::AfterProcessNextEvent(nsIThreadInternal* /* aThread */, if (shouldNotifyIdle) { nsCOMPtr runnable = NS_NewRunnableMethod(this, &LazyIdleThread::ScheduleTimer); - NS_ENSURE_TRUE(runnable, NS_ERROR_FAILURE); + if (NS_WARN_IF(!runnable)) { + return NS_ERROR_UNEXPECTED; + } nsresult rv = mOwningThread->Dispatch(runnable, NS_DISPATCH_NORMAL); - NS_ENSURE_SUCCESS(rv, rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } } return NS_OK; @@ -525,7 +565,7 @@ LazyIdleThread::AfterProcessNextEvent(nsIThreadInternal* /* aThread */, NS_IMETHODIMP LazyIdleThread::Observe(nsISupports* /* aSubject */, const char* aTopic, - const PRUnichar* /* aData */) + const char16_t* /* aData */) { MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); MOZ_ASSERT(mShutdownMethod == AutomaticShutdown, diff --git a/xpcom/threads/LazyIdleThread.h b/xpcom/threads/LazyIdleThread.h index 1c8cf0aef..bd3e43cd7 100644 --- a/xpcom/threads/LazyIdleThread.h +++ b/xpcom/threads/LazyIdleThread.h @@ -1,5 +1,5 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ +/* 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/. */ @@ -32,20 +32,22 @@ namespace mozilla { * is created on the main thread then it will automatically join its thread on * XPCOM shutdown using the Observer Service. */ -class LazyIdleThread MOZ_FINAL : public nsIThread, - public nsITimerCallback, - public nsIThreadObserver, - public nsIObserver +class LazyIdleThread final + : public nsIThread + , public nsITimerCallback + , public nsIThreadObserver + , public nsIObserver { public: - NS_DECL_ISUPPORTS + NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIEVENTTARGET NS_DECL_NSITHREAD NS_DECL_NSITIMERCALLBACK NS_DECL_NSITHREADOBSERVER NS_DECL_NSIOBSERVER - enum ShutdownMethod { + enum ShutdownMethod + { AutomaticShutdown = 0, ManualShutdown }; @@ -128,7 +130,8 @@ private: * Returns true if events should be queued rather than immediately dispatched * to mThread. Currently only happens when the thread is shutting down. */ - bool UseRunnableQueue() { + bool UseRunnableQueue() + { return !!mQueuedRunnables; } @@ -165,7 +168,7 @@ private: * Temporary storage for events that happen to be dispatched while we're in * the process of shutting down our real thread. */ - nsTArray >* mQueuedRunnables; + nsTArray>* mQueuedRunnables; /** * The number of milliseconds a thread should be idle before dying. diff --git a/xpcom/threads/Makefile.in b/xpcom/threads/Makefile.in deleted file mode 100644 index 2de09e5aa..000000000 --- a/xpcom/threads/Makefile.in +++ /dev/null @@ -1,26 +0,0 @@ -# -# 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/. - -DEPTH = @DEPTH@ -topsrcdir = @top_srcdir@ -srcdir = @srcdir@ -VPATH = @srcdir@ - -include $(DEPTH)/config/autoconf.mk - -MSVC_ENABLE_PGO := 1 -MOZILLA_INTERNAL_API = 1 -LIBXUL_LIBRARY = 1 - -LOCAL_INCLUDES = -I$(srcdir)/../components - -# we don't want the shared lib, but we want to force the creation of a static lib. -FORCE_STATIC_LIB = 1 - - -include $(topsrcdir)/config/rules.mk - -DEFINES += -D_IMPL_NS_COM - diff --git a/xpcom/threads/SyncRunnable.h b/xpcom/threads/SyncRunnable.h index 64a4edcd6..b0681a4fd 100644 --- a/xpcom/threads/SyncRunnable.h +++ b/xpcom/threads/SyncRunnable.h @@ -1,9 +1,11 @@ -/* -*- Mode: C++; tab-width: 12; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* -*- 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/. */ #ifndef mozilla_SyncRunnable_h +#define mozilla_SyncRunnable_h #include "nsThreadUtils.h" #include "mozilla/Monitor.h" @@ -29,48 +31,62 @@ namespace mozilla { class SyncRunnable : public nsRunnable { public: - SyncRunnable(nsIRunnable* r) - : mRunnable(r) + explicit SyncRunnable(nsIRunnable* aRunnable) + : mRunnable(aRunnable) , mMonitor("SyncRunnable") - { } + , mDone(false) + { + } - void DispatchToThread(nsIEventTarget* thread) + void DispatchToThread(nsIEventTarget* aThread, bool aForceDispatch = false) { nsresult rv; bool on; - rv = thread->IsOnCurrentThread(&on); - MOZ_ASSERT(NS_SUCCEEDED(rv)); - if (NS_SUCCEEDED(rv) && on) { - mRunnable->Run(); - return; + if (!aForceDispatch) { + rv = aThread->IsOnCurrentThread(&on); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + if (NS_SUCCEEDED(rv) && on) { + mRunnable->Run(); + return; + } } - mozilla::MonitorAutoLock lock(mMonitor); - rv = thread->Dispatch(this, NS_DISPATCH_NORMAL); + rv = aThread->Dispatch(this, NS_DISPATCH_NORMAL); if (NS_SUCCEEDED(rv)) { - lock.Wait(); + mozilla::MonitorAutoLock lock(mMonitor); + while (!mDone) { + lock.Wait(); + } } } - static void DispatchToThread(nsIEventTarget* thread, - nsIRunnable* r) + static void DispatchToThread(nsIEventTarget* aThread, + nsIRunnable* aRunnable, + bool aForceDispatch = false) { - nsRefPtr s(new SyncRunnable(r)); - s->DispatchToThread(thread); + nsRefPtr s(new SyncRunnable(aRunnable)); + s->DispatchToThread(aThread, aForceDispatch); } protected: NS_IMETHODIMP Run() { mRunnable->Run(); - mozilla::MonitorAutoLock(mMonitor).Notify(); + + mozilla::MonitorAutoLock lock(mMonitor); + MOZ_ASSERT(!mDone); + + mDone = true; + mMonitor.Notify(); + return NS_OK; } private: nsCOMPtr mRunnable; mozilla::Monitor mMonitor; + bool mDone; }; } // namespace mozilla diff --git a/xpcom/threads/TimerThread.cpp b/xpcom/threads/TimerThread.cpp index 883c40a3b..08a2651cd 100644 --- a/xpcom/threads/TimerThread.cpp +++ b/xpcom/threads/TimerThread.cpp @@ -1,5 +1,6 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- - * This Source Code Form is subject to the terms of the Mozilla Public +/* -*- 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/. */ @@ -12,20 +13,25 @@ #include "nsIObserverService.h" #include "nsIServiceManager.h" #include "mozilla/Services.h" +#include "mozilla/ChaosMode.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/BinarySearch.h" #include using namespace mozilla; -NS_IMPL_THREADSAFE_ISUPPORTS2(TimerThread, nsIRunnable, nsIObserver) +NS_IMPL_ISUPPORTS(TimerThread, nsIRunnable, nsIObserver) TimerThread::TimerThread() : - mInitInProgress(0), + mInitInProgress(false), mInitialized(false), mMonitor("TimerThread.mMonitor"), mShutdown(false), mWaiting(false), - mSleeping(false) + mNotified(false), + mSleeping(false), + mLastTimerEventLoopRun(TimeStamp::Now()) { } @@ -47,9 +53,10 @@ namespace { class TimerObserverRunnable : public nsRunnable { public: - TimerObserverRunnable(nsIObserver* observer) - : mObserver(observer) - { } + explicit TimerObserverRunnable(nsIObserver* aObserver) + : mObserver(aObserver) + { + } NS_DECL_NSIRUNNABLE @@ -73,29 +80,30 @@ TimerObserverRunnable::Run() } // anonymous namespace -nsresult TimerThread::Init() +nsresult +TimerThread::Init() { - PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("TimerThread::Init [%d]\n", mInitialized)); + PR_LOG(GetTimerLog(), PR_LOG_DEBUG, + ("TimerThread::Init [%d]\n", mInitialized)); if (mInitialized) { - if (!mThread) + if (!mThread) { return NS_ERROR_FAILURE; + } return NS_OK; } - if (PR_ATOMIC_SET(&mInitInProgress, 1) == 0) { + if (mInitInProgress.exchange(true) == false) { // We hold on to mThread to keep the thread alive. nsresult rv = NS_NewThread(getter_AddRefs(mThread), this); if (NS_FAILED(rv)) { mThread = nullptr; - } - else { + } else { nsRefPtr r = new TimerObserverRunnable(this); if (NS_IsMainThread()) { r->Run(); - } - else { + } else { NS_DispatchToMainThread(r); } } @@ -105,36 +113,41 @@ nsresult TimerThread::Init() mInitialized = true; mMonitor.NotifyAll(); } - } - else { + } else { MonitorAutoLock lock(mMonitor); while (!mInitialized) { mMonitor.Wait(); } } - if (!mThread) + if (!mThread) { return NS_ERROR_FAILURE; + } return NS_OK; } -nsresult TimerThread::Shutdown() +nsresult +TimerThread::Shutdown() { PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("TimerThread::Shutdown begin\n")); - if (!mThread) + if (!mThread) { return NS_ERROR_NOT_INITIALIZED; + } nsTArray timers; - { // lock scope + { + // lock scope MonitorAutoLock lock(mMonitor); mShutdown = true; // notify the cond var so that Run() can return - if (mWaiting) + if (mWaiting) { + mNotified = true; mMonitor.Notify(); + } // Need to copy content of mTimers array to a local array // because call to timers' ReleaseCallback() (and release its self) @@ -148,7 +161,7 @@ nsresult TimerThread::Shutdown() uint32_t timersCount = timers.Length(); for (uint32_t i = 0; i < timersCount; i++) { - nsTimerImpl *timer = timers[i]; + nsTimerImpl* timer = timers[i]; timer->ReleaseCallback(); ReleaseTimerInternal(timer); } @@ -159,51 +172,85 @@ nsresult TimerThread::Shutdown() return NS_OK; } +#ifdef MOZ_NUWA_PROCESS +#include "ipc/Nuwa.h" +#endif + +namespace { + +struct MicrosecondsToInterval +{ + PRIntervalTime operator[](size_t aMs) const { + return PR_MicrosecondsToInterval(aMs); + } +}; + +struct IntervalComparator +{ + int operator()(PRIntervalTime aInterval) const { + return (0 < aInterval) ? -1 : 1; + } +}; + +} // namespace + /* void Run(); */ -NS_IMETHODIMP TimerThread::Run() +NS_IMETHODIMP +TimerThread::Run() { PR_SetCurrentThreadName("Timer"); +#ifdef MOZ_NUWA_PROCESS + if (IsNuwaProcess()) { + NuwaMarkCurrentThread(nullptr, nullptr); + } +#endif + + NS_SetIgnoreStatusOfCurrentThread(); MonitorAutoLock lock(mMonitor); // We need to know how many microseconds give a positive PRIntervalTime. This - // is platform-dependent, we calculate it at runtime now. - // First we find a value such that PR_MicrosecondsToInterval(high) = 1 - int32_t low = 0, high = 1; - while (PR_MicrosecondsToInterval(high) == 0) - high <<= 1; - // We now have - // PR_MicrosecondsToInterval(low) = 0 - // PR_MicrosecondsToInterval(high) = 1 - // and we can proceed to find the critical value using binary search - while (high-low > 1) { - int32_t mid = (high+low) >> 1; - if (PR_MicrosecondsToInterval(mid) == 0) - low = mid; - else - high = mid; + // is platform-dependent and we calculate it at runtime, finding a value |v| + // such that |PR_MicrosecondsToInterval(v) > 0| and then binary-searching in + // the range [0, v) to find the ms-to-interval scale. + uint32_t usForPosInterval = 1; + while (PR_MicrosecondsToInterval(usForPosInterval) == 0) { + usForPosInterval <<= 1; } + size_t usIntervalResolution; + BinarySearchIf(MicrosecondsToInterval(), 0, usForPosInterval, IntervalComparator(), &usIntervalResolution); + MOZ_ASSERT(PR_MicrosecondsToInterval(usIntervalResolution - 1) == 0); + MOZ_ASSERT(PR_MicrosecondsToInterval(usIntervalResolution) == 1); + // Half of the amount of microseconds needed to get positive PRIntervalTime. // We use this to decide how to round our wait times later - int32_t halfMicrosecondsIntervalResolution = high >> 1; + int32_t halfMicrosecondsIntervalResolution = usIntervalResolution / 2; + bool forceRunNextTimer = false; while (!mShutdown) { // Have to use PRIntervalTime here, since PR_WaitCondVar takes it PRIntervalTime waitFor; + bool forceRunThisTimer = forceRunNextTimer; + forceRunNextTimer = false; if (mSleeping) { // Sleep for 0.1 seconds while not firing timers. - waitFor = PR_MillisecondsToInterval(100); + uint32_t milliseconds = 100; + if (ChaosMode::isActive(ChaosMode::TimerScheduling)) { + milliseconds = ChaosMode::randomUint32LessThan(200); + } + waitFor = PR_MillisecondsToInterval(milliseconds); } else { waitFor = PR_INTERVAL_NO_TIMEOUT; TimeStamp now = TimeStamp::Now(); - nsTimerImpl *timer = nullptr; + mLastTimerEventLoopRun = now; + nsTimerImpl* timer = nullptr; if (!mTimers.IsEmpty()) { timer = mTimers[0]; - if (now >= timer->mTimeout) { + if (now >= timer->mTimeout || forceRunThisTimer) { next: // NB: AddRef before the Release under RemoveTimerInternal to avoid // mRefCnt passing through zero, in case all other refs than the one @@ -232,14 +279,14 @@ NS_IMETHODIMP TimerThread::Run() // on the TimerThread instead of on the thread it targets. timerRef = nsTimerImpl::PostTimerEvent(timerRef.forget()); } - + if (timerRef) { // We got our reference back due to an error. // Unhook the nsRefPtr, and release manually so we can get the // refcount. - nsrefcnt rc = timerRef.forget().get()->Release(); + nsrefcnt rc = timerRef.forget().take()->Release(); (void)rc; - + // The nsITimer interface requires that its users keep a reference // to the timers they use while those timers are initialized but // have not yet fired. If this ever happens, it is a bug in the @@ -254,8 +301,9 @@ NS_IMETHODIMP TimerThread::Run() MOZ_ASSERT(rc != 0, "destroyed timer off its target thread!"); } - if (mShutdown) + if (mShutdown) { break; + } // Update now, as PostTimerEvent plus the locking may have taken a // tick or two, and we may goto next below. @@ -275,12 +323,29 @@ NS_IMETHODIMP TimerThread::Run() // resolution. We use halfMicrosecondsIntervalResolution, calculated // before, to do the optimal rounding (i.e., of how to decide what // interval is so small we should not wait at all). - double microseconds = (timeout - now).ToMilliseconds()*1000; - if (microseconds < halfMicrosecondsIntervalResolution) + double microseconds = (timeout - now).ToMilliseconds() * 1000; + + if (ChaosMode::isActive(ChaosMode::TimerScheduling)) { + // The mean value of sFractions must be 1 to ensure that + // the average of a long sequence of timeouts converges to the + // actual sum of their times. + static const float sFractions[] = { + 0.0f, 0.25f, 0.5f, 0.75f, 1.0f, 1.75f, 2.75f + }; + microseconds *= + sFractions[ChaosMode::randomUint32LessThan(ArrayLength(sFractions))]; + forceRunNextTimer = true; + } + + if (microseconds < halfMicrosecondsIntervalResolution) { + forceRunNextTimer = false; goto next; // round down; execute event now - waitFor = PR_MicrosecondsToInterval(static_cast(microseconds)); // Floor is accurate enough. - if (waitFor == 0) - waitFor = 1; // round up, wait the minimum time we can wait + } + waitFor = PR_MicrosecondsToInterval( + static_cast(microseconds)); // Floor is accurate enough. + if (waitFor == 0) { + waitFor = 1; // round up, wait the minimum time we can wait + } } #ifdef DEBUG_TIMERS @@ -296,30 +361,39 @@ NS_IMETHODIMP TimerThread::Run() } mWaiting = true; + mNotified = false; mMonitor.Wait(waitFor); + if (mNotified) { + forceRunNextTimer = false; + } mWaiting = false; } return NS_OK; } -nsresult TimerThread::AddTimer(nsTimerImpl *aTimer) +nsresult +TimerThread::AddTimer(nsTimerImpl* aTimer) { MonitorAutoLock lock(mMonitor); // Add the timer to our list. int32_t i = AddTimerInternal(aTimer); - if (i < 0) + if (i < 0) { return NS_ERROR_OUT_OF_MEMORY; + } // Awaken the timer thread. - if (mWaiting && i == 0) + if (mWaiting && i == 0) { + mNotified = true; mMonitor.Notify(); + } return NS_OK; } -nsresult TimerThread::TimerDelayChanged(nsTimerImpl *aTimer) +nsresult +TimerThread::TimerDelayChanged(nsTimerImpl* aTimer) { MonitorAutoLock lock(mMonitor); @@ -328,17 +402,21 @@ nsresult TimerThread::TimerDelayChanged(nsTimerImpl *aTimer) RemoveTimerInternal(aTimer); int32_t i = AddTimerInternal(aTimer); - if (i < 0) + if (i < 0) { return NS_ERROR_OUT_OF_MEMORY; + } // Awaken the timer thread. - if (mWaiting && i == 0) + if (mWaiting && i == 0) { + mNotified = true; mMonitor.Notify(); + } return NS_OK; } -nsresult TimerThread::RemoveTimer(nsTimerImpl *aTimer) +nsresult +TimerThread::RemoveTimer(nsTimerImpl* aTimer) { MonitorAutoLock lock(mMonitor); @@ -349,47 +427,63 @@ nsresult TimerThread::RemoveTimer(nsTimerImpl *aTimer) // TimerThread::Run, must wait for the mMonitor auto-lock here, and during the // wait Run drops the only remaining ref to aTimer via RemoveTimerInternal. - if (!RemoveTimerInternal(aTimer)) + if (!RemoveTimerInternal(aTimer)) { return NS_ERROR_NOT_AVAILABLE; + } // Awaken the timer thread. - if (mWaiting) + if (mWaiting) { + mNotified = true; mMonitor.Notify(); + } return NS_OK; } // This function must be called from within a lock -int32_t TimerThread::AddTimerInternal(nsTimerImpl *aTimer) +int32_t +TimerThread::AddTimerInternal(nsTimerImpl* aTimer) { mMonitor.AssertCurrentThreadOwns(); - if (mShutdown) + if (mShutdown) { return -1; + } TimeStamp now = TimeStamp::Now(); TimerAdditionComparator c(now, aTimer); nsTimerImpl** insertSlot = mTimers.InsertElementSorted(aTimer, c); - if (!insertSlot) + if (!insertSlot) { return -1; + } aTimer->mArmed = true; NS_ADDREF(aTimer); + +#ifdef MOZ_TASK_TRACER + // Create a FakeTracedTask, and dispatch it here. This is the start point of + // the latency. + aTimer->DispatchTracedTask(); +#endif + return insertSlot - mTimers.Elements(); } -bool TimerThread::RemoveTimerInternal(nsTimerImpl *aTimer) +bool +TimerThread::RemoveTimerInternal(nsTimerImpl* aTimer) { mMonitor.AssertCurrentThreadOwns(); - if (!mTimers.RemoveElement(aTimer)) + if (!mTimers.RemoveElement(aTimer)) { return false; + } ReleaseTimerInternal(aTimer); return true; } -void TimerThread::ReleaseTimerInternal(nsTimerImpl *aTimer) +void +TimerThread::ReleaseTimerInternal(nsTimerImpl* aTimer) { if (!mShutdown) { // copied to a local array before releasing in shutdown @@ -400,35 +494,58 @@ void TimerThread::ReleaseTimerInternal(nsTimerImpl *aTimer) NS_RELEASE(aTimer); } -void TimerThread::DoBeforeSleep() +void +TimerThread::DoBeforeSleep() { - // Main thread + // Mainthread MonitorAutoLock lock(mMonitor); + mLastTimerEventLoopRun = TimeStamp::Now(); mSleeping = true; } // Note: wake may be notified without preceding sleep notification -void TimerThread::DoAfterSleep() +void +TimerThread::DoAfterSleep() { - // Main thread + // Mainthread + TimeStamp now = TimeStamp::Now(); + MonitorAutoLock lock(mMonitor); + + // an over-estimate of time slept, usually small + TimeDuration slept = now - mLastTimerEventLoopRun; + + // Adjust all old timers to expire roughly similar times in the future + // compared to when we went to sleep, by adding the time we slept to the + // target time. It's slightly possible a few will end up slightly in the + // past and fire immediately, but ordering should be preserved. All + // timers retain the exact same order (and relative times) as before + // going to sleep. + for (uint32_t i = 0; i < mTimers.Length(); i ++) { + nsTimerImpl* timer = mTimers[i]; + timer->mTimeout += slept; + } mSleeping = false; - // Wake up the timer thread to re-process the array of timers, to - // ensure the sleep delay is correct and fire any expired timers. + mLastTimerEventLoopRun = now; + + // Wake up the timer thread to process the updated array + mNotified = true; mMonitor.Notify(); } /* void observe (in nsISupports aSubject, in string aTopic, in wstring aData); */ NS_IMETHODIMP -TimerThread::Observe(nsISupports* /* aSubject */, const char *aTopic, const PRUnichar* /* aData */) +TimerThread::Observe(nsISupports* /* aSubject */, const char* aTopic, + const char16_t* /* aData */) { if (strcmp(aTopic, "sleep_notification") == 0 || - strcmp(aTopic, "suspend_process_notification") == 0) + strcmp(aTopic, "suspend_process_notification") == 0) { DoBeforeSleep(); - else if (strcmp(aTopic, "wake_notification") == 0 || - strcmp(aTopic, "resume_process_notification") == 0) + } else if (strcmp(aTopic, "wake_notification") == 0 || + strcmp(aTopic, "resume_process_notification") == 0) { DoAfterSleep(); + } return NS_OK; } diff --git a/xpcom/threads/TimerThread.h b/xpcom/threads/TimerThread.h index 9c9dc5a78..02ebc15d5 100644 --- a/xpcom/threads/TimerThread.h +++ b/xpcom/threads/TimerThread.h @@ -1,5 +1,6 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- - * This Source Code Form is subject to the terms of the Mozilla Public +/* -*- 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/. */ @@ -15,12 +16,17 @@ #include "nsTArray.h" +#include "mozilla/Atomics.h" #include "mozilla/Attributes.h" #include "mozilla/Monitor.h" -#include "mozilla/TimeStamp.h" -class TimerThread MOZ_FINAL : public nsIRunnable, - public nsIObserver +namespace mozilla { +class TimeStamp; +} + +class TimerThread final + : public nsIRunnable + , public nsIObserver { public: typedef mozilla::Monitor Monitor; @@ -28,18 +34,18 @@ public: typedef mozilla::TimeDuration TimeDuration; TimerThread(); - NS_HIDDEN_(nsresult) InitLocks(); + nsresult InitLocks(); - NS_DECL_ISUPPORTS + NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIRUNNABLE NS_DECL_NSIOBSERVER - - NS_HIDDEN_(nsresult) Init(); - NS_HIDDEN_(nsresult) Shutdown(); - nsresult AddTimer(nsTimerImpl *aTimer); - nsresult TimerDelayChanged(nsTimerImpl *aTimer); - nsresult RemoveTimer(nsTimerImpl *aTimer); + nsresult Init(); + nsresult Shutdown(); + + nsresult AddTimer(nsTimerImpl* aTimer); + nsresult TimerDelayChanged(nsTimerImpl* aTimer); + nsresult RemoveTimer(nsTimerImpl* aTimer); void DoBeforeSleep(); void DoAfterSleep(); @@ -52,51 +58,57 @@ public: private: ~TimerThread(); - int32_t mInitInProgress; + mozilla::Atomic mInitInProgress; bool mInitialized; // These two internal helper methods must be called while mMonitor is held. // AddTimerInternal returns the position where the timer was added in the // list, or -1 if it failed. - int32_t AddTimerInternal(nsTimerImpl *aTimer); - bool RemoveTimerInternal(nsTimerImpl *aTimer); - void ReleaseTimerInternal(nsTimerImpl *aTimer); + int32_t AddTimerInternal(nsTimerImpl* aTimer); + bool RemoveTimerInternal(nsTimerImpl* aTimer); + void ReleaseTimerInternal(nsTimerImpl* aTimer); nsCOMPtr mThread; Monitor mMonitor; bool mShutdown; bool mWaiting; + bool mNotified; bool mSleeping; - + TimeStamp mLastTimerEventLoopRun; + nsTArray mTimers; }; -struct TimerAdditionComparator { - TimerAdditionComparator(const mozilla::TimeStamp &aNow, - nsTimerImpl *aTimerToInsert) : +struct TimerAdditionComparator +{ + TimerAdditionComparator(const mozilla::TimeStamp& aNow, + nsTimerImpl* aTimerToInsert) : now(aNow) #ifdef DEBUG , timerToInsert(aTimerToInsert) #endif - {} + { + } - bool LessThan(nsTimerImpl *fromArray, nsTimerImpl *newTimer) const { - NS_ABORT_IF_FALSE(newTimer == timerToInsert, "Unexpected timer ordering"); + bool LessThan(nsTimerImpl* aFromArray, nsTimerImpl* aNewTimer) const + { + MOZ_ASSERT(aNewTimer == timerToInsert, "Unexpected timer ordering"); // Skip any overdue timers. - return fromArray->mTimeout <= now || - fromArray->mTimeout <= newTimer->mTimeout; + return aFromArray->mTimeout <= now || + aFromArray->mTimeout <= aNewTimer->mTimeout; } - bool Equals(nsTimerImpl* fromArray, nsTimerImpl* newTimer) const { + bool Equals(nsTimerImpl* aFromArray, nsTimerImpl* aNewTimer) const + { return false; } private: - const mozilla::TimeStamp &now; + const mozilla::TimeStamp& now; #ifdef DEBUG - const nsTimerImpl * const timerToInsert; + const nsTimerImpl* const timerToInsert; #endif }; diff --git a/xpcom/threads/moz.build b/xpcom/threads/moz.build index 920864bc0..f07923c80 100644 --- a/xpcom/threads/moz.build +++ b/xpcom/threads/moz.build @@ -20,32 +20,49 @@ XPIDL_SOURCES += [ XPIDL_MODULE = 'xpcom_threads' -MODULE = 'xpcom' - EXPORTS += [ 'nsEventQueue.h', + 'nsMemoryPressure.h', 'nsProcess.h', 'nsThread.h', ] EXPORTS.mozilla += [ + 'BackgroundHangMonitor.h', 'HangMonitor.h', 'LazyIdleThread.h', 'SyncRunnable.h', ] -CPP_SOURCES += [ +UNIFIED_SOURCES += [ + 'BackgroundHangMonitor.cpp', 'HangMonitor.cpp', 'LazyIdleThread.cpp', - 'TimerThread.cpp', 'nsEnvironment.cpp', 'nsEventQueue.cpp', + 'nsMemoryPressure.cpp', 'nsProcessCommon.cpp', 'nsThread.cpp', 'nsThreadManager.cpp', 'nsThreadPool.cpp', 'nsTimerImpl.cpp', + 'TimerThread.cpp', +] + +MSVC_ENABLE_PGO = True + +LOCAL_INCLUDES += [ + '../build', + '/caps', ] -LIBRARY_NAME = 'xpcomthreads_s' +# BHR disabled for Release builds because of bug 965392. +# BHR disabled for debug builds because of bug 979069. +if CONFIG['MOZ_UPDATE_CHANNEL'] not in ('release') and not CONFIG['MOZ_DEBUG']: + DEFINES['MOZ_ENABLE_BACKGROUND_HANG_MONITOR'] = 1 + +FAIL_ON_WARNINGS = True + +FINAL_LIBRARY = 'xul' +include('/ipc/chromium/chromium-config.mozbuild') diff --git a/xpcom/threads/nsEnvironment.cpp b/xpcom/threads/nsEnvironment.cpp index 01b20a0bb..4648c9c2e 100644 --- a/xpcom/threads/nsEnvironment.cpp +++ b/xpcom/threads/nsEnvironment.cpp @@ -1,4 +1,5 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* -*- 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/. */ @@ -14,29 +15,28 @@ using namespace mozilla; -NS_IMPL_THREADSAFE_ISUPPORTS1(nsEnvironment, nsIEnvironment) +NS_IMPL_ISUPPORTS(nsEnvironment, nsIEnvironment) nsresult -nsEnvironment::Create(nsISupports *aOuter, REFNSIID aIID, - void **aResult) +nsEnvironment::Create(nsISupports* aOuter, REFNSIID aIID, void** aResult) { - nsresult rv; - *aResult = nullptr; - - if (aOuter != nullptr) { - return NS_ERROR_NO_AGGREGATION; - } - - nsEnvironment* obj = new nsEnvironment(); - if (!obj) { - return NS_ERROR_OUT_OF_MEMORY; - } - - rv = obj->QueryInterface(aIID, aResult); - if (NS_FAILED(rv)) { - delete obj; - } - return rv; + nsresult rv; + *aResult = nullptr; + + if (aOuter) { + return NS_ERROR_NO_AGGREGATION; + } + + nsEnvironment* obj = new nsEnvironment(); + if (!obj) { + return NS_ERROR_OUT_OF_MEMORY; + } + + rv = obj->QueryInterface(aIID, aResult); + if (NS_FAILED(rv)) { + delete obj; + } + return rv; } nsEnvironment::~nsEnvironment() @@ -44,113 +44,123 @@ nsEnvironment::~nsEnvironment() } NS_IMETHODIMP -nsEnvironment::Exists(const nsAString& aName, bool *aOutValue) +nsEnvironment::Exists(const nsAString& aName, bool* aOutValue) { - nsAutoCString nativeName; - nsresult rv = NS_CopyUnicodeToNative(aName, nativeName); - NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString nativeName; + nsresult rv = NS_CopyUnicodeToNative(aName, nativeName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } - nsAutoCString nativeVal; + nsAutoCString nativeVal; #if defined(XP_UNIX) - /* For Unix/Linux platforms we follow the Unix definition: - * An environment variable exists when |getenv()| returns a non-NULL value. - * An environment variable does not exist when |getenv()| returns NULL. - */ - const char *value = PR_GetEnv(nativeName.get()); - *aOutValue = value && *value; + /* For Unix/Linux platforms we follow the Unix definition: + * An environment variable exists when |getenv()| returns a non-nullptr + * value. An environment variable does not exist when |getenv()| returns + * nullptr. + */ + const char* value = PR_GetEnv(nativeName.get()); + *aOutValue = value && *value; #else - /* For non-Unix/Linux platforms we have to fall back to a - * "portable" definition (which is incorrect for Unix/Linux!!!!) - * which simply checks whether the string returned by |Get()| is empty - * or not. - */ - nsAutoString value; - Get(aName, value); - *aOutValue = !value.IsEmpty(); + /* For non-Unix/Linux platforms we have to fall back to a + * "portable" definition (which is incorrect for Unix/Linux!!!!) + * which simply checks whether the string returned by |Get()| is empty + * or not. + */ + nsAutoString value; + Get(aName, value); + *aOutValue = !value.IsEmpty(); #endif /* XP_UNIX */ - return NS_OK; + return NS_OK; } NS_IMETHODIMP nsEnvironment::Get(const nsAString& aName, nsAString& aOutValue) { - nsAutoCString nativeName; - nsresult rv = NS_CopyUnicodeToNative(aName, nativeName); - NS_ENSURE_SUCCESS(rv, rv); - - nsAutoCString nativeVal; - const char *value = PR_GetEnv(nativeName.get()); - if (value && *value) { - rv = NS_CopyNativeToUnicode(nsDependentCString(value), aOutValue); - } else { - aOutValue.Truncate(); - rv = NS_OK; - } - + nsAutoCString nativeName; + nsresult rv = NS_CopyUnicodeToNative(aName, nativeName); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; + } + + nsAutoCString nativeVal; + const char* value = PR_GetEnv(nativeName.get()); + if (value && *value) { + rv = NS_CopyNativeToUnicode(nsDependentCString(value), aOutValue); + } else { + aOutValue.Truncate(); + rv = NS_OK; + } + + return rv; } /* Environment strings must have static duration; We're gonna leak all of this * at shutdown: this is by design, caused how Unix/Linux implement environment - * vars. + * vars. */ -typedef nsBaseHashtableET EnvEntryType; +typedef nsBaseHashtableET EnvEntryType; typedef nsTHashtable EnvHashType; -static EnvHashType *gEnvHash = nullptr; +static EnvHashType* gEnvHash = nullptr; static bool EnsureEnvHash() { - if (gEnvHash) - return true; + if (gEnvHash) { + return true; + } - gEnvHash = new EnvHashType; - if (!gEnvHash) - return false; + gEnvHash = new EnvHashType; + if (!gEnvHash) { + return false; + } - gEnvHash->Init(); - return true; + return true; } NS_IMETHODIMP nsEnvironment::Set(const nsAString& aName, const nsAString& aValue) { - nsAutoCString nativeName; - nsAutoCString nativeVal; - - nsresult rv = NS_CopyUnicodeToNative(aName, nativeName); - NS_ENSURE_SUCCESS(rv, rv); - - rv = NS_CopyUnicodeToNative(aValue, nativeVal); - NS_ENSURE_SUCCESS(rv, rv); - - MutexAutoLock lock(mLock); - - if (!EnsureEnvHash()){ - return NS_ERROR_UNEXPECTED; - } - - EnvEntryType* entry = gEnvHash->PutEntry(nativeName.get()); - if (!entry) { - return NS_ERROR_OUT_OF_MEMORY; - } - - char* newData = PR_smprintf("%s=%s", - nativeName.get(), - nativeVal.get()); - if (!newData) { - return NS_ERROR_OUT_OF_MEMORY; - } - - PR_SetEnv(newData); - if (entry->mData) { - PR_smprintf_free(entry->mData); - } - entry->mData = newData; - return NS_OK; + nsAutoCString nativeName; + nsAutoCString nativeVal; + + nsresult rv = NS_CopyUnicodeToNative(aName, nativeName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = NS_CopyUnicodeToNative(aValue, nativeVal); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MutexAutoLock lock(mLock); + + if (!EnsureEnvHash()) { + return NS_ERROR_UNEXPECTED; + } + + EnvEntryType* entry = gEnvHash->PutEntry(nativeName.get()); + if (!entry) { + return NS_ERROR_OUT_OF_MEMORY; + } + + char* newData = PR_smprintf("%s=%s", + nativeName.get(), + nativeVal.get()); + if (!newData) { + return NS_ERROR_OUT_OF_MEMORY; + } + + PR_SetEnv(newData); + if (entry->mData) { + PR_smprintf_free(entry->mData); + } + entry->mData = newData; + return NS_OK; } diff --git a/xpcom/threads/nsEnvironment.h b/xpcom/threads/nsEnvironment.h index a74ecbab7..234055a07 100644 --- a/xpcom/threads/nsEnvironment.h +++ b/xpcom/threads/nsEnvironment.h @@ -1,4 +1,5 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* -*- 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/. */ @@ -15,20 +16,21 @@ { 0X9B, 0XE9, 0X7C, 0XB2, 0X39, 0X87, 0X41, 0X72 } } #define NS_ENVIRONMENT_CONTRACTID "@mozilla.org/process/environment;1" -class nsEnvironment MOZ_FINAL : public nsIEnvironment +class nsEnvironment final : public nsIEnvironment { public: - NS_DECL_ISUPPORTS - NS_DECL_NSIENVIRONMENT + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIENVIRONMENT - static nsresult Create(nsISupports *aOuter, REFNSIID aIID, - void **aResult); + static nsresult Create(nsISupports* aOuter, REFNSIID aIID, void** aResult); private: - nsEnvironment() : mLock("nsEnvironment.mLock") { } - ~nsEnvironment(); + nsEnvironment() : mLock("nsEnvironment.mLock") + { + } + ~nsEnvironment(); - mozilla::Mutex mLock; + mozilla::Mutex mLock; }; #endif /* !nsEnvironment_h__ */ diff --git a/xpcom/threads/nsEventQueue.cpp b/xpcom/threads/nsEventQueue.cpp index 2b36b1f74..9e50c64b9 100644 --- a/xpcom/threads/nsEventQueue.cpp +++ b/xpcom/threads/nsEventQueue.cpp @@ -1,5 +1,5 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* -*- 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/. */ @@ -8,19 +8,25 @@ #include "nsAutoPtr.h" #include "prlog.h" #include "nsThreadUtils.h" +#include "prthread.h" +#include "mozilla/ChaosMode.h" using namespace mozilla; #ifdef PR_LOGGING -static PRLogModuleInfo * +static PRLogModuleInfo* GetLog() { - static PRLogModuleInfo *sLog; - if (!sLog) + static PRLogModuleInfo* sLog; + if (!sLog) { sLog = PR_NewLogModule("nsEventQueue"); + } return sLog; } #endif +#ifdef LOG +#undef LOG +#endif #define LOG(args) PR_LOG(GetLog(), PR_LOG_DEBUG, args) nsEventQueue::nsEventQueue() @@ -36,35 +42,38 @@ nsEventQueue::~nsEventQueue() { // It'd be nice to be able to assert that no one else is holding the monitor, // but NSPR doesn't really expose APIs for it. - NS_ASSERTION(IsEmpty(), "Non-empty event queue being destroyed; events being leaked."); + NS_ASSERTION(IsEmpty(), + "Non-empty event queue being destroyed; events being leaked."); - if (mHead) + if (mHead) { FreePage(mHead); + } } bool -nsEventQueue::GetEvent(bool mayWait, nsIRunnable **result) +nsEventQueue::GetEvent(bool aMayWait, nsIRunnable** aResult) { { ReentrantMonitorAutoEnter mon(mReentrantMonitor); - + while (IsEmpty()) { - if (!mayWait) { - if (result) - *result = nullptr; + if (!aMayWait) { + if (aResult) { + *aResult = nullptr; + } return false; } - LOG(("EVENTQ(%p): wait begin\n", this)); + LOG(("EVENTQ(%p): wait begin\n", this)); mon.Wait(); - LOG(("EVENTQ(%p): wait end\n", this)); + LOG(("EVENTQ(%p): wait end\n", this)); } - - if (result) { - *result = mHead->mEvents[mOffsetHead++]; - + + if (aResult) { + *aResult = mHead->mEvents[mOffsetHead++]; + // Check if mHead points to empty Page if (mOffsetHead == EVENTS_PER_PAGE) { - Page *dead = mHead; + Page* dead = mHead; mHead = mHead->mNext; FreePage(dead); mOffsetHead = 0; @@ -75,40 +84,40 @@ nsEventQueue::GetEvent(bool mayWait, nsIRunnable **result) return true; } -bool -nsEventQueue::PutEvent(nsIRunnable *runnable) +void +nsEventQueue::PutEvent(nsIRunnable* aRunnable) { // Avoid calling AddRef+Release while holding our monitor. - nsRefPtr event(runnable); - bool rv = true; - { - ReentrantMonitorAutoEnter mon(mReentrantMonitor); + nsRefPtr event(aRunnable); - if (!mHead) { - mHead = NewPage(); - if (!mHead) { - rv = false; - } else { - mTail = mHead; - mOffsetHead = 0; - mOffsetTail = 0; - } - } else if (mOffsetTail == EVENTS_PER_PAGE) { - Page *page = NewPage(); - if (!page) { - rv = false; - } else { - mTail->mNext = page; - mTail = page; - mOffsetTail = 0; - } - } - if (rv) { - event.swap(mTail->mEvents[mOffsetTail]); - ++mOffsetTail; - LOG(("EVENTQ(%p): notify\n", this)); - mon.NotifyAll(); + if (ChaosMode::isActive(ChaosMode::ThreadScheduling)) { + // With probability 0.5, yield so other threads have a chance to + // dispatch events to this queue first. + if (ChaosMode::randomUint32LessThan(2)) { + PR_Sleep(PR_INTERVAL_NO_WAIT); } } - return rv; + + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + if (!mHead) { + mHead = NewPage(); + MOZ_ASSERT(mHead); + + mTail = mHead; + mOffsetHead = 0; + mOffsetTail = 0; + } else if (mOffsetTail == EVENTS_PER_PAGE) { + Page* page = NewPage(); + MOZ_ASSERT(page); + + mTail->mNext = page; + mTail = page; + mOffsetTail = 0; + } + + event.swap(mTail->mEvents[mOffsetTail]); + ++mOffsetTail; + LOG(("EVENTQ(%p): notify\n", this)); + mon.NotifyAll(); } diff --git a/xpcom/threads/nsEventQueue.h b/xpcom/threads/nsEventQueue.h index 7cb08bcad..18d8d0861 100644 --- a/xpcom/threads/nsEventQueue.h +++ b/xpcom/threads/nsEventQueue.h @@ -1,5 +1,5 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* -*- 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/. */ @@ -20,11 +20,10 @@ public: nsEventQueue(); ~nsEventQueue(); - // This method adds a new event to the pending event queue. The event object - // is AddRef'd if this method succeeds. This method returns true if the - // event was stored in the event queue, and it returns false if it could - // not allocate sufficient memory. - bool PutEvent(nsIRunnable *event); + // This method adds a new event to the pending event queue. The queue holds + // a strong reference to the event after this method returns. This method + // cannot fail. + void PutEvent(nsIRunnable* aEvent); // This method gets an event from the event queue. If mayWait is true, then // the method will block the calling thread until an event is available. If @@ -32,55 +31,63 @@ public: // or not an event is pending. When the resulting event is non-null, the // caller is responsible for releasing the event object. This method does // not alter the reference count of the resulting event. - bool GetEvent(bool mayWait, nsIRunnable **event); + bool GetEvent(bool aMayWait, nsIRunnable** aEvent); // This method returns true if there is a pending event. - bool HasPendingEvent() { + bool HasPendingEvent() + { return GetEvent(false, nullptr); } // This method returns the next pending event or null. - bool GetPendingEvent(nsIRunnable **runnable) { + bool GetPendingEvent(nsIRunnable** runnable) + { return GetEvent(false, runnable); } - // This method waits for and returns the next pending event. - bool WaitPendingEvent(nsIRunnable **runnable) { - return GetEvent(true, runnable); - } - // Expose the event queue's monitor for "power users" - ReentrantMonitor& GetReentrantMonitor() { + ReentrantMonitor& GetReentrantMonitor() + { return mReentrantMonitor; } private: - bool IsEmpty() { + bool IsEmpty() + { return !mHead || (mHead == mTail && mOffsetHead == mOffsetTail); } - enum { EVENTS_PER_PAGE = 250 }; + enum + { + EVENTS_PER_PAGE = 255 + }; // Page objects are linked together to form a simple deque. - struct Page { - struct Page *mNext; - nsIRunnable *mEvents[EVENTS_PER_PAGE]; + struct Page + { + struct Page* mNext; + nsIRunnable* mEvents[EVENTS_PER_PAGE]; }; - static Page *NewPage() { - return static_cast(calloc(1, sizeof(Page))); + static_assert((sizeof(Page) & (sizeof(Page) - 1)) == 0, + "sizeof(Page) should be a power of two to avoid heap slop."); + + static Page* NewPage() + { + return static_cast(moz_xcalloc(1, sizeof(Page))); } - static void FreePage(Page *p) { - free(p); + static void FreePage(Page* aPage) + { + free(aPage); } ReentrantMonitor mReentrantMonitor; - Page *mHead; - Page *mTail; + Page* mHead; + Page* mTail; uint16_t mOffsetHead; // offset into mHead where next item is removed uint16_t mOffsetTail; // offset into mTail where next item is added diff --git a/xpcom/threads/nsIProcess.idl b/xpcom/threads/nsIProcess.idl index 49e57175b..2c7dcd55e 100644 --- a/xpcom/threads/nsIProcess.idl +++ b/xpcom/threads/nsIProcess.idl @@ -2,9 +2,9 @@ * 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 "nsIFile.idl" #include "nsISupports.idl" +interface nsIFile; interface nsIObserver; [scriptable, uuid(609610de-9954-4a63-8a7c-346350a86403)] diff --git a/xpcom/threads/nsIThreadInternal.idl b/xpcom/threads/nsIThreadInternal.idl index e0e31987f..1c2782e4c 100644 --- a/xpcom/threads/nsIThreadInternal.idl +++ b/xpcom/threads/nsIThreadInternal.idl @@ -6,14 +6,14 @@ #include "nsIThread.idl" +interface nsIRunnable; interface nsIThreadObserver; -interface nsIThreadEventFilter; /** * The XPCOM thread object implements this interface, which allows a consumer * to observe dispatch activity on the thread. */ -[scriptable, uuid(504e9e1f-70e1-4f33-a785-5840a4680414)] +[scriptable, uuid(b24c5af3-43c2-4d17-be14-94d6648a305f)] interface nsIThreadInternal : nsIThread { /** @@ -33,11 +33,12 @@ interface nsIThreadInternal : nsIThread readonly attribute unsigned long recursionDepth; /** - * Add an observer that will *only* receive onProcessNextEvent and - * afterProcessNextEvent callbacks. Always called on the target thread, and - * the implementation does not have to be threadsafe. Order of callbacks is - * not guaranteed (i.e. afterProcessNextEvent may be called first depending on - * whether or not the observer is added in a nested loop). Holds a strong ref. + * Add an observer that will *only* receive onProcessNextEvent, + * beforeProcessNextEvent. and afterProcessNextEvent callbacks. Always called + * on the target thread, and the implementation does not have to be + * threadsafe. Order of callbacks is not guaranteed (i.e. + * afterProcessNextEvent may be called first depending on whether or not the + * observer is added in a nested loop). Holds a strong ref. */ void addObserver(in nsIThreadObserver observer); @@ -46,6 +47,28 @@ interface nsIThreadInternal : nsIThread * observer will never be called again by the thread. */ void removeObserver(in nsIThreadObserver observer); + + /** + * This method causes any events currently enqueued on the thread to be + * suppressed until PopEventQueue is called, and any event dispatched to this + * thread's nsIEventTarget will queue as well. Calls to PushEventQueue may be + * nested and must each be paired with a call to PopEventQueue in order to + * restore the original state of the thread. The returned nsIEventTarget may + * be used to push events onto the nested queue. Dispatching will be disabled + * once the event queue is popped. The thread will only ever process pending + * events for the innermost event queue. Must only be called on the target + * thread. + */ + [noscript] nsIEventTarget pushEventQueue(); + + /** + * Revert a call to PushEventQueue. When an event queue is popped, any events + * remaining in the queue are appended to the elder queue. This also causes + * the nsIEventTarget returned from PushEventQueue to stop dispatching events. + * Must only be called on the target thread, and with the innermost event + * queue. + */ + [noscript] void popEventQueue(in nsIEventTarget aInnermostTarget); }; /** @@ -77,7 +100,7 @@ interface nsIThreadInternal : nsIThread * afterProcessNextEvent, then another that inherits the first and adds * onDispatchedEvent. */ -[scriptable, uuid(81D0B509-F198-4417-8020-08EB4271491F)] +[scriptable, uuid(09b424c3-26b0-4128-9039-d66f85b02c63)] interface nsIThreadObserver : nsISupports { /** @@ -90,8 +113,9 @@ interface nsIThreadObserver : nsISupports void onDispatchedEvent(in nsIThreadInternal thread); /** - * This method is called (from nsIThread::ProcessNextEvent) before an event - * is processed. This method is only called on the target thread. + * This method is called when nsIThread::ProcessNextEvent is called. It does + * not guarantee that an event is actually going to be processed. This method + * is only called on the target thread. * * @param thread * The thread being asked to process another event. @@ -107,14 +131,20 @@ interface nsIThreadObserver : nsISupports /** * This method is called (from nsIThread::ProcessNextEvent) after an event - * is processed. This method is only called on the target thread. + * is processed. It does not guarantee that an event was actually processed + * (depends on the value of |eventWasProcessed|. This method is only called + * on the target thread. * * @param thread * The thread that processed another event. * @param recursionDepth * Indicates the number of calls to ProcessNextEvent on the call stack in * addition to the current call. + * @param eventWasProcessed + * Indicates whether an event was actually processed. May be false if the + * |mayWait| flag was false when calling nsIThread::ProcessNextEvent(). */ void afterProcessNextEvent(in nsIThreadInternal thread, - in unsigned long recursionDepth); + in unsigned long recursionDepth, + in bool eventWasProcessed); }; diff --git a/xpcom/threads/nsIThreadManager.idl b/xpcom/threads/nsIThreadManager.idl index 7ae5e23b2..9b4fc126f 100644 --- a/xpcom/threads/nsIThreadManager.idl +++ b/xpcom/threads/nsIThreadManager.idl @@ -13,7 +13,7 @@ interface nsIThread; /** * An interface for creating and locating nsIThread instances. */ -[scriptable, uuid(2bbbc38c-cf96-4099-ba6b-f6a44d8b014c)] +[scriptable, uuid(1be89eca-e2f7-453b-8d38-c11ba247f6f3)] interface nsIThreadManager : nsISupports { /** @@ -65,10 +65,4 @@ interface nsIThreadManager : nsISupports * application process. */ readonly attribute boolean isMainThread; - - /** - * This attribute is true if the calling thread is the thread on which the - * cycle collector runs. - */ - readonly attribute boolean isCycleCollectorThread; }; diff --git a/xpcom/threads/nsIThreadPool.idl b/xpcom/threads/nsIThreadPool.idl index c90ce200e..97d419c74 100644 --- a/xpcom/threads/nsIThreadPool.idl +++ b/xpcom/threads/nsIThreadPool.idl @@ -27,7 +27,7 @@ interface nsIThreadPoolListener : nsISupports * anonymous (unnamed) worker threads. An event dispatched to the thread pool * will be run on the next available worker thread. */ -[scriptable, uuid(ba9a466b-8d4a-4b33-ae5c-6ed751068c90)] +[scriptable, uuid(53675068-cb3a-40e5-a026-1be5a97c9b23)] interface nsIThreadPool : nsIEventTarget { /** @@ -36,12 +36,14 @@ interface nsIThreadPool : nsIEventTarget * thread (usually the thread that created this thread pool). When this * function returns, the thread pool and all of its threads will be shutdown, * and it will no longer be possible to dispatch tasks to the thread pool. + * + * As a side effect, events on the current thread will be processed. */ void shutdown(); /** * Get/set the maximum number of threads allowed at one time in this pool. - */ + */ attribute unsigned long threadLimit; /** @@ -55,6 +57,12 @@ interface nsIThreadPool : nsIEventTarget */ attribute unsigned long idleThreadTimeout; + /** + * Get/set the number of bytes reserved for the stack of all threads in + * the pool. By default this is nsIThreadManager::DEFAULT_STACK_SIZE. + */ + attribute unsigned long threadStackSize; + /** * An optional listener that will be notified when a thread is created or * destroyed in the course of the thread pool's operation. diff --git a/xpcom/threads/nsITimer.idl b/xpcom/threads/nsITimer.idl index d952a186d..b495be8d7 100644 --- a/xpcom/threads/nsITimer.idl +++ b/xpcom/threads/nsITimer.idl @@ -9,6 +9,8 @@ interface nsIObserver; interface nsIEventTarget; %{C++ +#include "mozilla/MemoryReporting.h" + /** * The signature of the timer callback function passed to initWithFuncCallback. * This is the function that will get called when the timer expires if the @@ -63,7 +65,7 @@ interface nsITimer : nsISupports /** * Type of a timer that fires once only. */ - const short TYPE_ONE_SHOT = 0; + const short TYPE_ONE_SHOT = 0; /** * After firing, a TYPE_REPEATING_SLACK timer is stopped and not restarted @@ -73,15 +75,28 @@ interface nsITimer : nsISupports * * This is the preferable repeating type for most situations. */ - const short TYPE_REPEATING_SLACK = 1; + const short TYPE_REPEATING_SLACK = 1; /** - * TYPE_REPEATING_PRECISE is just a synonym for - * TYPE_REPEATING_PRECISE_CAN_SKIP. They used to be distinct, but the old - * TYPE_REPEATING_PRECISE kind was similar to TYPE_REPEATING_PRECISE_CAN_SKIP - * while also being less useful and causing issues, So the distinction was removed. + * An TYPE_REPEATING_PRECISE repeating timer aims to have constant period + * between firings. The processing time for each timer callback should not + * influence the timer period. However, if the processing for the last + * timer firing could not be completed until just before the next firing + * occurs, then you could have two timer notification routines being + * executed in quick succession. Furthermore, if your callback processing + * time is longer than the timer period, then the timer will post more + * notifications while your callback is running. For example, if a + * REPEATING_PRECISE timer has a 10ms period and a callback takes 50ms, + * then by the time the callback is done there will be 5 events to run the + * timer callback in the event queue. Furthermore, the next scheduled time + * will always advance by exactly the delay every time the timer fires. + * This means that if the clock increments without the timer thread running + * (e.g. the computer is asleep) when the timer thread gets to run again it + * will post all the events that it "missed" while it wasn't running. Use + * this timer type with extreme caution. Chances are, this is not what you + * want. */ - const short TYPE_REPEATING_PRECISE = 2; + const short TYPE_REPEATING_PRECISE = 2; /** * A TYPE_REPEATING_PRECISE_CAN_SKIP repeating timer aims to have constant @@ -90,9 +105,10 @@ interface nsITimer : nsISupports * guarantees that it will not queue up new events to fire the callback * until the previous callback event finishes firing. If the callback * takes a long time, then the next callback will be scheduled immediately - * afterward, but only once. This is the only non-slack timer available. + * afterward, but only once, unlike TYPE_REPEATING_PRECISE. If you want a + * non-slack timer, you probably want this one. */ - const short TYPE_REPEATING_PRECISE_CAN_SKIP = 3; + const short TYPE_REPEATING_PRECISE_CAN_SKIP = 3; /** * Initialize a timer that will fire after the said delay. @@ -180,6 +196,10 @@ interface nsITimer : nsISupports * By default the target is the thread that created the timer. */ attribute nsIEventTarget target; + +%{C++ + virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const = 0; +%} }; %{C++ diff --git a/xpcom/threads/nsMemoryPressure.cpp b/xpcom/threads/nsMemoryPressure.cpp new file mode 100644 index 000000000..18e71b48b --- /dev/null +++ b/xpcom/threads/nsMemoryPressure.cpp @@ -0,0 +1,54 @@ +/* -*- 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 "nsMemoryPressure.h" +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" + +#include "nsThreadUtils.h" + +using namespace mozilla; + +static Atomic sMemoryPressurePending; +static_assert(MemPressure_None == 0, + "Bad static initialization with the default constructor."); + +MemoryPressureState +NS_GetPendingMemoryPressure() +{ + int32_t value = sMemoryPressurePending.exchange(MemPressure_None); + return MemoryPressureState(value); +} + +void +NS_DispatchEventualMemoryPressure(MemoryPressureState aState) +{ + /* + * A new memory pressure event erases an ongoing memory pressure, but an + * existing "new" memory pressure event takes precedence over a new "ongoing" + * memory pressure event. + */ + switch (aState) { + case MemPressure_None: + sMemoryPressurePending = MemPressure_None; + break; + case MemPressure_New: + sMemoryPressurePending = MemPressure_New; + break; + case MemPressure_Ongoing: + sMemoryPressurePending.compareExchange(MemPressure_None, + MemPressure_Ongoing); + break; + } +} + +nsresult +NS_DispatchMemoryPressure(MemoryPressureState aState) +{ + NS_DispatchEventualMemoryPressure(aState); + nsCOMPtr event = new nsRunnable; + return NS_DispatchToMainThread(event); +} diff --git a/xpcom/threads/nsMemoryPressure.h b/xpcom/threads/nsMemoryPressure.h new file mode 100644 index 000000000..dc34665aa --- /dev/null +++ b/xpcom/threads/nsMemoryPressure.h @@ -0,0 +1,77 @@ +/* -*- 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/. */ + +#ifndef nsMemoryPressure_h__ +#define nsMemoryPressure_h__ + +#include "nscore.h" + +enum MemoryPressureState +{ + /* + * No memory pressure. + */ + MemPressure_None = 0, + + /* + * New memory pressure deteced. + * + * On a new memory pressure, we stop everything to start cleaning + * aggresively the memory used, in order to free as much memory as + * possible. + */ + MemPressure_New, + + /* + * Repeated memory pressure. + * + * A repeated memory pressure implies to clean softly recent allocations. + * It is supposed to happen after a new memory pressure which already + * cleaned aggressivley. So there is no need to damage the reactivity of + * Goanna by stopping the world again. + * + * In case of conflict with an new memory pressue, the new memory pressure + * takes precedence over an ongoing memory pressure. The reason being + * that if no events are processed between 2 notifications (new followed + * by ongoing, or ongoing followed by a new) we want to be as aggresive as + * possible on the clean-up of the memory. After all, we are trying to + * keep Goanna alive as long as possible. + */ + MemPressure_Ongoing +}; + +/** + * Return and erase the latest state of the memory pressure event set by any of + * the corresponding dispatch function. + */ +MemoryPressureState +NS_GetPendingMemoryPressure(); + +/** + * This function causes the main thread to fire a memory pressure event + * before processing the next event, but if there are no events pending in + * the main thread's event queue, the memory pressure event would not be + * dispatched until one is enqueued. It is infallible and does not allocate + * any memory. + * + * You may call this function from any thread. + */ +void +NS_DispatchEventualMemoryPressure(MemoryPressureState aState); + +/** + * This function causes the main thread to fire a memory pressure event + * before processing the next event. We wake up the main thread by adding a + * dummy event to its event loop, so, unlike with + * NS_DispatchEventualMemoryPressure, this memory-pressure event is always + * fired relatively quickly, even if the event loop is otherwise empty. + * + * You may call this function from any thread. + */ +nsresult +NS_DispatchMemoryPressure(MemoryPressureState aState); + +#endif // nsMemoryPressure_h__ diff --git a/xpcom/threads/nsProcess.h b/xpcom/threads/nsProcess.h index 6d02dc591..140944415 100644 --- a/xpcom/threads/nsProcess.h +++ b/xpcom/threads/nsProcess.h @@ -1,4 +1,5 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* -*- 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/. */ @@ -31,12 +32,13 @@ {0x7b4eeb20, 0xd781, 0x11d4, \ {0x8A, 0x83, 0x00, 0x10, 0xa4, 0xe0, 0xc9, 0xca}} -class nsProcess MOZ_FINAL : public nsIProcess, - public nsIObserver +class nsProcess final + : public nsIProcess + , public nsIObserver { public: - NS_DECL_ISUPPORTS + NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIPROCESS NS_DECL_NSIOBSERVER @@ -44,17 +46,17 @@ public: private: ~nsProcess(); - static void Monitor(void *arg); + static void Monitor(void* aArg); void ProcessComplete(); - nsresult CopyArgsAndRunProcess(bool blocking, const char** args, - uint32_t count, nsIObserver* observer, - bool holdWeak); - nsresult CopyArgsAndRunProcessw(bool blocking, const PRUnichar** args, - uint32_t count, nsIObserver* observer, - bool holdWeak); + nsresult CopyArgsAndRunProcess(bool aBlocking, const char** aArgs, + uint32_t aCount, nsIObserver* aObserver, + bool aHoldWeak); + nsresult CopyArgsAndRunProcessw(bool aBlocking, const char16_t** aArgs, + uint32_t aCount, nsIObserver* aObserver, + bool aHoldWeak); // The 'args' array is null-terminated. - nsresult RunProcess(bool blocking, char **args, nsIObserver* observer, - bool holdWeak, bool argsUTF8); + nsresult RunProcess(bool aBlocking, char** aArgs, nsIObserver* aObserver, + bool aHoldWeak, bool aArgsUTF8); PRThread* mThread; mozilla::Mutex mLock; @@ -73,7 +75,7 @@ private: #if defined(PROCESSMODEL_WINAPI) HANDLE mProcess; #elif !defined(XP_MACOSX) - PRProcess *mProcess; + PRProcess* mProcess; #endif }; diff --git a/xpcom/threads/nsProcessCommon.cpp b/xpcom/threads/nsProcessCommon.cpp index 828e0b6e0..0ce2e1eb2 100644 --- a/xpcom/threads/nsProcessCommon.cpp +++ b/xpcom/threads/nsProcessCommon.cpp @@ -1,23 +1,23 @@ -/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* -*- 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/. */ /***************************************************************************** - * + * * nsProcess is used to execute new processes and specify if you want to * wait (blocking) or continue (non-blocking). * ***************************************************************************** */ -#include "mozilla/Util.h" +#include "mozilla/ArrayUtils.h" #include "nsCOMPtr.h" #include "nsAutoPtr.h" #include "nsMemory.h" #include "nsProcess.h" -#include "prtypes.h" #include "prio.h" #include "prenv.h" #include "nsCRT.h" @@ -37,6 +37,7 @@ #include #include #include +#include #endif #include #include @@ -47,33 +48,34 @@ using namespace mozilla; #ifdef XP_MACOSX cpu_type_t pref_cpu_types[2] = { #if defined(__i386__) - CPU_TYPE_X86, + CPU_TYPE_X86, #elif defined(__x86_64__) - CPU_TYPE_X86_64, + CPU_TYPE_X86_64, #elif defined(__ppc__) - CPU_TYPE_POWERPC, + CPU_TYPE_POWERPC, #endif - CPU_TYPE_ANY }; + CPU_TYPE_ANY +}; #endif //-------------------------------------------------------------------// // nsIProcess implementation //-------------------------------------------------------------------// -NS_IMPL_THREADSAFE_ISUPPORTS2(nsProcess, nsIProcess, - nsIObserver) +NS_IMPL_ISUPPORTS(nsProcess, nsIProcess, + nsIObserver) //Constructor nsProcess::nsProcess() - : mThread(nullptr) - , mLock("nsProcess.mLock") - , mShutdown(false) - , mBlocking(false) - , mPid(-1) - , mObserver(nullptr) - , mWeakObserver(nullptr) - , mExitValue(-1) + : mThread(nullptr) + , mLock("nsProcess.mLock") + , mShutdown(false) + , mBlocking(false) + , mPid(-1) + , mObserver(nullptr) + , mWeakObserver(nullptr) + , mExitValue(-1) #if !defined(XP_MACOSX) - , mProcess(nullptr) + , mProcess(nullptr) #endif { } @@ -84,533 +86,576 @@ nsProcess::~nsProcess() } NS_IMETHODIMP -nsProcess::Init(nsIFile* executable) +nsProcess::Init(nsIFile* aExecutable) { - if (mExecutable) - return NS_ERROR_ALREADY_INITIALIZED; - - NS_ENSURE_ARG_POINTER(executable); - bool isFile; - - //First make sure the file exists - nsresult rv = executable->IsFile(&isFile); - if (NS_FAILED(rv)) return rv; - if (!isFile) - return NS_ERROR_FAILURE; - - //Store the nsIFile in mExecutable - mExecutable = executable; - //Get the path because it is needed by the NSPR process creation -#ifdef XP_WIN - rv = mExecutable->GetTarget(mTargetPath); - if (NS_FAILED(rv) || mTargetPath.IsEmpty() ) + if (mExecutable) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + if (NS_WARN_IF(!aExecutable)) { + return NS_ERROR_INVALID_ARG; + } + bool isFile; + + //First make sure the file exists + nsresult rv = aExecutable->IsFile(&isFile); + if (NS_FAILED(rv)) { + return rv; + } + if (!isFile) { + return NS_ERROR_FAILURE; + } + + //Store the nsIFile in mExecutable + mExecutable = aExecutable; + //Get the path because it is needed by the NSPR process creation +#ifdef XP_WIN + rv = mExecutable->GetTarget(mTargetPath); + if (NS_FAILED(rv) || mTargetPath.IsEmpty()) #endif - rv = mExecutable->GetPath(mTargetPath); + rv = mExecutable->GetPath(mTargetPath); - return rv; + return rv; } #if defined(XP_WIN) -// Out param `wideCmdLine` must be PR_Freed by the caller. -static int assembleCmdLine(char *const *argv, PRUnichar **wideCmdLine, - UINT codePage) +// Out param `aWideCmdLine` must be PR_Freed by the caller. +static int +assembleCmdLine(char* const* aArgv, wchar_t** aWideCmdLine, UINT aCodePage) { - char *const *arg; - char *p, *q, *cmdLine; - int cmdLineSize; - int numBackslashes; - int i; - int argNeedQuotes; - + char* const* arg; + char* p; + char* q; + char* cmdLine; + int cmdLineSize; + int numBackslashes; + int i; + int argNeedQuotes; + + /* + * Find out how large the command line buffer should be. + */ + cmdLineSize = 0; + for (arg = aArgv; *arg; ++arg) { /* - * Find out how large the command line buffer should be. + * \ and " need to be escaped by a \. In the worst case, + * every character is a \ or ", so the string of length + * may double. If we quote an argument, that needs two ". + * Finally, we need a space between arguments, and + * a null byte at the end of command line. */ - cmdLineSize = 0; - for (arg = argv; *arg; arg++) { - /* - * \ and " need to be escaped by a \. In the worst case, - * every character is a \ or ", so the string of length - * may double. If we quote an argument, that needs two ". - * Finally, we need a space between arguments, and - * a null byte at the end of command line. - */ - cmdLineSize += 2 * strlen(*arg) /* \ and " need to be escaped */ - + 2 /* we quote every argument */ - + 1; /* space in between, or final null */ - } - p = cmdLine = (char *) PR_MALLOC(cmdLineSize*sizeof(char)); - if (p == NULL) { - return -1; + cmdLineSize += 2 * strlen(*arg) /* \ and " need to be escaped */ + + 2 /* we quote every argument */ + + 1; /* space in between, or final null */ + } + p = cmdLine = (char*)PR_MALLOC(cmdLineSize * sizeof(char)); + if (!p) { + return -1; + } + + for (arg = aArgv; *arg; ++arg) { + /* Add a space to separates the arguments */ + if (arg != aArgv) { + *p++ = ' '; } + q = *arg; + numBackslashes = 0; + argNeedQuotes = 0; - for (arg = argv; *arg; arg++) { - /* Add a space to separates the arguments */ - if (arg != argv) { - *p++ = ' '; - } - q = *arg; - numBackslashes = 0; - argNeedQuotes = 0; - - /* If the argument contains white space, it needs to be quoted. */ - if (strpbrk(*arg, " \f\n\r\t\v")) { - argNeedQuotes = 1; - } - - if (argNeedQuotes) { - *p++ = '"'; - } - while (*q) { - if (*q == '\\') { - numBackslashes++; - q++; - } else if (*q == '"') { - if (numBackslashes) { - /* - * Double the backslashes since they are followed - * by a quote - */ - for (i = 0; i < 2 * numBackslashes; i++) { - *p++ = '\\'; - } - numBackslashes = 0; - } - /* To escape the quote */ - *p++ = '\\'; - *p++ = *q++; - } else { - if (numBackslashes) { - /* - * Backslashes are not followed by a quote, so - * don't need to double the backslashes. - */ - for (i = 0; i < numBackslashes; i++) { - *p++ = '\\'; - } - numBackslashes = 0; - } - *p++ = *q++; - } - } + /* If the argument contains white space, it needs to be quoted. */ + if (strpbrk(*arg, " \f\n\r\t\v")) { + argNeedQuotes = 1; + } - /* Now we are at the end of this argument */ + if (argNeedQuotes) { + *p++ = '"'; + } + while (*q) { + if (*q == '\\') { + numBackslashes++; + q++; + } else if (*q == '"') { if (numBackslashes) { - /* - * Double the backslashes if we have a quote string - * delimiter at the end. - */ - if (argNeedQuotes) { - numBackslashes *= 2; - } - for (i = 0; i < numBackslashes; i++) { - *p++ = '\\'; - } + /* + * Double the backslashes since they are followed + * by a quote + */ + for (i = 0; i < 2 * numBackslashes; i++) { + *p++ = '\\'; + } + numBackslashes = 0; } - if (argNeedQuotes) { - *p++ = '"'; + /* To escape the quote */ + *p++ = '\\'; + *p++ = *q++; + } else { + if (numBackslashes) { + /* + * Backslashes are not followed by a quote, so + * don't need to double the backslashes. + */ + for (i = 0; i < numBackslashes; i++) { + *p++ = '\\'; + } + numBackslashes = 0; } - } + *p++ = *q++; + } + } - *p = '\0'; - int32_t numChars = MultiByteToWideChar(codePage, 0, cmdLine, -1, NULL, 0); - *wideCmdLine = (PRUnichar *) PR_MALLOC(numChars*sizeof(PRUnichar)); - MultiByteToWideChar(codePage, 0, cmdLine, -1, *wideCmdLine, numChars); - PR_Free(cmdLine); - return 0; + /* Now we are at the end of this argument */ + if (numBackslashes) { + /* + * Double the backslashes if we have a quote string + * delimiter at the end. + */ + if (argNeedQuotes) { + numBackslashes *= 2; + } + for (i = 0; i < numBackslashes; i++) { + *p++ = '\\'; + } + } + if (argNeedQuotes) { + *p++ = '"'; + } + } + + *p = '\0'; + int32_t numChars = MultiByteToWideChar(aCodePage, 0, cmdLine, -1, nullptr, 0); + *aWideCmdLine = (wchar_t*)PR_MALLOC(numChars * sizeof(wchar_t)); + MultiByteToWideChar(aCodePage, 0, cmdLine, -1, *aWideCmdLine, numChars); + PR_Free(cmdLine); + return 0; } #endif -void nsProcess::Monitor(void *arg) +void +nsProcess::Monitor(void* aArg) { - nsRefPtr process = dont_AddRef(static_cast(arg)); + nsRefPtr process = dont_AddRef(static_cast(aArg)); - if (!process->mBlocking) - PR_SetCurrentThreadName("RunProcess"); + if (!process->mBlocking) { + PR_SetCurrentThreadName("RunProcess"); + } #if defined(PROCESSMODEL_WINAPI) - DWORD dwRetVal; - unsigned long exitCode = -1; + DWORD dwRetVal; + unsigned long exitCode = -1; - dwRetVal = WaitForSingleObject(process->mProcess, INFINITE); - if (dwRetVal != WAIT_FAILED) { - if (GetExitCodeProcess(process->mProcess, &exitCode) == FALSE) - exitCode = -1; + dwRetVal = WaitForSingleObject(process->mProcess, INFINITE); + if (dwRetVal != WAIT_FAILED) { + if (GetExitCodeProcess(process->mProcess, &exitCode) == FALSE) { + exitCode = -1; } - - // Lock in case Kill or GetExitCode are called during this - { - MutexAutoLock lock(process->mLock); - CloseHandle(process->mProcess); - process->mProcess = NULL; - process->mExitValue = exitCode; - if (process->mShutdown) - return; + } + + // Lock in case Kill or GetExitCode are called during this + { + MutexAutoLock lock(process->mLock); + CloseHandle(process->mProcess); + process->mProcess = nullptr; + process->mExitValue = exitCode; + if (process->mShutdown) { + return; } + } #else #ifdef XP_MACOSX - int exitCode = -1; - int status = 0; - if (waitpid(process->mPid, &status, 0) == process->mPid) { - if (WIFEXITED(status)) { - exitCode = WEXITSTATUS(status); - } - else if(WIFSIGNALED(status)) { - exitCode = 256; // match NSPR's signal exit status - } + int exitCode = -1; + int status = 0; + pid_t result; + do { + result = waitpid(process->mPid, &status, 0); + } while (result == -1 && errno == EINTR); + if (result == process->mPid) { + if (WIFEXITED(status)) { + exitCode = WEXITSTATUS(status); + } else if (WIFSIGNALED(status)) { + exitCode = 256; // match NSPR's signal exit status } + } #else - int32_t exitCode = -1; - if (PR_WaitProcess(process->mProcess, &exitCode) != PR_SUCCESS) - exitCode = -1; + int32_t exitCode = -1; + if (PR_WaitProcess(process->mProcess, &exitCode) != PR_SUCCESS) { + exitCode = -1; + } #endif - // Lock in case Kill or GetExitCode are called during this - { - MutexAutoLock lock(process->mLock); + // Lock in case Kill or GetExitCode are called during this + { + MutexAutoLock lock(process->mLock); #if !defined(XP_MACOSX) - process->mProcess = nullptr; + process->mProcess = nullptr; #endif - process->mExitValue = exitCode; - if (process->mShutdown) - return; + process->mExitValue = exitCode; + if (process->mShutdown) { + return; } + } #endif - // If we ran a background thread for the monitor then notify on the main - // thread - if (NS_IsMainThread()) { - process->ProcessComplete(); - } - else { - nsCOMPtr event = - NS_NewRunnableMethod(process, &nsProcess::ProcessComplete); - NS_DispatchToMainThread(event); - } + // If we ran a background thread for the monitor then notify on the main + // thread + if (NS_IsMainThread()) { + process->ProcessComplete(); + } else { + nsCOMPtr event = + NS_NewRunnableMethod(process, &nsProcess::ProcessComplete); + NS_DispatchToMainThread(event); + } } -void nsProcess::ProcessComplete() +void +nsProcess::ProcessComplete() { - if (mThread) { - nsCOMPtr os = - mozilla::services::GetObserverService(); - if (os) - os->RemoveObserver(this, "xpcom-shutdown"); - PR_JoinThread(mThread); - mThread = nullptr; + if (mThread) { + nsCOMPtr os = + mozilla::services::GetObserverService(); + if (os) { + os->RemoveObserver(this, "xpcom-shutdown"); } - - const char* topic; - if (mExitValue < 0) - topic = "process-failed"; - else - topic = "process-finished"; - - mPid = -1; - nsCOMPtr observer; - if (mWeakObserver) - observer = do_QueryReferent(mWeakObserver); - else if (mObserver) - observer = mObserver; - mObserver = nullptr; - mWeakObserver = nullptr; - - if (observer) - observer->Observe(NS_ISUPPORTS_CAST(nsIProcess*, this), topic, nullptr); + PR_JoinThread(mThread); + mThread = nullptr; + } + + const char* topic; + if (mExitValue < 0) { + topic = "process-failed"; + } else { + topic = "process-finished"; + } + + mPid = -1; + nsCOMPtr observer; + if (mWeakObserver) { + observer = do_QueryReferent(mWeakObserver); + } else if (mObserver) { + observer = mObserver; + } + mObserver = nullptr; + mWeakObserver = nullptr; + + if (observer) { + observer->Observe(NS_ISUPPORTS_CAST(nsIProcess*, this), topic, nullptr); + } } -// XXXldb |args| has the wrong const-ness -NS_IMETHODIMP -nsProcess::Run(bool blocking, const char **args, uint32_t count) +// XXXldb |aArgs| has the wrong const-ness +NS_IMETHODIMP +nsProcess::Run(bool aBlocking, const char** aArgs, uint32_t aCount) { - return CopyArgsAndRunProcess(blocking, args, count, nullptr, false); + return CopyArgsAndRunProcess(aBlocking, aArgs, aCount, nullptr, false); } -// XXXldb |args| has the wrong const-ness -NS_IMETHODIMP -nsProcess::RunAsync(const char **args, uint32_t count, - nsIObserver* observer, bool holdWeak) +// XXXldb |aArgs| has the wrong const-ness +NS_IMETHODIMP +nsProcess::RunAsync(const char** aArgs, uint32_t aCount, + nsIObserver* aObserver, bool aHoldWeak) { - return CopyArgsAndRunProcess(false, args, count, observer, holdWeak); + return CopyArgsAndRunProcess(false, aArgs, aCount, aObserver, aHoldWeak); } nsresult -nsProcess::CopyArgsAndRunProcess(bool blocking, const char** args, - uint32_t count, nsIObserver* observer, - bool holdWeak) +nsProcess::CopyArgsAndRunProcess(bool aBlocking, const char** aArgs, + uint32_t aCount, nsIObserver* aObserver, + bool aHoldWeak) { - // Add one to the count for the program name and one for NULL termination. - char **my_argv = NULL; - my_argv = (char**)NS_Alloc(sizeof(char*) * (count + 2)); - if (!my_argv) { - return NS_ERROR_OUT_OF_MEMORY; - } + // Add one to the aCount for the program name and one for null termination. + char** my_argv = nullptr; + my_argv = (char**)NS_Alloc(sizeof(char*) * (aCount + 2)); + if (!my_argv) { + return NS_ERROR_OUT_OF_MEMORY; + } - my_argv[0] = ToNewUTF8String(mTargetPath); + my_argv[0] = ToNewUTF8String(mTargetPath); - for (uint32_t i = 0; i < count; i++) { - my_argv[i + 1] = const_cast(args[i]); - } + for (uint32_t i = 0; i < aCount; ++i) { + my_argv[i + 1] = const_cast(aArgs[i]); + } - my_argv[count + 1] = NULL; + my_argv[aCount + 1] = nullptr; - nsresult rv = RunProcess(blocking, my_argv, observer, holdWeak, false); + nsresult rv = RunProcess(aBlocking, my_argv, aObserver, aHoldWeak, false); - NS_Free(my_argv[0]); - NS_Free(my_argv); - return rv; + NS_Free(my_argv[0]); + NS_Free(my_argv); + return rv; } -// XXXldb |args| has the wrong const-ness -NS_IMETHODIMP -nsProcess::Runw(bool blocking, const PRUnichar **args, uint32_t count) +// XXXldb |aArgs| has the wrong const-ness +NS_IMETHODIMP +nsProcess::Runw(bool aBlocking, const char16_t** aArgs, uint32_t aCount) { - return CopyArgsAndRunProcessw(blocking, args, count, nullptr, false); + return CopyArgsAndRunProcessw(aBlocking, aArgs, aCount, nullptr, false); } -// XXXldb |args| has the wrong const-ness -NS_IMETHODIMP -nsProcess::RunwAsync(const PRUnichar **args, uint32_t count, - nsIObserver* observer, bool holdWeak) +// XXXldb |aArgs| has the wrong const-ness +NS_IMETHODIMP +nsProcess::RunwAsync(const char16_t** aArgs, uint32_t aCount, + nsIObserver* aObserver, bool aHoldWeak) { - return CopyArgsAndRunProcessw(false, args, count, observer, holdWeak); + return CopyArgsAndRunProcessw(false, aArgs, aCount, aObserver, aHoldWeak); } nsresult -nsProcess::CopyArgsAndRunProcessw(bool blocking, const PRUnichar** args, - uint32_t count, nsIObserver* observer, - bool holdWeak) +nsProcess::CopyArgsAndRunProcessw(bool aBlocking, const char16_t** aArgs, + uint32_t aCount, nsIObserver* aObserver, + bool aHoldWeak) { - // Add one to the count for the program name and one for NULL termination. - char **my_argv = NULL; - my_argv = (char**)NS_Alloc(sizeof(char*) * (count + 2)); - if (!my_argv) { - return NS_ERROR_OUT_OF_MEMORY; - } + // Add one to the aCount for the program name and one for null termination. + char** my_argv = nullptr; + my_argv = (char**)NS_Alloc(sizeof(char*) * (aCount + 2)); + if (!my_argv) { + return NS_ERROR_OUT_OF_MEMORY; + } - my_argv[0] = ToNewUTF8String(mTargetPath); + my_argv[0] = ToNewUTF8String(mTargetPath); - for (uint32_t i = 0; i < count; i++) { - my_argv[i + 1] = ToNewUTF8String(nsDependentString(args[i])); - } + for (uint32_t i = 0; i < aCount; i++) { + my_argv[i + 1] = ToNewUTF8String(nsDependentString(aArgs[i])); + } - my_argv[count + 1] = NULL; + my_argv[aCount + 1] = nullptr; - nsresult rv = RunProcess(blocking, my_argv, observer, holdWeak, true); + nsresult rv = RunProcess(aBlocking, my_argv, aObserver, aHoldWeak, true); - for (uint32_t i = 0; i <= count; i++) { - NS_Free(my_argv[i]); - } - NS_Free(my_argv); - return rv; + for (uint32_t i = 0; i <= aCount; ++i) { + NS_Free(my_argv[i]); + } + NS_Free(my_argv); + return rv; } -nsresult -nsProcess::RunProcess(bool blocking, char **my_argv, nsIObserver* observer, - bool holdWeak, bool argsUTF8) +nsresult +nsProcess::RunProcess(bool aBlocking, char** aMyArgv, nsIObserver* aObserver, + bool aHoldWeak, bool aArgsUTF8) { - NS_ENSURE_TRUE(mExecutable, NS_ERROR_NOT_INITIALIZED); - NS_ENSURE_FALSE(mThread, NS_ERROR_ALREADY_INITIALIZED); - - if (observer) { - if (holdWeak) { - mWeakObserver = do_GetWeakReference(observer); - if (!mWeakObserver) - return NS_NOINTERFACE; - } - else { - mObserver = observer; - } + if (NS_WARN_IF(!mExecutable)) { + return NS_ERROR_NOT_INITIALIZED; + } + if (NS_WARN_IF(mThread)) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + if (aObserver) { + if (aHoldWeak) { + mWeakObserver = do_GetWeakReference(aObserver); + if (!mWeakObserver) { + return NS_NOINTERFACE; + } + } else { + mObserver = aObserver; } + } - mExitValue = -1; - mPid = -1; + mExitValue = -1; + mPid = -1; #if defined(PROCESSMODEL_WINAPI) - BOOL retVal; - PRUnichar *cmdLine = NULL; - - // The 'argv' array is null-terminated and always starts with the program path. - // If the second slot is non-null then arguments are being passed. - if (my_argv[1] != NULL && - assembleCmdLine(my_argv + 1, &cmdLine, argsUTF8 ? CP_UTF8 : CP_ACP) == -1) { - return NS_ERROR_FILE_EXECUTION_FAILED; - } - - /* The SEE_MASK_NO_CONSOLE flag is important to prevent console windows - * from appearing. This makes behavior the same on all platforms. The flag - * will not have any effect on non-console applications. - */ - - // The program name in my_argv[0] is always UTF-8 - NS_ConvertUTF8toUTF16 wideFile(my_argv[0]); - - SHELLEXECUTEINFOW sinfo; - memset(&sinfo, 0, sizeof(SHELLEXECUTEINFOW)); - sinfo.cbSize = sizeof(SHELLEXECUTEINFOW); - sinfo.hwnd = NULL; - sinfo.lpFile = wideFile.get(); - sinfo.nShow = SW_SHOWNORMAL; - sinfo.fMask = SEE_MASK_FLAG_DDEWAIT | - SEE_MASK_NO_CONSOLE | - SEE_MASK_NOCLOSEPROCESS; - - if (cmdLine) - sinfo.lpParameters = cmdLine; - - retVal = ShellExecuteExW(&sinfo); - if (!retVal) { - return NS_ERROR_FILE_EXECUTION_FAILED; - } - - mProcess = sinfo.hProcess; - - if (cmdLine) - PR_Free(cmdLine); + BOOL retVal; + wchar_t* cmdLine = nullptr; + + // |aMyArgv| is null-terminated and always starts with the program path. If + // the second slot is non-null then arguments are being passed. + if (aMyArgv[1] && assembleCmdLine(aMyArgv + 1, &cmdLine, + aArgsUTF8 ? CP_UTF8 : CP_ACP) == -1) { + return NS_ERROR_FILE_EXECUTION_FAILED; + } + + /* The SEE_MASK_NO_CONSOLE flag is important to prevent console windows + * from appearing. This makes behavior the same on all platforms. The flag + * will not have any effect on non-console applications. + */ + + // The program name in aMyArgv[0] is always UTF-8 + NS_ConvertUTF8toUTF16 wideFile(aMyArgv[0]); + + SHELLEXECUTEINFOW sinfo; + memset(&sinfo, 0, sizeof(SHELLEXECUTEINFOW)); + sinfo.cbSize = sizeof(SHELLEXECUTEINFOW); + sinfo.hwnd = nullptr; + sinfo.lpFile = wideFile.get(); + sinfo.nShow = SW_SHOWNORMAL; + sinfo.fMask = SEE_MASK_FLAG_DDEWAIT | + SEE_MASK_NO_CONSOLE | + SEE_MASK_NOCLOSEPROCESS; + + if (cmdLine) { + sinfo.lpParameters = cmdLine; + } + + retVal = ShellExecuteExW(&sinfo); + if (!retVal) { + return NS_ERROR_FILE_EXECUTION_FAILED; + } + + mProcess = sinfo.hProcess; + + if (cmdLine) { + PR_Free(cmdLine); + } - mPid = GetProcessId(mProcess); + mPid = GetProcessId(mProcess); #elif defined(XP_MACOSX) - // Initialize spawn attributes. - posix_spawnattr_t spawnattr; - if (posix_spawnattr_init(&spawnattr) != 0) { - return NS_ERROR_FAILURE; - } - - // Set spawn attributes. - size_t attr_count = ArrayLength(pref_cpu_types); - size_t attr_ocount = 0; - if (posix_spawnattr_setbinpref_np(&spawnattr, attr_count, pref_cpu_types, &attr_ocount) != 0 || - attr_ocount != attr_count) { - posix_spawnattr_destroy(&spawnattr); - return NS_ERROR_FAILURE; - } + // Initialize spawn attributes. + posix_spawnattr_t spawnattr; + if (posix_spawnattr_init(&spawnattr) != 0) { + return NS_ERROR_FAILURE; + } + + // Set spawn attributes. + size_t attr_count = ArrayLength(pref_cpu_types); + size_t attr_ocount = 0; + if (posix_spawnattr_setbinpref_np(&spawnattr, attr_count, pref_cpu_types, + &attr_ocount) != 0 || + attr_ocount != attr_count) { + posix_spawnattr_destroy(&spawnattr); + return NS_ERROR_FAILURE; + } - // Note that the 'argv' array is already null-terminated, which 'posix_spawnp' requires. - pid_t newPid = 0; - int result = posix_spawnp(&newPid, my_argv[0], NULL, &spawnattr, my_argv, *_NSGetEnviron()); - mPid = static_cast(newPid); + // Note: |aMyArgv| is already null-terminated as required by posix_spawnp. + pid_t newPid = 0; + int result = posix_spawnp(&newPid, aMyArgv[0], nullptr, &spawnattr, aMyArgv, + *_NSGetEnviron()); + mPid = static_cast(newPid); - posix_spawnattr_destroy(&spawnattr); + posix_spawnattr_destroy(&spawnattr); - if (result != 0) { - return NS_ERROR_FAILURE; - } + if (result != 0) { + return NS_ERROR_FAILURE; + } #else - mProcess = PR_CreateProcess(my_argv[0], my_argv, NULL, NULL); - if (!mProcess) - return NS_ERROR_FAILURE; - struct MYProcess { - uint32_t pid; - }; - MYProcess* ptrProc = (MYProcess *) mProcess; - mPid = ptrProc->pid; + mProcess = PR_CreateProcess(aMyArgv[0], aMyArgv, nullptr, nullptr); + if (!mProcess) { + return NS_ERROR_FAILURE; + } + struct MYProcess + { + uint32_t pid; + }; + MYProcess* ptrProc = (MYProcess*)mProcess; + mPid = ptrProc->pid; #endif - NS_ADDREF_THIS(); - mBlocking = blocking; - if (blocking) { - Monitor(this); - if (mExitValue < 0) - return NS_ERROR_FILE_EXECUTION_FAILED; + NS_ADDREF_THIS(); + mBlocking = aBlocking; + if (aBlocking) { + Monitor(this); + if (mExitValue < 0) { + return NS_ERROR_FILE_EXECUTION_FAILED; + } + } else { + mThread = PR_CreateThread(PR_SYSTEM_THREAD, Monitor, this, + PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, + PR_JOINABLE_THREAD, 0); + if (!mThread) { + NS_RELEASE_THIS(); + return NS_ERROR_FAILURE; } - else { - mThread = PR_CreateThread(PR_SYSTEM_THREAD, Monitor, this, - PR_PRIORITY_NORMAL, PR_LOCAL_THREAD, - PR_JOINABLE_THREAD, 0); - if (!mThread) { - NS_RELEASE_THIS(); - return NS_ERROR_FAILURE; - } - // It isn't a failure if we just can't watch for shutdown - nsCOMPtr os = - mozilla::services::GetObserverService(); - if (os) - os->AddObserver(this, "xpcom-shutdown", false); + // It isn't a failure if we just can't watch for shutdown + nsCOMPtr os = + mozilla::services::GetObserverService(); + if (os) { + os->AddObserver(this, "xpcom-shutdown", false); } + } - return NS_OK; + return NS_OK; } -NS_IMETHODIMP nsProcess::GetIsRunning(bool *aIsRunning) +NS_IMETHODIMP +nsProcess::GetIsRunning(bool* aIsRunning) { - if (mThread) - *aIsRunning = true; - else - *aIsRunning = false; + if (mThread) { + *aIsRunning = true; + } else { + *aIsRunning = false; + } - return NS_OK; + return NS_OK; } NS_IMETHODIMP -nsProcess::GetPid(uint32_t *aPid) +nsProcess::GetPid(uint32_t* aPid) { - if (!mThread) - return NS_ERROR_FAILURE; - if (mPid < 0) - return NS_ERROR_NOT_IMPLEMENTED; - *aPid = mPid; - return NS_OK; + if (!mThread) { + return NS_ERROR_FAILURE; + } + if (mPid < 0) { + return NS_ERROR_NOT_IMPLEMENTED; + } + *aPid = mPid; + return NS_OK; } NS_IMETHODIMP nsProcess::Kill() { - if (!mThread) - return NS_ERROR_FAILURE; + if (!mThread) { + return NS_ERROR_FAILURE; + } - { - MutexAutoLock lock(mLock); + { + MutexAutoLock lock(mLock); #if defined(PROCESSMODEL_WINAPI) - if (TerminateProcess(mProcess, 0) == 0) - return NS_ERROR_FAILURE; + if (TerminateProcess(mProcess, 0) == 0) { + return NS_ERROR_FAILURE; + } #elif defined(XP_MACOSX) - if (kill(mPid, SIGKILL) != 0) - return NS_ERROR_FAILURE; + if (kill(mPid, SIGKILL) != 0) { + return NS_ERROR_FAILURE; + } #else - if (!mProcess || (PR_KillProcess(mProcess) != PR_SUCCESS)) - return NS_ERROR_FAILURE; -#endif + if (!mProcess || (PR_KillProcess(mProcess) != PR_SUCCESS)) { + return NS_ERROR_FAILURE; } - - // We must null out mThread if we want IsRunning to return false immediately - // after this call. - nsCOMPtr os = mozilla::services::GetObserverService(); - if (os) - os->RemoveObserver(this, "xpcom-shutdown"); - PR_JoinThread(mThread); - mThread = nullptr; - - return NS_OK; +#endif + } + + // We must null out mThread if we want IsRunning to return false immediately + // after this call. + nsCOMPtr os = mozilla::services::GetObserverService(); + if (os) { + os->RemoveObserver(this, "xpcom-shutdown"); + } + PR_JoinThread(mThread); + mThread = nullptr; + + return NS_OK; } NS_IMETHODIMP -nsProcess::GetExitValue(int32_t *aExitValue) +nsProcess::GetExitValue(int32_t* aExitValue) { - MutexAutoLock lock(mLock); + MutexAutoLock lock(mLock); + + *aExitValue = mExitValue; - *aExitValue = mExitValue; - - return NS_OK; + return NS_OK; } NS_IMETHODIMP -nsProcess::Observe(nsISupports* subject, const char* topic, const PRUnichar* data) +nsProcess::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { - // Shutting down, drop all references - if (mThread) { - nsCOMPtr os = - mozilla::services::GetObserverService(); - if (os) - os->RemoveObserver(this, "xpcom-shutdown"); - mThread = nullptr; + // Shutting down, drop all references + if (mThread) { + nsCOMPtr os = + mozilla::services::GetObserverService(); + if (os) { + os->RemoveObserver(this, "xpcom-shutdown"); } + mThread = nullptr; + } - mObserver = nullptr; - mWeakObserver = nullptr; + mObserver = nullptr; + mWeakObserver = nullptr; - MutexAutoLock lock(mLock); - mShutdown = true; + MutexAutoLock lock(mLock); + mShutdown = true; - return NS_OK; + return NS_OK; } diff --git a/xpcom/threads/nsThread.cpp b/xpcom/threads/nsThread.cpp index 76b48774e..46434bd54 100644 --- a/xpcom/threads/nsThread.cpp +++ b/xpcom/threads/nsThread.cpp @@ -1,28 +1,50 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* -*- 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 "mozilla/ReentrantMonitor.h" #include "nsThread.h" + +#include "base/message_loop.h" + +// Chromium's logging can sometimes leak through... +#ifdef LOG +#undef LOG +#endif + +#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/ipc/MessageChannel.h" #include "mozilla/Services.h" +#include "nsXPCOMPrivate.h" +#include "mozilla/ChaosMode.h" +#include "mozilla/ipc/BackgroundChild.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_UNIX) && !defined(ANDROID) && !defined(DEBUG) && HAVE_UALARM \ - && defined(_GNU_SOURCE) -# define MOZ_CANARY +#if defined(XP_LINUX) && !defined(ANDROID) && defined(_GNU_SOURCE) +#define HAVE_SCHED_SETAFFINITY +#endif + +#ifdef MOZ_CANARY # include # include # include @@ -38,115 +60,118 @@ #include "nsCRT.h" #endif +#ifdef MOZ_TASK_TRACER +#include "GoannaTaskTracer.h" +using namespace mozilla::tasktracer; +#endif + using namespace mozilla; #ifdef PR_LOGGING -static PRLogModuleInfo * +static PRLogModuleInfo* GetThreadLog() { - static PRLogModuleInfo *sLog; - if (!sLog) + 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; -namespace mozilla { - -// Fun fact: Android's GCC won't convert bool* to int32_t*, so we can't -// PR_ATOMIC_SET a bool. -static int32_t sMemoryPressurePending = 0; - -/* - * It's important that this function not acquire any locks, nor do anything - * which might cause malloc to run. - */ -void ScheduleMemoryPressureEvent() -{ - PR_ATOMIC_SET(&sMemoryPressurePending, 1); -} - -} // namespace mozilla - //----------------------------------------------------------------------------- // Because we do not have our own nsIFactory, we have to implement nsIClassInfo // somewhat manually. -class nsThreadClassInfo : public nsIClassInfo { +class nsThreadClassInfo : public nsIClassInfo +{ public: NS_DECL_ISUPPORTS_INHERITED // no mRefCnt NS_DECL_NSICLASSINFO - nsThreadClassInfo() {} + nsThreadClassInfo() + { + } }; -NS_IMETHODIMP_(nsrefcnt) nsThreadClassInfo::AddRef() { return 2; } -NS_IMETHODIMP_(nsrefcnt) nsThreadClassInfo::Release() { return 1; } -NS_IMPL_QUERY_INTERFACE1(nsThreadClassInfo, nsIClassInfo) +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) +nsThreadClassInfo::GetInterfaces(uint32_t* aCount, nsIID*** aArray) { - return NS_CI_INTERFACE_GETTER_NAME(nsThread)(count, array); + return NS_CI_INTERFACE_GETTER_NAME(nsThread)(aCount, aArray); } NS_IMETHODIMP -nsThreadClassInfo::GetHelperForLanguage(uint32_t lang, nsISupports **result) +nsThreadClassInfo::GetHelperForLanguage(uint32_t aLang, nsISupports** aResult) { - *result = nullptr; + *aResult = nullptr; return NS_OK; } NS_IMETHODIMP -nsThreadClassInfo::GetContractID(char **result) +nsThreadClassInfo::GetContractID(char** aResult) { - *result = nullptr; + *aResult = nullptr; return NS_OK; } NS_IMETHODIMP -nsThreadClassInfo::GetClassDescription(char **result) +nsThreadClassInfo::GetClassDescription(char** aResult) { - *result = nullptr; + *aResult = nullptr; return NS_OK; } NS_IMETHODIMP -nsThreadClassInfo::GetClassID(nsCID **result) +nsThreadClassInfo::GetClassID(nsCID** aResult) { - *result = nullptr; + *aResult = nullptr; return NS_OK; } NS_IMETHODIMP -nsThreadClassInfo::GetImplementationLanguage(uint32_t *result) +nsThreadClassInfo::GetImplementationLanguage(uint32_t* aResult) { - *result = nsIProgrammingLanguage::CPLUSPLUS; + *aResult = nsIProgrammingLanguage::CPLUSPLUS; return NS_OK; } NS_IMETHODIMP -nsThreadClassInfo::GetFlags(uint32_t *result) +nsThreadClassInfo::GetFlags(uint32_t* aResult) { - *result = THREADSAFE; + *aResult = THREADSAFE; return NS_OK; } NS_IMETHODIMP -nsThreadClassInfo::GetClassIDNoAlloc(nsCID *result) +nsThreadClassInfo::GetClassIDNoAlloc(nsCID* aResult) { return NS_ERROR_NOT_AVAILABLE; } //----------------------------------------------------------------------------- -NS_IMPL_THREADSAFE_ADDREF(nsThread) -NS_IMPL_THREADSAFE_RELEASE(nsThread) +NS_IMPL_ADDREF(nsThread) +NS_IMPL_RELEASE(nsThread) NS_INTERFACE_MAP_BEGIN(nsThread) NS_INTERFACE_MAP_ENTRY(nsIThread) NS_INTERFACE_MAP_ENTRY(nsIThreadInternal) @@ -158,97 +183,155 @@ NS_INTERFACE_MAP_BEGIN(nsThread) foundInterface = static_cast(&sThreadClassInfo); } else NS_INTERFACE_MAP_END -NS_IMPL_CI_INTERFACE_GETTER4(nsThread, nsIThread, nsIThreadInternal, - nsIEventTarget, nsISupportsPriority) +NS_IMPL_CI_INTERFACE_GETTER(nsThread, nsIThread, nsIThreadInternal, + nsIEventTarget, nsISupportsPriority) //----------------------------------------------------------------------------- -class nsThreadStartupEvent : public nsRunnable { +class nsThreadStartupEvent : public nsRunnable +{ public: - // Create a new thread startup object. - static nsThreadStartupEvent *Create() { - return new nsThreadStartupEvent(); + 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... + void Wait() + { + if (mInitialized) { + // Maybe avoid locking... return; + } + ReentrantMonitorAutoEnter mon(mMon); - while (!mInitialized) + 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() { - } + virtual ~nsThreadStartupEvent() {} private: - NS_IMETHOD Run() { + NS_IMETHOD Run() + { ReentrantMonitorAutoEnter mon(mMon); mInitialized = true; mon.Notify(); return NS_OK; } - nsThreadStartupEvent() - : mMon("nsThreadStartupEvent.mMon") - , mInitialized(false) { - } - ReentrantMonitor mMon; - bool mInitialized; + bool mInitialized; }; //----------------------------------------------------------------------------- -struct nsThreadShutdownContext { - nsThread *joiningThread; +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 { +class nsThreadShutdownAckEvent : public nsRunnable +{ public: - nsThreadShutdownAckEvent(nsThreadShutdownContext *ctx) - : mShutdownContext(ctx) { + explicit nsThreadShutdownAckEvent(nsThreadShutdownContext* aCtx) + : mShutdownContext(aCtx) + { } - NS_IMETHOD Run() { + NS_IMETHOD Run() + { mShutdownContext->shutdownAck = true; return NS_OK; } private: - nsThreadShutdownContext *mShutdownContext; + nsThreadShutdownContext* mShutdownContext; }; // This event is responsible for setting mShutdownContext -class nsThreadShutdownEvent : public nsRunnable { +class nsThreadShutdownEvent : public nsRunnable +{ public: - nsThreadShutdownEvent(nsThread *thr, nsThreadShutdownContext *ctx) - : mThread(thr), mShutdownContext(ctx) { - } - NS_IMETHOD Run() { + nsThreadShutdownEvent(nsThread* aThr, nsThreadShutdownContext* aCtx) + : mThread(aThr) + , mShutdownContext(aCtx) + { + } + NS_IMETHOD Run() + { mThread->mShutdownContext = mShutdownContext; + MessageLoop::current()->Quit(); return NS_OK; } private: nsRefPtr mThread; - nsThreadShutdownContext *mShutdownContext; + nsThreadShutdownContext* mShutdownContext; }; //----------------------------------------------------------------------------- +static void +SetupCurrentThreadForChaosMode() +{ + if (!ChaosMode::isActive(ChaosMode::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 + +#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::ThreadFunc(void* aArg) { - nsThread *self = static_cast(arg); // strong reference + using mozilla::ipc::BackgroundChild; + + nsThread* self = static_cast(aArg); // strong reference self->mThread = PR_GetCurrentThread(); + SetupCurrentThreadForChaosMode(); // Inform the ThreadManager nsThreadManager::get()->RegisterCurrentThread(self); +#ifdef MOZ_NUWA_PROCESS + self->mThreadStatusInfo = + static_cast(nsThreadManager::get()->GetCurrentThreadStatusInfo()); +#endif + + mozilla::IOInterposer::RegisterCurrentThread(); // Wait for and process startup event nsCOMPtr event; @@ -259,30 +342,39 @@ nsThread::ThreadFunc(void *arg) event->Run(); // unblocks nsThread::Init event = nullptr; - // Now, process incoming events... - while (!self->ShuttingDown()) - NS_ProcessNextEvent(self); - - // 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; + { + // Scope for MessageLoop. + nsAutoPtr loop( + new MessageLoop(MessageLoop::TYPE_MOZILLA_NONMAINTHREAD)); + + // Now, process incoming events... + loop->Run(); + + BackgroundChild::CloseForCurrentThread(); + + // 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); } - NS_ProcessPendingEvents(self); } + mozilla::IOInterposer::UnregisterCurrentThread(); + // Inform the threadmanager that this thread is going away nsThreadManager::get()->UnregisterCurrentThread(self); @@ -293,13 +385,22 @@ nsThread::ThreadFunc(void *arg) // 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) @@ -308,6 +409,10 @@ nsThread::nsThread(MainThreadFlag aMainThread, uint32_t aStackSize) , mShutdownRequired(false) , mEventsAreDoomed(false) , mIsMainThread(aMainThread) +#ifdef MOZ_NUWA_PROCESS + , mThreadStatusMonitor("nsThread.mThreadStatusLock") + , mThreadStatusInfo(nullptr) +#endif { } @@ -319,15 +424,14 @@ nsresult nsThread::Init() { // spawn thread and wait until it is fully setup - nsRefPtr startup = nsThreadStartupEvent::Create(); - NS_ENSURE_TRUE(startup, NS_ERROR_OUT_OF_MEMORY); - + nsRefPtr startup = new nsThreadStartupEvent(); + NS_ADDREF_THIS(); - + mShutdownRequired = true; // ThreadFunc is responsible for setting mThread - PRThread *thr = PR_CreateThread(PR_USER_THREAD, ThreadFunc, this, + PRThread* thr = PR_CreateThread(PR_USER_THREAD, ThreadFunc, this, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, mStackSize); if (!thr) { @@ -340,7 +444,7 @@ nsThread::Init() // that mThread is set properly. { MutexAutoLock lock(mLock); - mEvents.PutEvent(startup); + mEventsRoot.PutEvent(startup); } // Wait for thread to call ThreadManager::SetupCurrentThread, which completes @@ -353,71 +457,117 @@ nsresult nsThread::InitCurrentThread() { mThread = PR_GetCurrentThread(); + SetupCurrentThreadForChaosMode(); nsThreadManager::get()->RegisterCurrentThread(this); +#ifdef MOZ_NUWA_PROCESS + mThreadStatusInfo = + static_cast(nsThreadManager::get()->GetCurrentThreadStatusInfo()); +#endif + return NS_OK; } nsresult -nsThread::PutEvent(nsIRunnable *event) +nsThread::PutEvent(nsIRunnable* aEvent, nsNestedEventTarget* aTarget) { + nsCOMPtr obs; + { MutexAutoLock lock(mLock); - if (mEventsAreDoomed) { + 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; } - if (!mEvents.PutEvent(event)) - return NS_ERROR_OUT_OF_MEMORY; +#ifdef MOZ_NUWA_PROCESS + { + ReentrantMonitorAutoEnter mon(mThreadStatusMonitor); + SetWorking(); +#endif // MOZ_NUWA_PROCESS + queue->PutEvent(aEvent); +#ifdef MOZ_NUWA_PROCESS + } +#endif // MOZ_NUWA_PROCESS + + // 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; } - nsCOMPtr obs = GetObserver(); - if (obs) + if (obs) { obs->OnDispatchedEvent(this); + } return NS_OK; } -//----------------------------------------------------------------------------- -// nsIEventTarget - -NS_IMETHODIMP -nsThread::Dispatch(nsIRunnable *event, uint32_t flags) +nsresult +nsThread::DispatchInternal(nsIRunnable* aEvent, uint32_t aFlags, + nsNestedEventTarget* aTarget) { - LOG(("THRD(%p) Dispatch [%p %x]\n", this, event, flags)); + if (NS_WARN_IF(!aEvent)) { + return NS_ERROR_INVALID_ARG; + } - NS_ENSURE_ARG_POINTER(event); + if (gXPCOMThreadsShutDown && MAIN_THREAD != mIsMainThread && !aTarget) { + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + } + +#ifdef MOZ_TASK_TRACER + nsRefPtr tracedRunnable = CreateTracedRunnable(aEvent); + aEvent = tracedRunnable; +#endif - if (flags & DISPATCH_SYNC) { - nsThread *thread = nsThreadManager::get()->GetCurrentThread(); - NS_ENSURE_STATE(thread); + 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. - + nsRefPtr wrapper = - new nsThreadSyncDispatch(thread, event); - if (!wrapper) + new nsThreadSyncDispatch(thread, aEvent); + if (!wrapper) { return NS_ERROR_OUT_OF_MEMORY; - nsresult rv = PutEvent(wrapper); + } + nsresult rv = PutEvent(wrapper, aTarget); // Don't wait for the event to finish if we didn't dispatch it... - if (NS_FAILED(rv)) + if (NS_FAILED(rv)) { return rv; + } - while (wrapper->IsPending()) - NS_ProcessNextEvent(thread); + // 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); + NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL, "unexpected dispatch flags"); + return PutEvent(aEvent, aTarget); } +//----------------------------------------------------------------------------- +// nsIEventTarget + NS_IMETHODIMP -nsThread::IsOnCurrentThread(bool *result) +nsThread::Dispatch(nsIRunnable* aEvent, uint32_t aFlags) { - *result = (PR_GetCurrentThread() == mThread); + LOG(("THRD(%p) Dispatch [%p %x]\n", this, aEvent, aFlags)); + + return DispatchInternal(aEvent, aFlags, nullptr); +} + +NS_IMETHODIMP +nsThread::IsOnCurrentThread(bool* aResult) +{ + *aResult = (PR_GetCurrentThread() == mThread); return NS_OK; } @@ -425,9 +575,9 @@ nsThread::IsOnCurrentThread(bool *result) // nsIThread NS_IMETHODIMP -nsThread::GetPRThread(PRThread **result) +nsThread::GetPRThread(PRThread** aResult) { - *result = mThread; + *aResult = mThread; return NS_OK; } @@ -439,16 +589,20 @@ nsThread::Shutdown() // 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) + if (!mThread) { return NS_OK; + } - NS_ENSURE_STATE(mThread != PR_GetCurrentThread()); + if (NS_WARN_IF(mThread == PR_GetCurrentThread())) { + return NS_ERROR_UNEXPECTED; + } // Prevent multiple calls to this method { MutexAutoLock lock(mLock); - if (!mShutdownRequired) + if (!mShutdownRequired) { return NS_ERROR_UNEXPECTED; + } mShutdownRequired = false; } @@ -459,18 +613,21 @@ nsThread::Shutdown() // Set mShutdownContext and wake up the thread in case it is waiting for // events to process. nsCOMPtr event = new nsThreadShutdownEvent(this, &context); - if (!event) + if (!event) { return NS_ERROR_OUT_OF_MEMORY; + } // XXXroc What if posting the event fails due to OOM? - PutEvent(event); + 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. - while (!context.shutdownAck) - NS_ProcessNextEvent(context.joiningThread); + // 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. @@ -493,58 +650,51 @@ nsThread::Shutdown() } NS_IMETHODIMP -nsThread::HasPendingEvents(bool *result) +nsThread::HasPendingEvents(bool* aResult) { - NS_ENSURE_STATE(PR_GetCurrentThread() == mThread); + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } - *result = mEvents.GetEvent(false, nullptr); + *aResult = mEvents->GetEvent(false, nullptr); return NS_OK; } #ifdef MOZ_CANARY -void canary_alarm_handler (int signum); +void canary_alarm_handler(int signum); -class Canary { -//XXX ToDo: support nested loops +class Canary +{ + //XXX ToDo: support nested loops public: - Canary() { - if (sOutputFD != 0 && EventLatencyIsImportant()) { - if (sOutputFD == -1) { - const int flags = O_WRONLY | O_APPEND | O_CREAT | O_NONBLOCK; - const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; - char* env_var_flag = getenv("MOZ_KILL_CANARIES"); - sOutputFD = env_var_flag ? (env_var_flag[0] ? - open(env_var_flag, flags, mode) : - STDERR_FILENO) : 0; - if (sOutputFD == 0) - return; - } + Canary() + { + if (sCanaryOutputFD > 0 && EventLatencyIsImportant()) { signal(SIGALRM, canary_alarm_handler); - ualarm(15000, 0); + ualarm(15000, 0); } } - ~Canary() { - if (sOutputFD != 0 && EventLatencyIsImportant()) + ~Canary() + { + if (sCanaryOutputFD != 0 && EventLatencyIsImportant()) { ualarm(0, 0); + } } - static bool EventLatencyIsImportant() { + static bool EventLatencyIsImportant() + { return NS_IsMainThread() && XRE_GetProcessType() == GoannaProcessType_Default; } - - static int sOutputFD; }; -int Canary::sOutputFD = -1; - -void canary_alarm_handler (int signum) +void canary_alarm_handler(int signum) { - void *array[30]; + void* array[30]; const char msg[29] = "event took too long to run:\n"; // use write to be safe in the signal handler - write(Canary::sOutputFD, msg, sizeof(msg)); - backtrace_symbols_fd(array, backtrace(array, 30), Canary::sOutputFD); + write(sCanaryOutputFD, msg, sizeof(msg)); + backtrace_symbols_fd(array, backtrace(array, 30), sCanaryOutputFD); } #endif @@ -563,26 +713,49 @@ void canary_alarm_handler (int signum) PR_END_MACRO NS_IMETHODIMP -nsThread::ProcessNextEvent(bool mayWait, bool *result) +nsThread::ProcessNextEvent(bool aMayWait, bool* aResult) { - LOG(("THRD(%p) ProcessNextEvent [%u %u]\n", this, mayWait, mRunningEvent)); + LOG(("THRD(%p) ProcessNextEvent [%u %u]\n", this, aMayWait, mRunningEvent)); + + // If we're on the main thread, we shouldn't be dispatching CPOWs. + MOZ_RELEASE_ASSERT(mIsMainThread != MAIN_THREAD || + !ipc::ParentProcessIsBlocked()); - NS_ENSURE_STATE(PR_GetCurrentThread() == mThread); + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } - if (MAIN_THREAD == mIsMainThread && mayWait && !ShuttingDown()) + // 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 && (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()) { - bool mpPending = PR_ATOMIC_SET(&sMemoryPressurePending, 0); - if (mpPending) { + 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", - NS_LITERAL_STRING("low-memory").get()); - } - else { + mpPending == MemPressure_New ? lowMem.get() : + lowMemOngoing.get()); + } else { NS_WARNING("Can't get observer service!"); } } @@ -590,16 +763,17 @@ nsThread::ProcessNextEvent(bool mayWait, bool *result) bool notifyMainThreadObserver = (MAIN_THREAD == mIsMainThread) && sMainThreadObserver; - if (notifyMainThreadObserver) - sMainThreadObserver->OnProcessNextEvent(this, mayWait && !ShuttingDown(), - mRunningEvent); + if (notifyMainThreadObserver) { + sMainThreadObserver->OnProcessNextEvent(this, reallyWait, mRunningEvent); + } nsCOMPtr obs = mObserver; - if (obs) - obs->OnProcessNextEvent(this, mayWait && !ShuttingDown(), mRunningEvent); + if (obs) { + obs->OnProcessNextEvent(this, reallyWait, mRunningEvent); + } NOTIFY_EVENT_OBSERVERS(OnProcessNextEvent, - (this, mayWait && !ShuttingDown(), mRunningEvent)); + (this, reallyWait, mRunningEvent)); ++mRunningEvent; @@ -615,16 +789,17 @@ nsThread::ProcessNextEvent(bool mayWait, bool *result) // If we are shutting down, then do not wait for new events. nsCOMPtr event; - mEvents.GetEvent(mayWait && !ShuttingDown(), getter_AddRefs(event)); + mEvents->GetEvent(reallyWait, getter_AddRefs(event)); - *result = (event.get() != nullptr); + *aResult = (event.get() != nullptr); if (event) { LOG(("THRD(%p) running [%p]\n", this, event.get())); - if (MAIN_THREAD == mIsMainThread) + if (MAIN_THREAD == mIsMainThread) { HangMonitor::NotifyActivity(); + } event->Run(); - } else if (mayWait) { + } else if (aMayWait) { MOZ_ASSERT(ShuttingDown(), "This should only happen when shutting down"); rv = NS_ERROR_UNEXPECTED; @@ -633,13 +808,37 @@ nsThread::ProcessNextEvent(bool mayWait, bool *result) --mRunningEvent; - NOTIFY_EVENT_OBSERVERS(AfterProcessNextEvent, (this, mRunningEvent)); +#ifdef MOZ_NUWA_PROCESS + nsCOMPtr notifyAllIdleRunnable; + { + ReentrantMonitorAutoEnter mon(mThreadStatusMonitor); + if ((!mEvents->GetEvent(false, nullptr)) && (mRunningEvent == 0)) { + nsThreadManager::get()->SetThreadIsWorking( + static_cast(mThreadStatusInfo), + false, getter_AddRefs(notifyAllIdleRunnable)); + } + } + if (notifyAllIdleRunnable) { + // Dispatching a task leads us to acquire |mLock| of the thread. If we + // dispatch to main thread while holding main thread's + // |mThreadStatusMonitor|, deadlock could happen if other thread is + // blocked by main thread's |mThreadStatusMonitor| and is holding + // main thread's |mLock|. + Dispatch(notifyAllIdleRunnable, NS_DISPATCH_NORMAL); + nsThreadManager::get()->ResetIsDispatchingToMainThread(); + } +#endif // MOZ_NUWA_PROCESS + + NOTIFY_EVENT_OBSERVERS(AfterProcessNextEvent, + (this, mRunningEvent, *aResult)); - if (obs) - obs->AfterProcessNextEvent(this, mRunningEvent); + if (obs) { + obs->AfterProcessNextEvent(this, mRunningEvent, *aResult); + } - if (notifyMainThreadObserver && sMainThreadObserver) - sMainThreadObserver->AfterProcessNextEvent(this, mRunningEvent); + if (notifyMainThreadObserver && sMainThreadObserver) { + sMainThreadObserver->AfterProcessNextEvent(this, mRunningEvent, *aResult); + } return rv; } @@ -648,16 +847,18 @@ nsThread::ProcessNextEvent(bool mayWait, bool *result) // nsISupportsPriority NS_IMETHODIMP -nsThread::GetPriority(int32_t *priority) +nsThread::GetPriority(int32_t* aPriority) { - *priority = mPriority; + *aPriority = mPriority; return NS_OK; } NS_IMETHODIMP -nsThread::SetPriority(int32_t priority) +nsThread::SetPriority(int32_t aPriority) { - NS_ENSURE_STATE(mThread); + if (NS_WARN_IF(!mThread)) { + return NS_ERROR_NOT_INITIALIZED; + } // NSPR defines the following four thread priorities: // PR_PRIORITY_LOW @@ -666,7 +867,7 @@ nsThread::SetPriority(int32_t priority) // PR_PRIORITY_URGENT // We map the priority values defined on nsISupportsPriority to these values. - mPriority = priority; + mPriority = aPriority; PRThreadPriority pri; if (mPriority <= PRIORITY_HIGHEST) { @@ -678,58 +879,68 @@ nsThread::SetPriority(int32_t priority) } else { pri = PR_PRIORITY_NORMAL; } - PR_SetThreadPriority(mThread, pri); + // If chaos mode is active, retain the randomly chosen priority + if (!ChaosMode::isActive(ChaosMode::ThreadScheduling)) { + PR_SetThreadPriority(mThread, pri); + } return NS_OK; } NS_IMETHODIMP -nsThread::AdjustPriority(int32_t delta) +nsThread::AdjustPriority(int32_t aDelta) { - return SetPriority(mPriority + delta); + return SetPriority(mPriority + aDelta); } //----------------------------------------------------------------------------- // nsIThreadInternal NS_IMETHODIMP -nsThread::GetObserver(nsIThreadObserver **obs) +nsThread::GetObserver(nsIThreadObserver** aObs) { MutexAutoLock lock(mLock); - NS_IF_ADDREF(*obs = mObserver); + NS_IF_ADDREF(*aObs = mObserver); return NS_OK; } NS_IMETHODIMP -nsThread::SetObserver(nsIThreadObserver *obs) +nsThread::SetObserver(nsIThreadObserver* aObs) { - NS_ENSURE_STATE(PR_GetCurrentThread() == mThread); + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } MutexAutoLock lock(mLock); - mObserver = obs; + mObserver = aObs; return NS_OK; } NS_IMETHODIMP -nsThread::GetRecursionDepth(uint32_t *depth) +nsThread::GetRecursionDepth(uint32_t* aDepth) { - NS_ENSURE_ARG_POINTER(depth); - NS_ENSURE_STATE(PR_GetCurrentThread() == mThread); + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } - *depth = mRunningEvent; + *aDepth = mRunningEvent; return NS_OK; } NS_IMETHODIMP -nsThread::AddObserver(nsIThreadObserver *observer) +nsThread::AddObserver(nsIThreadObserver* aObserver) { - NS_ENSURE_ARG_POINTER(observer); - NS_ENSURE_STATE(PR_GetCurrentThread() == mThread); + if (NS_WARN_IF(!aObserver)) { + 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), + NS_WARN_IF_FALSE(!mEventObservers.Contains(aObserver), "Adding an observer twice!"); - if (!mEventObservers.AppendElement(observer)) { + if (!mEventObservers.AppendElement(aObserver)) { NS_WARNING("Out of memory!"); return NS_ERROR_OUT_OF_MEMORY; } @@ -738,17 +949,80 @@ nsThread::AddObserver(nsIThreadObserver *observer) } NS_IMETHODIMP -nsThread::RemoveObserver(nsIThreadObserver *observer) +nsThread::RemoveObserver(nsIThreadObserver* aObserver) { - NS_ENSURE_STATE(PR_GetCurrentThread() == mThread); + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } - if (observer && !mEventObservers.RemoveElement(observer)) { + 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; + } + + nsChainedEventQueue* queue = new nsChainedEventQueue(); + queue->mEventTarget = new nsNestedEventTarget(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 queue; + nsRefPtr 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 = 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) { @@ -764,6 +1038,24 @@ nsThread::SetMainThreadObserver(nsIThreadObserver* aObserver) return NS_OK; } +#ifdef MOZ_NUWA_PROCESS +void +nsThread::SetWorking() +{ + nsThreadManager::get()->SetThreadIsWorking( + static_cast(mThreadStatusInfo), + true, nullptr); +} + +void +nsThread::SetIdle() +{ + nsThreadManager::get()->SetThreadIsWorking( + static_cast(mThreadStatusInfo), + false, nullptr); +} +#endif + //----------------------------------------------------------------------------- NS_IMETHODIMP @@ -777,3 +1069,22 @@ nsThreadSyncDispatch::Run() } return NS_OK; } + +//----------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS(nsThread::nsNestedEventTarget, nsIEventTarget) + +NS_IMETHODIMP +nsThread::nsNestedEventTarget::Dispatch(nsIRunnable* aEvent, uint32_t aFlags) +{ + LOG(("THRD(%p) Dispatch [%p %x] to nested loop %p\n", mThread.get(), aEvent, + aFlags, this)); + + return mThread->DispatchInternal(aEvent, aFlags, this); +} + +NS_IMETHODIMP +nsThread::nsNestedEventTarget::IsOnCurrentThread(bool* aResult) +{ + return mThread->IsOnCurrentThread(aResult); +} diff --git a/xpcom/threads/nsThread.h b/xpcom/threads/nsThread.h index 19c509216..3d68ab69b 100644 --- a/xpcom/threads/nsThread.h +++ b/xpcom/threads/nsThread.h @@ -1,5 +1,5 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* -*- 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/. */ @@ -15,19 +15,23 @@ #include "nsString.h" #include "nsTObserverArray.h" #include "mozilla/Attributes.h" +#include "nsAutoPtr.h" +#include "mozilla/ReentrantMonitor.h" // A native thread -class nsThread MOZ_FINAL : public nsIThreadInternal, - public nsISupportsPriority +class nsThread + : public nsIThreadInternal + , public nsISupportsPriority { public: - NS_DECL_ISUPPORTS + NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIEVENTTARGET NS_DECL_NSITHREAD NS_DECL_NSITHREADINTERNAL NS_DECL_NSISUPPORTSPRIORITY - enum MainThreadFlag { + enum MainThreadFlag + { MAIN_THREAD, NOT_MAIN_THREAD }; @@ -41,41 +45,125 @@ public: nsresult InitCurrentThread(); // The PRThread corresponding to this thread. - PRThread *GetPRThread() { return mThread; } + PRThread* GetPRThread() + { + return mThread; + } // If this flag is true, then the nsThread was created using // nsIThreadManager::NewThread. - bool ShutdownRequired() { return mShutdownRequired; } + bool ShutdownRequired() + { + return mShutdownRequired; + } // Clear the observer list. - void ClearObservers() { mEventObservers.Clear(); } + void ClearObservers() + { + mEventObservers.Clear(); + } static nsresult SetMainThreadObserver(nsIThreadObserver* aObserver); -private: +#ifdef MOZ_NUWA_PROCESS + void SetWorking(); + void SetIdle(); + mozilla::ReentrantMonitor& ThreadStatusMonitor() { + return mThreadStatusMonitor; + } +#endif + +protected: static nsIThreadObserver* sMainThreadObserver; + class nsChainedEventQueue; + + class nsNestedEventTarget; + friend class nsNestedEventTarget; + friend class nsThreadShutdownEvent; - ~nsThread(); + virtual ~nsThread(); - bool ShuttingDown() { return mShutdownContext != nullptr; } + bool ShuttingDown() + { + return mShutdownContext != nullptr; + } - static void ThreadFunc(void *arg); + static void ThreadFunc(void* aArg); // Helper - already_AddRefed GetObserver() { - nsIThreadObserver *obs; + already_AddRefed GetObserver() + { + nsIThreadObserver* obs; nsThread::GetObserver(&obs); return already_AddRefed(obs); } // Wrappers for event queue methods: - bool GetEvent(bool mayWait, nsIRunnable **event) { - return mEvents.GetEvent(mayWait, event); + bool GetEvent(bool aMayWait, nsIRunnable** aEvent) + { + return mEvents->GetEvent(aMayWait, aEvent); } - nsresult PutEvent(nsIRunnable *event); + nsresult PutEvent(nsIRunnable* aEvent, nsNestedEventTarget* aTarget); + + nsresult DispatchInternal(nsIRunnable* aEvent, uint32_t aFlags, + nsNestedEventTarget* aTarget); + + // Wrapper for nsEventQueue that supports chaining. + class nsChainedEventQueue + { + public: + nsChainedEventQueue() + : mNext(nullptr) + { + } + + bool GetEvent(bool aMayWait, nsIRunnable** aEvent) + { + return mQueue.GetEvent(aMayWait, aEvent); + } + + void PutEvent(nsIRunnable* aEvent) + { + mQueue.PutEvent(aEvent); + } + + bool HasPendingEvent() + { + return mQueue.HasPendingEvent(); + } + + nsChainedEventQueue* mNext; + nsRefPtr mEventTarget; + + private: + nsEventQueue mQueue; + }; + + class nsNestedEventTarget final : public nsIEventTarget + { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIEVENTTARGET + + nsNestedEventTarget(nsThread* aThread, nsChainedEventQueue* aQueue) + : mThread(aThread) + , mQueue(aQueue) + { + } + + nsRefPtr mThread; + + // This is protected by mThread->mLock. + nsChainedEventQueue* mQueue; + + private: + ~nsNestedEventTarget() + { + } + }; // This lock protects access to mObserver, mEvents and mEventsAreDoomed. // All of those fields are only modified on the thread itself (never from @@ -89,34 +177,47 @@ private: // Only accessed on the target thread. nsAutoTObserverArray, 2> mEventObservers; - nsEventQueue mEvents; + nsChainedEventQueue* mEvents; // never null + nsChainedEventQueue mEventsRoot; int32_t mPriority; - PRThread *mThread; + PRThread* mThread; uint32_t mRunningEvent; // counter uint32_t mStackSize; - struct nsThreadShutdownContext *mShutdownContext; + struct nsThreadShutdownContext* mShutdownContext; bool mShutdownRequired; // Set to true when events posted to this thread will never run. bool mEventsAreDoomed; MainThreadFlag mIsMainThread; +#ifdef MOZ_NUWA_PROCESS + mozilla::ReentrantMonitor mThreadStatusMonitor; + // The actual type is defined in nsThreadManager.h which is not exposed to + // file out of thread module. + void* mThreadStatusInfo; +#endif }; //----------------------------------------------------------------------------- -class nsThreadSyncDispatch : public nsRunnable { +class nsThreadSyncDispatch : public nsRunnable +{ public: - nsThreadSyncDispatch(nsIThread *origin, nsIRunnable *task) - : mOrigin(origin), mSyncTask(task), mResult(NS_ERROR_NOT_INITIALIZED) { + nsThreadSyncDispatch(nsIThread* aOrigin, nsIRunnable* aTask) + : mOrigin(aOrigin) + , mSyncTask(aTask) + , mResult(NS_ERROR_NOT_INITIALIZED) + { } - bool IsPending() { + bool IsPending() + { return mSyncTask != nullptr; } - nsresult Result() { + nsresult Result() + { return mResult; } @@ -128,16 +229,11 @@ private: nsresult mResult; }; -namespace mozilla { - -/** - * This function causes the main thread to fire a memory pressure event at its - * next available opportunity. - * - * You may call this function from any thread. - */ -void ScheduleMemoryPressureEvent(); +#if defined(XP_UNIX) && !defined(ANDROID) && !defined(DEBUG) && HAVE_UALARM \ + && defined(_GNU_SOURCE) +# define MOZ_CANARY -} // namespace mozilla +extern int sCanaryOutputFD; +#endif #endif // nsThread_h__ diff --git a/xpcom/threads/nsThreadManager.cpp b/xpcom/threads/nsThreadManager.cpp index fe70a28c2..d85161804 100644 --- a/xpcom/threads/nsThreadManager.cpp +++ b/xpcom/threads/nsThreadManager.cpp @@ -1,5 +1,5 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* -*- 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/. */ @@ -10,7 +10,12 @@ #include "nsIClassInfoImpl.h" #include "nsTArray.h" #include "nsAutoPtr.h" -#include "nsCycleCollectorUtils.h" +#include "mozilla/ThreadLocal.h" +#include "mozilla/ReentrantMonitor.h" +#ifdef MOZ_CANARY +#include +#include +#endif using namespace mozilla; @@ -21,49 +26,158 @@ DWORD gTLSThreadIDIndex = TlsAlloc(); NS_TLS mozilla::threads::ID gTLSThreadID = mozilla::threads::Generic; #endif -typedef nsTArray< nsRefPtr > nsThreadArray; +static mozilla::ThreadLocal sTLSIsMainThread; + +bool +NS_IsMainThread() +{ + return sTLSIsMainThread.get(); +} + +void +NS_SetMainThread() +{ + if (!sTLSIsMainThread.initialized()) { + if (!sTLSIsMainThread.init()) { + MOZ_CRASH(); + } + sTLSIsMainThread.set(true); + } + MOZ_ASSERT(NS_IsMainThread()); +} + +typedef nsTArray> nsThreadArray; + +#ifdef MOZ_NUWA_PROCESS +class NotifyAllThreadsWereIdle: public nsRunnable +{ +public: + + NotifyAllThreadsWereIdle( + nsTArray>* aListeners) + : mListeners(aListeners) + { + } + + virtual NS_IMETHODIMP + Run() { + // Copy listener array, which may be modified during call back. + nsTArray> arr(*mListeners); + for (size_t i = 0; i < arr.Length(); i++) { + arr[i]->OnAllThreadsWereIdle(); + } + return NS_OK; + } + +private: + // Raw pointer, since it's pointing to a member of thread manager. + nsTArray>* mListeners; +}; + +struct nsThreadManager::ThreadStatusInfo { + Atomic mWorking; + Atomic mWillBeWorking; + bool mIgnored; + ThreadStatusInfo() + : mWorking(false) + , mWillBeWorking(false) + , mIgnored(false) + { + } +}; +#endif // MOZ_NUWA_PROCESS //----------------------------------------------------------------------------- static void -ReleaseObject(void *data) +ReleaseObject(void* aData) +{ + static_cast(aData)->Release(); +} + +#ifdef MOZ_NUWA_PROCESS +void +nsThreadManager::DeleteThreadStatusInfo(void* aData) { - static_cast(data)->Release(); + nsThreadManager* mgr = nsThreadManager::get(); + nsThreadManager::ThreadStatusInfo* thrInfo = + static_cast(aData); + { + ReentrantMonitorAutoEnter mon(*(mgr->mMonitor)); + mgr->mThreadStatusInfos.RemoveElement(thrInfo); + if (NS_IsMainThread()) { + mgr->mMainThreadStatusInfo = nullptr; + } + } + delete thrInfo; } +#endif static PLDHashOperator -AppendAndRemoveThread(PRThread *key, nsRefPtr &thread, void *arg) +AppendAndRemoveThread(PRThread* aKey, nsRefPtr& aThread, void* aArg) { - nsThreadArray *threads = static_cast(arg); - threads->AppendElement(thread); + nsThreadArray* threads = static_cast(aArg); + threads->AppendElement(aThread); return PL_DHASH_REMOVE; } // statically allocated instance -NS_IMETHODIMP_(nsrefcnt) nsThreadManager::AddRef() { return 2; } -NS_IMETHODIMP_(nsrefcnt) nsThreadManager::Release() { return 1; } -NS_IMPL_CLASSINFO(nsThreadManager, NULL, +NS_IMETHODIMP_(MozExternalRefCountType) +nsThreadManager::AddRef() +{ + return 2; +} +NS_IMETHODIMP_(MozExternalRefCountType) +nsThreadManager::Release() +{ + return 1; +} +NS_IMPL_CLASSINFO(nsThreadManager, nullptr, nsIClassInfo::THREADSAFE | nsIClassInfo::SINGLETON, NS_THREADMANAGER_CID) -NS_IMPL_QUERY_INTERFACE1_CI(nsThreadManager, nsIThreadManager) -NS_IMPL_CI_INTERFACE_GETTER1(nsThreadManager, nsIThreadManager) +NS_IMPL_QUERY_INTERFACE_CI(nsThreadManager, nsIThreadManager) +NS_IMPL_CI_INTERFACE_GETTER(nsThreadManager, nsIThreadManager) //----------------------------------------------------------------------------- nsresult nsThreadManager::Init() { - mThreadsByPRThread.Init(); + // Child processes need to initialize the thread manager before they + // initialize XPCOM in order to set up the crash reporter. This leads to + // situations where we get initialized twice. + if (mInitialized) { + return NS_OK; + } - if (PR_NewThreadPrivateIndex(&mCurThreadIndex, ReleaseObject) == PR_FAILURE) + if (PR_NewThreadPrivateIndex(&mCurThreadIndex, ReleaseObject) == PR_FAILURE) { return NS_ERROR_FAILURE; + } - mLock = new Mutex("nsThreadManager.mLock"); +#ifdef MOZ_NUWA_PROCESS + if (PR_NewThreadPrivateIndex( + &mThreadStatusInfoIndex, + nsThreadManager::DeleteThreadStatusInfo) == PR_FAILURE) { + return NS_ERROR_FAILURE; + } +#endif // MOZ_NUWA_PROCESS + +#ifdef MOZ_NUWA_PROCESS + mMonitor = MakeUnique("nsThreadManager.mMonitor"); +#endif // MOZ_NUWA_PROCESS + +#ifdef MOZ_CANARY + const int flags = O_WRONLY | O_APPEND | O_CREAT | O_NONBLOCK; + const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + char* env_var_flag = getenv("MOZ_KILL_CANARIES"); + sCanaryOutputFD = + env_var_flag ? (env_var_flag[0] ? open(env_var_flag, flags, mode) : + STDERR_FILENO) : + 0; +#endif // Setup "main" thread mMainThread = new nsThread(nsThread::MAIN_THREAD, 0); - if (!mMainThread) - return NS_ERROR_OUT_OF_MEMORY; nsresult rv = mMainThread->InitCurrentThread(); if (NS_FAILED(rv)) { @@ -76,7 +190,7 @@ nsThreadManager::Init() mMainThread->GetPRThread(&mMainPRThread); #ifdef XP_WIN - TlsSetValue(gTLSThreadIDIndex, (void*) mozilla::threads::Main); + TlsSetValue(gTLSThreadIDIndex, (void*)mozilla::threads::Main); #elif defined(NS_TLS) gTLSThreadID = mozilla::threads::Main; #endif @@ -92,8 +206,10 @@ nsThreadManager::Shutdown() // Prevent further access to the thread manager (no more new threads!) // - // XXX What happens if shutdown happens before NewThread completes? - // Fortunately, NewThread is only called on the main thread for now. + // What happens if shutdown happens before NewThread completes? + // We Shutdown() the new thread, and return error if we've started Shutdown + // between when NewThread started, and when the thread finished initializing + // and registering with ThreadManager. // mInitialized = false; @@ -104,7 +220,7 @@ nsThreadManager::Shutdown() // holding the hashtable lock while calling nsIThread::Shutdown. nsThreadArray threads; { - MutexAutoLock lock(*mLock); + OffTheBooksMutexAutoLock lock(mLock); mThreadsByPRThread.Enumerate(AppendAndRemoveThread, &threads); } @@ -112,16 +228,17 @@ nsThreadManager::Shutdown() // accepting new events, but that could lead to badness if one of those // threads is stuck waiting for a response from another thread. To do it // right, we'd need some way to interrupt the threads. - // + // // Instead, we process events on the current thread while waiting for threads // to shutdown. This means that we have to preserve a mostly functioning // world until such time as the threads exit. // Shutdown all threads that require it (join with threads that we created). for (uint32_t i = 0; i < threads.Length(); ++i) { - nsThread *thread = threads[i]; - if (thread->ShutdownRequired()) + nsThread* thread = threads[i]; + if (thread->ShutdownRequired()) { thread->Shutdown(); + } } // In case there are any more events somehow... @@ -131,7 +248,7 @@ nsThreadManager::Shutdown() // Clear the table of threads. { - MutexAutoLock lock(*mLock); + OffTheBooksMutexAutoLock lock(mLock); mThreadsByPRThread.Clear(); } @@ -143,51 +260,57 @@ nsThreadManager::Shutdown() // Release main thread object. mMainThread = nullptr; - mLock = nullptr; // Remove the TLS entry for the main thread. PR_SetThreadPrivate(mCurThreadIndex, nullptr); +#ifdef MOZ_NUWA_PROCESS + PR_SetThreadPrivate(mThreadStatusInfoIndex, nullptr); +#endif } void -nsThreadManager::RegisterCurrentThread(nsThread *thread) +nsThreadManager::RegisterCurrentThread(nsThread* aThread) { - MOZ_ASSERT(thread->GetPRThread() == PR_GetCurrentThread(), "bad thread"); + MOZ_ASSERT(aThread->GetPRThread() == PR_GetCurrentThread(), "bad aThread"); - MutexAutoLock lock(*mLock); + OffTheBooksMutexAutoLock lock(mLock); ++mCurrentNumberOfThreads; if (mCurrentNumberOfThreads > mHighestNumberOfThreads) { mHighestNumberOfThreads = mCurrentNumberOfThreads; } - mThreadsByPRThread.Put(thread->GetPRThread(), thread); // XXX check OOM? + mThreadsByPRThread.Put(aThread->GetPRThread(), aThread); // XXX check OOM? - NS_ADDREF(thread); // for TLS entry - PR_SetThreadPrivate(mCurThreadIndex, thread); + NS_ADDREF(aThread); // for TLS entry + PR_SetThreadPrivate(mCurThreadIndex, aThread); } void -nsThreadManager::UnregisterCurrentThread(nsThread *thread) +nsThreadManager::UnregisterCurrentThread(nsThread* aThread) { - MOZ_ASSERT(thread->GetPRThread() == PR_GetCurrentThread(), "bad thread"); + MOZ_ASSERT(aThread->GetPRThread() == PR_GetCurrentThread(), "bad aThread"); - MutexAutoLock lock(*mLock); + OffTheBooksMutexAutoLock lock(mLock); --mCurrentNumberOfThreads; - mThreadsByPRThread.Remove(thread->GetPRThread()); + mThreadsByPRThread.Remove(aThread->GetPRThread()); PR_SetThreadPrivate(mCurThreadIndex, nullptr); // Ref-count balanced via ReleaseObject +#ifdef MOZ_NUWA_PROCESS + PR_SetThreadPrivate(mThreadStatusInfoIndex, nullptr); +#endif } -nsThread * +nsThread* nsThreadManager::GetCurrentThread() { // read thread local storage - void *data = PR_GetThreadPrivate(mCurThreadIndex); - if (data) - return static_cast(data); + void* data = PR_GetThreadPrivate(mCurThreadIndex); + if (data) { + return static_cast(data); + } if (!mInitialized) { return nullptr; @@ -195,96 +318,281 @@ nsThreadManager::GetCurrentThread() // OK, that's fine. We'll dynamically create one :-) nsRefPtr thread = new nsThread(nsThread::NOT_MAIN_THREAD, 0); - if (!thread || NS_FAILED(thread->InitCurrentThread())) + if (!thread || NS_FAILED(thread->InitCurrentThread())) { return nullptr; + } return thread.get(); // reference held in TLS } +#ifdef MOZ_NUWA_PROCESS +nsThreadManager::ThreadStatusInfo* +nsThreadManager::GetCurrentThreadStatusInfo() +{ + void* data = PR_GetThreadPrivate(mThreadStatusInfoIndex); + if (!data) { + ThreadStatusInfo *thrInfo = new ThreadStatusInfo(); + PR_SetThreadPrivate(mThreadStatusInfoIndex, thrInfo); + data = thrInfo; + + ReentrantMonitorAutoEnter mon(*mMonitor); + mThreadStatusInfos.AppendElement(thrInfo); + if (NS_IsMainThread()) { + mMainThreadStatusInfo = thrInfo; + } + } + + return static_cast(data); +} +#endif + NS_IMETHODIMP -nsThreadManager::NewThread(uint32_t creationFlags, - uint32_t stackSize, - nsIThread **result) +nsThreadManager::NewThread(uint32_t aCreationFlags, + uint32_t aStackSize, + nsIThread** aResult) { + // Note: can be called from arbitrary threads + // No new threads during Shutdown - NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED); - - nsThread *thr = new nsThread(nsThread::NOT_MAIN_THREAD, stackSize); - if (!thr) - return NS_ERROR_OUT_OF_MEMORY; - NS_ADDREF(thr); + if (NS_WARN_IF(!mInitialized)) { + return NS_ERROR_NOT_INITIALIZED; + } - nsresult rv = thr->Init(); + nsRefPtr thr = new nsThread(nsThread::NOT_MAIN_THREAD, aStackSize); + nsresult rv = thr->Init(); // Note: blocks until the new thread has been set up if (NS_FAILED(rv)) { - NS_RELEASE(thr); return rv; } - // At this point, we expect that the thread has been registered in mThread; + // At this point, we expect that the thread has been registered in mThreadByPRThread; // however, it is possible that it could have also been replaced by now, so - // we cannot really assert that it was added. + // we cannot really assert that it was added. Instead, kill it if we entered + // Shutdown() during/before Init() + + if (NS_WARN_IF(!mInitialized)) { + if (thr->ShutdownRequired()) { + thr->Shutdown(); // ok if it happens multiple times + } + return NS_ERROR_NOT_INITIALIZED; + } - *result = thr; + thr.forget(aResult); return NS_OK; } NS_IMETHODIMP -nsThreadManager::GetThreadFromPRThread(PRThread *thread, nsIThread **result) +nsThreadManager::GetThreadFromPRThread(PRThread* aThread, nsIThread** aResult) { // Keep this functioning during Shutdown - NS_ENSURE_TRUE(mMainThread, NS_ERROR_NOT_INITIALIZED); - NS_ENSURE_ARG_POINTER(thread); + if (NS_WARN_IF(!mMainThread)) { + return NS_ERROR_NOT_INITIALIZED; + } + if (NS_WARN_IF(!aThread)) { + return NS_ERROR_INVALID_ARG; + } nsRefPtr temp; { - MutexAutoLock lock(*mLock); - mThreadsByPRThread.Get(thread, getter_AddRefs(temp)); + OffTheBooksMutexAutoLock lock(mLock); + mThreadsByPRThread.Get(aThread, getter_AddRefs(temp)); } - NS_IF_ADDREF(*result = temp); + NS_IF_ADDREF(*aResult = temp); return NS_OK; } NS_IMETHODIMP -nsThreadManager::GetMainThread(nsIThread **result) +nsThreadManager::GetMainThread(nsIThread** aResult) { // Keep this functioning during Shutdown - NS_ENSURE_TRUE(mMainThread, NS_ERROR_NOT_INITIALIZED); - NS_ADDREF(*result = mMainThread); + if (NS_WARN_IF(!mMainThread)) { + return NS_ERROR_NOT_INITIALIZED; + } + NS_ADDREF(*aResult = mMainThread); return NS_OK; } NS_IMETHODIMP -nsThreadManager::GetCurrentThread(nsIThread **result) +nsThreadManager::GetCurrentThread(nsIThread** aResult) { // Keep this functioning during Shutdown - NS_ENSURE_TRUE(mMainThread, NS_ERROR_NOT_INITIALIZED); - *result = GetCurrentThread(); - if (!*result) + if (NS_WARN_IF(!mMainThread)) { + return NS_ERROR_NOT_INITIALIZED; + } + *aResult = GetCurrentThread(); + if (!*aResult) { return NS_ERROR_OUT_OF_MEMORY; - NS_ADDREF(*result); + } + NS_ADDREF(*aResult); return NS_OK; } NS_IMETHODIMP -nsThreadManager::GetIsMainThread(bool *result) +nsThreadManager::GetIsMainThread(bool* aResult) { // This method may be called post-Shutdown - *result = (PR_GetCurrentThread() == mMainPRThread); - return NS_OK; -} - -NS_IMETHODIMP -nsThreadManager::GetIsCycleCollectorThread(bool *result) -{ - *result = bool(NS_IsCycleCollectorThread()); + *aResult = (PR_GetCurrentThread() == mMainPRThread); return NS_OK; } uint32_t nsThreadManager::GetHighestNumberOfThreads() { - MutexAutoLock lock(*mLock); + OffTheBooksMutexAutoLock lock(mLock); return mHighestNumberOfThreads; } + +#ifdef MOZ_NUWA_PROCESS +void +nsThreadManager::SetIgnoreThreadStatus() +{ + GetCurrentThreadStatusInfo()->mIgnored = true; +} + +void +nsThreadManager::SetThreadIdle(nsIRunnable **aReturnRunnable) +{ + SetThreadIsWorking(GetCurrentThreadStatusInfo(), false, aReturnRunnable); +} + +void +nsThreadManager::SetThreadWorking() +{ + SetThreadIsWorking(GetCurrentThreadStatusInfo(), true, nullptr); +} + +void +nsThreadManager::SetThreadIsWorking(ThreadStatusInfo* aInfo, + bool aIsWorking, + nsIRunnable **aReturnRunnable) +{ + aInfo->mWillBeWorking = aIsWorking; + if (mThreadsIdledListeners.Length() > 0) { + + // A race condition occurs since we don't want threads to try to enter the + // monitor (nsThreadManager::mMonitor) when no one cares about their status. + // And thus the race can happen when we put the first listener into + // |mThreadsIdledListeners|: + // + // (1) Thread A wants to dispatch a task to Thread B. + // (2) Thread A checks |mThreadsIdledListeners|, and nothing is in the + // list. So Thread A decides not to enter |mMonitor| when updating B's + // status. + // (3) Thread A is suspended just before it changed status of B. + // (4) A listener is added to |mThreadsIdledListeners| + // (5) Now is Thread C's turn to run. Thread C finds there's something in + // |mThreadsIdledListeners|, so it enters |mMonitor| and check all + // thread info structs in |mThreadStatusInfos| while A is in the middle + // of changing B's status. + // + // Then C may find Thread B is an idle thread (which is not correct, because + // A attempted to change B's status prior to C starting to walk throught + // |mThreadStatusInfo|), but the fact that thread A is working (thread A + // hasn't finished dispatching a task to thread B) can prevent thread C from + // firing a bogus notification. + // + // If the state transition that happens outside the monitor is in the other + // direction, the race condition could be: + // + // (1) Thread D has just finished its jobs and wants to set its status to idle. + // (2) Thread D checks |mThreadsIdledListeners|, and nothing is in the list. + // So Thread D decides not to enter |mMonitor|. + // (3) Thread D is is suspended before it updates its own status. + // (4) A listener is put into |mThreadsIdledListeners|. + // (5) Thread C wants to changes status of itself. It checks + // |mThreadsIdledListeners| and finds something inside the list. Thread C + // then enters |mMonitor|, updates its status and checks thread info in + // |mThreadStatusInfos| while D is changing status of itself out of monitor. + // + // Thread C will find that thread D is working (D actually wants to change its + // status to idle before C starting to check), then C returns without firing + // any notification. Finding that thread D is working can make our checking + // mechanism miss a chance to fire a notification: because thread D thought + // there's nothing in |mThreadsIdledListeners| and thus won't check the + // |mThreadStatusInfos| after changing the status of itself. + // + // |mWillBeWorking| can be used to address this problem. We require each + // thread to put the value that is going to be set to |mWorking| to + // |mWillBeWorking| before the thread decide whether it should enter + // |mMonitor| to change status or not. Thus C finds that D is working while + // D's |mWillBeWorking| is false, and C realizes that D is just updating and + // can treat D as an idle thread. + // + // It doesn't matter whether D will check thread status after changing its + // own status or not. If D checks, which means D will enter the monitor + // before updating status, thus D must be blocked until C has finished + // dispatching the notification task to main thread, and D will find that main + // thread is working and will not fire an additional event. On the other hand, + // if D doesn't check |mThreadStatusInfos|, it's still ok, because C has + // treated D as an idle thread already. + + bool hasWorkingThread = false; + nsRefPtr runnable; + { + ReentrantMonitorAutoEnter mon(*mMonitor); + // Get data structure of thread info. + aInfo->mWorking = aIsWorking; + if (aIsWorking) { + // We are working, so there's no need to check futher. + return; + } + + for (size_t i = 0; i < mThreadStatusInfos.Length(); i++) { + ThreadStatusInfo *info = mThreadStatusInfos[i]; + if (!info->mIgnored) { + if (info->mWorking) { + if (info->mWillBeWorking) { + hasWorkingThread = true; + break; + } + } + } + } + if (!hasWorkingThread && !mDispatchingToMainThread) { + runnable = new NotifyAllThreadsWereIdle(&mThreadsIdledListeners); + mDispatchingToMainThread = true; + } + } + + if (runnable) { + if (NS_IsMainThread()) { + // We are holding the main thread's |nsThread::mThreadStatusMonitor|. + // If we dispatch a task to ourself, then we are in danger of causing + // deadlock. Instead, return the task, and let the caller dispatch it + // for us. + MOZ_ASSERT(aReturnRunnable, + "aReturnRunnable must be provided on main thread"); + runnable.forget(aReturnRunnable); + } else { + NS_DispatchToMainThread(runnable); + ResetIsDispatchingToMainThread(); + } + } + } else { + // Update thread info without holding any lock. + aInfo->mWorking = aIsWorking; + } +} + +void +nsThreadManager::ResetIsDispatchingToMainThread() +{ + ReentrantMonitorAutoEnter mon(*mMonitor); + mDispatchingToMainThread = false; +} + +void +nsThreadManager::AddAllThreadsWereIdleListener(AllThreadsWereIdleListener *listener) +{ + MOZ_ASSERT(GetCurrentThreadStatusInfo()->mWorking); + mThreadsIdledListeners.AppendElement(listener); +} + +void +nsThreadManager::RemoveAllThreadsWereIdleListener(AllThreadsWereIdleListener *listener) +{ + mThreadsIdledListeners.RemoveElement(listener); +} + +#endif // MOZ_NUWA_PROCESS diff --git a/xpcom/threads/nsThreadManager.h b/xpcom/threads/nsThreadManager.h index 64d82cb9c..32ffda818 100644 --- a/xpcom/threads/nsThreadManager.h +++ b/xpcom/threads/nsThreadManager.h @@ -1,5 +1,5 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* -*- 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/. */ @@ -14,13 +14,31 @@ class nsIRunnable; +namespace mozilla { +class ReentrantMonitor; +} + class nsThreadManager : public nsIThreadManager { public: +#ifdef MOZ_NUWA_PROCESS + struct ThreadStatusInfo; + class AllThreadsWereIdleListener { + public: + NS_INLINE_DECL_REFCOUNTING(AllThreadsWereIdleListener); + virtual void OnAllThreadsWereIdle() = 0; + protected: + virtual ~AllThreadsWereIdleListener() + { + } + }; +#endif // MOZ_NUWA_PROCESS + NS_DECL_ISUPPORTS NS_DECL_NSITHREADMANAGER - static nsThreadManager *get() { + static nsThreadManager* get() + { static nsThreadManager sInstance; return &sInstance; } @@ -33,15 +51,15 @@ public: // Called by nsThread to inform the ThreadManager it exists. This method // must be called when the given thread is the current thread. - void RegisterCurrentThread(nsThread *thread); + void RegisterCurrentThread(nsThread* aThread); // Called by nsThread to inform the ThreadManager it is going away. This // method must be called when the given thread is the current thread. - void UnregisterCurrentThread(nsThread *thread); + void UnregisterCurrentThread(nsThread* aThread); // Returns the current thread. Returns null if OOM or if ThreadManager isn't // initialized. - nsThread *GetCurrentThread(); + nsThread* GetCurrentThread(); // Returns the maximal number of threads that have been in existence // simultaneously during the execution of the thread manager. @@ -49,31 +67,75 @@ public: // This needs to be public in order to support static instantiation of this // class with older compilers (e.g., egcs-2.91.66). - ~nsThreadManager() {} + ~nsThreadManager() + { + } + +#ifdef MOZ_NUWA_PROCESS + void SetIgnoreThreadStatus(); + + // |SetThreadWorking| and |SetThreadIdle| set status of thread that is + // currently running. They get thread status information from TLS and pass + // the information to |SetThreadIsWorking|. + void SetThreadIdle(nsIRunnable** aReturnRunnable); + void SetThreadWorking(); + + // |SetThreadIsWorking| is where is status actually changed. Thread status + // information is passed as a argument so caller must obtain the structure + // by itself. If this method is invoked on main thread, |aReturnRunnable| + // should be provided to receive the runnable of notifying listeners. + // |ResetIsDispatchingToMainThread| should be invoked after caller on main + // thread dispatched the task to main thread's queue. + void SetThreadIsWorking(ThreadStatusInfo* aInfo, + bool aIsWorking, + nsIRunnable** aReturnRunnable); + void ResetIsDispatchingToMainThread(); + + void AddAllThreadsWereIdleListener(AllThreadsWereIdleListener *listener); + void RemoveAllThreadsWereIdleListener(AllThreadsWereIdleListener *listener); + ThreadStatusInfo* GetCurrentThreadStatusInfo(); +#endif // MOZ_NUWA_PROCESS private: nsThreadManager() : mCurThreadIndex(0) , mMainPRThread(nullptr) - , mLock(nullptr) + , mLock("nsThreadManager.mLock") , mInitialized(false) , mCurrentNumberOfThreads(1) - , mHighestNumberOfThreads(1) { + , mHighestNumberOfThreads(1) +#ifdef MOZ_NUWA_PROCESS + , mMonitor(nullptr) + , mMainThreadStatusInfo(nullptr) + , mDispatchingToMainThread(nullptr) +#endif + { } nsRefPtrHashtable, nsThread> mThreadsByPRThread; - unsigned mCurThreadIndex; // thread-local-storage index + unsigned mCurThreadIndex; // thread-local-storage index nsRefPtr mMainThread; - PRThread *mMainPRThread; - // This is a pointer in order to allow creating nsThreadManager from - // the static context in debug builds. - nsAutoPtr mLock; // protects tables - bool mInitialized; - - // The current number of threads - uint32_t mCurrentNumberOfThreads; - // The highest number of threads encountered so far during the session - uint32_t mHighestNumberOfThreads; + PRThread* mMainPRThread; + mozilla::OffTheBooksMutex mLock; // protects tables + mozilla::Atomic mInitialized; + + // The current number of threads + uint32_t mCurrentNumberOfThreads; + // The highest number of threads encountered so far during the session + uint32_t mHighestNumberOfThreads; + +#ifdef MOZ_NUWA_PROCESS + static void DeleteThreadStatusInfo(void *aData); + unsigned mThreadStatusInfoIndex; + nsTArray> mThreadsIdledListeners; + nsTArray mThreadStatusInfos; + mozilla::UniquePtr mMonitor; + ThreadStatusInfo* mMainThreadStatusInfo; + // |mDispatchingToMainThread| is set when all thread are found to be idle + // before task of notifying all listeners are dispatched to main thread. + // The flag is protected by |mMonitor|. + bool mDispatchingToMainThread; +#endif // MOZ_NUWA_PROCESS }; #define NS_THREADMANAGER_CID \ diff --git a/xpcom/threads/nsThreadPool.cpp b/xpcom/threads/nsThreadPool.cpp index f2b42f21a..2d35fcb20 100644 --- a/xpcom/threads/nsThreadPool.cpp +++ b/xpcom/threads/nsThreadPool.cpp @@ -1,5 +1,5 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* -*- 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/. */ @@ -16,15 +16,19 @@ using namespace mozilla; #ifdef PR_LOGGING -static PRLogModuleInfo * +static PRLogModuleInfo* GetThreadPoolLog() { - static PRLogModuleInfo *sLog; - if (!sLog) + static PRLogModuleInfo* sLog; + if (!sLog) { sLog = PR_NewLogModule("nsThreadPool"); + } return sLog; } #endif +#ifdef LOG +#undef LOG +#endif #define LOG(args) PR_LOG(GetThreadPoolLog(), PR_LOG_DEBUG, args) // DESIGN: @@ -37,62 +41,74 @@ GetThreadPoolLog() #define DEFAULT_IDLE_THREAD_LIMIT 1 #define DEFAULT_IDLE_THREAD_TIMEOUT PR_SecondsToInterval(60) -NS_IMPL_THREADSAFE_ADDREF(nsThreadPool) -NS_IMPL_THREADSAFE_RELEASE(nsThreadPool) -NS_IMPL_CLASSINFO(nsThreadPool, NULL, nsIClassInfo::THREADSAFE, +NS_IMPL_ADDREF(nsThreadPool) +NS_IMPL_RELEASE(nsThreadPool) +NS_IMPL_CLASSINFO(nsThreadPool, nullptr, nsIClassInfo::THREADSAFE, NS_THREADPOOL_CID) -NS_IMPL_QUERY_INTERFACE3_CI(nsThreadPool, nsIThreadPool, nsIEventTarget, - nsIRunnable) -NS_IMPL_CI_INTERFACE_GETTER2(nsThreadPool, nsIThreadPool, nsIEventTarget) +NS_IMPL_QUERY_INTERFACE_CI(nsThreadPool, nsIThreadPool, nsIEventTarget, + nsIRunnable) +NS_IMPL_CI_INTERFACE_GETTER(nsThreadPool, nsIThreadPool, nsIEventTarget) nsThreadPool::nsThreadPool() : mThreadLimit(DEFAULT_THREAD_LIMIT) , mIdleThreadLimit(DEFAULT_IDLE_THREAD_LIMIT) , mIdleThreadTimeout(DEFAULT_IDLE_THREAD_TIMEOUT) , mIdleCount(0) + , mStackSize(nsIThreadManager::DEFAULT_STACK_SIZE) , mShutdown(false) { } nsThreadPool::~nsThreadPool() { - Shutdown(); + // Threads keep a reference to the nsThreadPool until they return from Run() + // after removing themselves from mThreads. + MOZ_ASSERT(mThreads.IsEmpty()); } nsresult -nsThreadPool::PutEvent(nsIRunnable *event) +nsThreadPool::PutEvent(nsIRunnable* aEvent) { // Avoid spawning a new thread while holding the event queue lock... - + bool spawnThread = false; + uint32_t stackSize = 0; { ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); + if (NS_WARN_IF(mShutdown)) { + return NS_ERROR_NOT_AVAILABLE; + } LOG(("THRD-P(%p) put [%d %d %d]\n", this, mIdleCount, mThreads.Count(), mThreadLimit)); - MOZ_ASSERT(mIdleCount <= (uint32_t) mThreads.Count(), "oops"); + MOZ_ASSERT(mIdleCount <= (uint32_t)mThreads.Count(), "oops"); // Make sure we have a thread to service this event. - if (mIdleCount == 0 && mThreads.Count() < (int32_t) mThreadLimit) + if (mIdleCount == 0 && mThreads.Count() < (int32_t)mThreadLimit) { spawnThread = true; + } - mEvents.PutEvent(event); + mEvents.PutEvent(aEvent); + stackSize = mStackSize; } LOG(("THRD-P(%p) put [spawn=%d]\n", this, spawnThread)); - if (!spawnThread) + if (!spawnThread) { return NS_OK; + } nsCOMPtr thread; nsThreadManager::get()->NewThread(0, - nsIThreadManager::DEFAULT_STACK_SIZE, + stackSize, getter_AddRefs(thread)); - NS_ENSURE_STATE(thread); + if (NS_WARN_IF(!thread)) { + return NS_ERROR_UNEXPECTED; + } bool killThread = false; { ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); - if (mThreads.Count() < (int32_t) mThreadLimit) { + if (mThreads.Count() < (int32_t)mThreadLimit) { mThreads.AppendObject(thread); } else { killThread = true; // okay, we don't need this thread anymore @@ -100,7 +116,15 @@ nsThreadPool::PutEvent(nsIRunnable *event) } LOG(("THRD-P(%p) put [%p kill=%d]\n", this, thread.get(), killThread)); if (killThread) { - thread->Shutdown(); + // Pending events are processed on the current thread during + // nsIThread::Shutdown() execution, so if nsThreadPool::Dispatch() is called + // under caller's lock then deadlock could occur. This happens e.g. in case + // of nsStreamCopier. To prevent this situation, dispatch a shutdown event + // to the current thread instead of calling nsIThread::Shutdown() directly. + + nsRefPtr r = NS_NewRunnableMethod(thread, + &nsIThread::Shutdown); + NS_DispatchToCurrentThread(r); } else { thread->Dispatch(this, NS_DISPATCH_NORMAL); } @@ -109,16 +133,16 @@ nsThreadPool::PutEvent(nsIRunnable *event) } void -nsThreadPool::ShutdownThread(nsIThread *thread) +nsThreadPool::ShutdownThread(nsIThread* aThread) { - LOG(("THRD-P(%p) shutdown async [%p]\n", this, thread)); + LOG(("THRD-P(%p) shutdown async [%p]\n", this, aThread)); - // This method is responsible for calling Shutdown on |thread|. This must be + // This method is responsible for calling Shutdown on |aThread|. This must be // done from some other thread, so we use the main thread of the application. MOZ_ASSERT(!NS_IsMainThread(), "wrong thread"); - nsRefPtr r = NS_NewRunnableMethod(thread, &nsIThread::Shutdown); + nsRefPtr r = NS_NewRunnableMethod(aThread, &nsIThread::Shutdown); NS_DispatchToMainThread(r); } @@ -126,7 +150,6 @@ NS_IMETHODIMP nsThreadPool::Run() { LOG(("THRD-P(%p) enter\n", this)); - mThreadNaming.SetThreadPoolName(mName); nsCOMPtr current; @@ -161,8 +184,9 @@ nsThreadPool::Run() } else { if (wasIdle) { // if too many idle threads or idle for too long, then bail. - if (mIdleCount > mIdleThreadLimit || (now - idleSince) >= timeout) + if (mIdleCount > mIdleThreadLimit || (now - idleSince) >= timeout) { exitThread = true; + } } else { // if would be too many idle threads... if (mIdleCount == mIdleThreadLimit) { @@ -176,12 +200,16 @@ nsThreadPool::Run() } if (exitThread) { - if (wasIdle) + if (wasIdle) { --mIdleCount; + } shutdownThreadOnExit = mThreads.RemoveObject(current); } else { PRIntervalTime delta = timeout - (now - idleSince); LOG(("THRD-P(%p) waiting [%d]\n", this, delta)); +#ifdef MOZ_NUWA_PROCESS + nsThreadManager::get()->SetThreadIdle(nullptr); +#endif // MOZ_NUWA_PROCESS mon.Wait(delta); } } else if (wasIdle) { @@ -191,6 +219,9 @@ nsThreadPool::Run() } if (event) { LOG(("THRD-P(%p) running [%p]\n", this, event.get())); +#ifdef MOZ_NUWA_PROCESS + nsThreadManager::get()->SetThreadWorking(); +#endif // MOZ_NUWA_PROCESS event->Run(); } } while (!exitThread); @@ -208,38 +239,51 @@ nsThreadPool::Run() } NS_IMETHODIMP -nsThreadPool::Dispatch(nsIRunnable *event, uint32_t flags) +nsThreadPool::Dispatch(nsIRunnable* aEvent, uint32_t aFlags) { - LOG(("THRD-P(%p) dispatch [%p %x]\n", this, event, flags)); + LOG(("THRD-P(%p) dispatch [%p %x]\n", this, aEvent, aFlags)); - NS_ENSURE_STATE(!mShutdown); + if (NS_WARN_IF(mShutdown)) { + return NS_ERROR_NOT_AVAILABLE; + } - if (flags & DISPATCH_SYNC) { + if (aFlags & DISPATCH_SYNC) { nsCOMPtr thread; nsThreadManager::get()->GetCurrentThread(getter_AddRefs(thread)); - NS_ENSURE_STATE(thread); + if (NS_WARN_IF(!thread)) { + return NS_ERROR_NOT_AVAILABLE; + } nsRefPtr wrapper = - new nsThreadSyncDispatch(thread, event); + new nsThreadSyncDispatch(thread, aEvent); PutEvent(wrapper); - while (wrapper->IsPending()) + while (wrapper->IsPending()) { NS_ProcessNextEvent(thread); + } } else { - NS_ASSERTION(flags == NS_DISPATCH_NORMAL, "unexpected dispatch flags"); - PutEvent(event); + NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL, "unexpected dispatch flags"); + PutEvent(aEvent); } return NS_OK; } NS_IMETHODIMP -nsThreadPool::IsOnCurrentThread(bool *result) +nsThreadPool::IsOnCurrentThread(bool* aResult) { - // No one should be calling this method. If this assertion gets hit, then we - // need to think carefully about what this method should be returning. - NS_NOTREACHED("implement me"); + ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); + if (NS_WARN_IF(mShutdown)) { + return NS_ERROR_NOT_AVAILABLE; + } - *result = false; + nsIThread* thread = NS_GetCurrentThread(); + for (uint32_t i = 0; i < static_cast(mThreads.Count()); ++i) { + if (mThreads[i] == thread) { + *aResult = true; + return NS_OK; + } + } + *aResult = false; return NS_OK; } @@ -265,26 +309,28 @@ nsThreadPool::Shutdown() // It's important that we shutdown the threads while outside the event queue // monitor. Otherwise, we could end up dead-locking. - for (int32_t i = 0; i < threads.Count(); ++i) + for (int32_t i = 0; i < threads.Count(); ++i) { threads[i]->Shutdown(); + } return NS_OK; } NS_IMETHODIMP -nsThreadPool::GetThreadLimit(uint32_t *value) +nsThreadPool::GetThreadLimit(uint32_t* aValue) { - *value = mThreadLimit; + *aValue = mThreadLimit; return NS_OK; } NS_IMETHODIMP -nsThreadPool::SetThreadLimit(uint32_t value) +nsThreadPool::SetThreadLimit(uint32_t aValue) { ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); - mThreadLimit = value; - if (mIdleThreadLimit > mThreadLimit) + mThreadLimit = aValue; + if (mIdleThreadLimit > mThreadLimit) { mIdleThreadLimit = mThreadLimit; + } if (static_cast(mThreads.Count()) > mThreadLimit) { mon.NotifyAll(); // wake up threads so they observe this change @@ -293,19 +339,20 @@ nsThreadPool::SetThreadLimit(uint32_t value) } NS_IMETHODIMP -nsThreadPool::GetIdleThreadLimit(uint32_t *value) +nsThreadPool::GetIdleThreadLimit(uint32_t* aValue) { - *value = mIdleThreadLimit; + *aValue = mIdleThreadLimit; return NS_OK; } NS_IMETHODIMP -nsThreadPool::SetIdleThreadLimit(uint32_t value) +nsThreadPool::SetIdleThreadLimit(uint32_t aValue) { ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); - mIdleThreadLimit = value; - if (mIdleThreadLimit > mThreadLimit) + mIdleThreadLimit = aValue; + if (mIdleThreadLimit > mThreadLimit) { mIdleThreadLimit = mThreadLimit; + } // Do we need to kill some idle threads? if (mIdleCount > mIdleThreadLimit) { @@ -315,18 +362,18 @@ nsThreadPool::SetIdleThreadLimit(uint32_t value) } NS_IMETHODIMP -nsThreadPool::GetIdleThreadTimeout(uint32_t *value) +nsThreadPool::GetIdleThreadTimeout(uint32_t* aValue) { - *value = mIdleThreadTimeout; + *aValue = mIdleThreadTimeout; return NS_OK; } NS_IMETHODIMP -nsThreadPool::SetIdleThreadTimeout(uint32_t value) +nsThreadPool::SetIdleThreadTimeout(uint32_t aValue) { ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); uint32_t oldTimeout = mIdleThreadTimeout; - mIdleThreadTimeout = value; + mIdleThreadTimeout = aValue; // Do we need to notify any idle threads that their sleep time has shortened? if (mIdleThreadTimeout < oldTimeout && mIdleCount > 0) { @@ -335,6 +382,22 @@ nsThreadPool::SetIdleThreadTimeout(uint32_t value) return NS_OK; } +NS_IMETHODIMP +nsThreadPool::GetThreadStackSize(uint32_t* aValue) +{ + ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); + *aValue = mStackSize; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::SetThreadStackSize(uint32_t aValue) +{ + ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); + mStackSize = aValue; + return NS_OK; +} + NS_IMETHODIMP nsThreadPool::GetListener(nsIThreadPoolListener** aListener) { @@ -359,8 +422,9 @@ nsThreadPool::SetName(const nsACString& aName) { { ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); - if (mThreads.Count()) + if (mThreads.Count()) { return NS_ERROR_NOT_AVAILABLE; + } } mName = aName; diff --git a/xpcom/threads/nsThreadPool.h b/xpcom/threads/nsThreadPool.h index 729b47697..8a868ba2e 100644 --- a/xpcom/threads/nsThreadPool.h +++ b/xpcom/threads/nsThreadPool.h @@ -1,5 +1,5 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* -*- 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/. */ @@ -16,11 +16,12 @@ #include "nsThreadUtils.h" #include "mozilla/Attributes.h" -class nsThreadPool MOZ_FINAL : public nsIThreadPool, - public nsIRunnable +class nsThreadPool final + : public nsIThreadPool + , public nsIRunnable { public: - NS_DECL_ISUPPORTS + NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIEVENTTARGET NS_DECL_NSITHREADPOOL NS_DECL_NSIRUNNABLE @@ -30,8 +31,8 @@ public: private: ~nsThreadPool(); - void ShutdownThread(nsIThread *thread); - nsresult PutEvent(nsIRunnable *event); + void ShutdownThread(nsIThread* aThread); + nsresult PutEvent(nsIRunnable* aEvent); nsCOMArray mThreads; nsEventQueue mEvents; @@ -39,6 +40,7 @@ private: uint32_t mIdleThreadLimit; uint32_t mIdleThreadTimeout; uint32_t mIdleCount; + uint32_t mStackSize; nsCOMPtr mListener; bool mShutdown; nsCString mName; diff --git a/xpcom/threads/nsTimerImpl.cpp b/xpcom/threads/nsTimerImpl.cpp index 5a624794d..9fa8987e0 100644 --- a/xpcom/threads/nsTimerImpl.cpp +++ b/xpcom/threads/nsTimerImpl.cpp @@ -10,12 +10,18 @@ #include "nsThreadManager.h" #include "nsThreadUtils.h" #include "plarena.h" +#include "pratom.h" #include "GoannaProfiler.h" +#include "mozilla/Atomics.h" +#ifdef MOZ_NUWA_PROCESS +#include "ipc/Nuwa.h" +#endif +using mozilla::Atomic; using mozilla::TimeDuration; using mozilla::TimeStamp; -static int32_t gGenerator = 0; +static Atomic gGenerator; static TimerThread* gThread = nullptr; #ifdef DEBUG_TIMERS @@ -23,9 +29,10 @@ static TimerThread* gThread = nullptr; PRLogModuleInfo* GetTimerLog() { - static PRLogModuleInfo *sLog; - if (!sLog) + static PRLogModuleInfo* sLog; + if (!sLog) { sLog = PR_NewLogModule("nsTimerImpl"); + } return sLog; } @@ -37,16 +44,17 @@ double nsTimerImpl::sDeltaNum = 0; static void myNS_MeanAndStdDev(double n, double sumOfValues, double sumOfSquaredValues, - double *meanResult, double *stdDevResult) + double* meanResult, double* stdDevResult) { double mean = 0.0, var = 0.0, stdDev = 0.0; if (n > 0.0 && sumOfValues >= 0) { mean = sumOfValues / n; double temp = (n * sumOfSquaredValues) - (sumOfValues * sumOfValues); - if (temp < 0.0 || n <= 1) + if (temp < 0.0 || n <= 1) { var = 0.0; - else + } else { var = temp / (n * (n - 1)); + } // for some reason, Windows says sqrt(0.0) is "-1.#J" (?!) so do this: stdDev = var != 0.0 ? sqrt(var) : 0.0; } @@ -74,7 +82,8 @@ namespace { class TimerEventAllocator { private: - struct FreeEntry { + struct FreeEntry + { FreeEntry* mNext; }; @@ -84,8 +93,8 @@ private: public: TimerEventAllocator() - : mFirstFree(nullptr), - mMonitor("TimerEventAllocator") + : mFirstFree(nullptr) + , mMonitor("TimerEventAllocator") { PL_InitArenaPool(&mPool, "TimerEventPool", 4096, /* align = */ 0); } @@ -101,7 +110,8 @@ public: } // anonymous namespace -class nsTimerEvent : public nsRunnable { +class nsTimerEvent : public nsRunnable +{ public: NS_IMETHOD Run(); @@ -114,7 +124,7 @@ public: MOZ_ASSERT(gThread->IsOnTimerThread(), "nsTimer must always be allocated on the timer thread"); - PR_ATOMIC_INCREMENT(&sAllocatorUsers); + sAllocatorUsers++; } #ifdef DEBUG_TIMERS @@ -125,11 +135,13 @@ public: static void Shutdown(); static void DeleteAllocatorIfNeeded(); - static void* operator new(size_t size) CPP_THROW_NEW { - return sAllocator->Alloc(size); + static void* operator new(size_t aSize) CPP_THROW_NEW + { + return sAllocator->Alloc(aSize); } - void operator delete(void* p) { - sAllocator->Free(p); + void operator delete(void* aPtr) + { + sAllocator->Free(aPtr); DeleteAllocatorIfNeeded(); } @@ -145,29 +157,31 @@ public: } private: - ~nsTimerEvent() { + ~nsTimerEvent() + { MOZ_COUNT_DTOR(nsTimerEvent); MOZ_ASSERT(!sCanDeleteAllocator || sAllocatorUsers > 0, "This will result in us attempting to deallocate the nsTimerEvent allocator twice"); - PR_ATOMIC_DECREMENT(&sAllocatorUsers); + sAllocatorUsers--; } nsRefPtr mTimer; int32_t mGeneration; static TimerEventAllocator* sAllocator; - static int32_t sAllocatorUsers; + static Atomic sAllocatorUsers; static bool sCanDeleteAllocator; }; TimerEventAllocator* nsTimerEvent::sAllocator = nullptr; -int32_t nsTimerEvent::sAllocatorUsers = 0; +Atomic nsTimerEvent::sAllocatorUsers; bool nsTimerEvent::sCanDeleteAllocator = false; namespace { -void* TimerEventAllocator::Alloc(size_t aSize) +void* +TimerEventAllocator::Alloc(size_t aSize) { MOZ_ASSERT(aSize == sizeof(nsTimerEvent)); @@ -177,17 +191,18 @@ void* TimerEventAllocator::Alloc(size_t aSize) if (mFirstFree) { p = mFirstFree; mFirstFree = mFirstFree->mNext; - } - else { + } else { PL_ARENA_ALLOCATE(p, &mPool, aSize); - if (!p) + if (!p) { return nullptr; + } } return p; } -void TimerEventAllocator::Free(void* aPtr) +void +TimerEventAllocator::Free(void* aPtr) { mozilla::MonitorAutoLock lock(mMonitor); @@ -199,15 +214,16 @@ void TimerEventAllocator::Free(void* aPtr) } // anonymous namespace -NS_IMPL_THREADSAFE_QUERY_INTERFACE1(nsTimerImpl, nsITimer) -NS_IMPL_THREADSAFE_ADDREF(nsTimerImpl) +NS_IMPL_QUERY_INTERFACE(nsTimerImpl, nsITimer) +NS_IMPL_ADDREF(nsTimerImpl) -NS_IMETHODIMP_(nsrefcnt) nsTimerImpl::Release(void) +NS_IMETHODIMP_(MozExternalRefCountType) +nsTimerImpl::Release(void) { nsrefcnt count; MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); - count = NS_AtomicDecrementRefcnt(mRefCnt); + count = --mRefCnt; NS_LOG_RELEASE(this, count, "nsTimerImpl"); if (count == 0) { mRefCnt = 1; /* stabilize */ @@ -251,8 +267,9 @@ NS_IMETHODIMP_(nsrefcnt) nsTimerImpl::Release(void) mCanceled = true; MOZ_ASSERT(gThread, "Armed timer exists after the thread timer stopped."); - if (NS_SUCCEEDED(gThread->RemoveTimer(this))) + if (NS_SUCCEEDED(gThread->RemoveTimer(this))) { return 0; + } } return count; @@ -287,7 +304,9 @@ nsTimerImpl::Startup() nsTimerEvent::Init(); gThread = new TimerThread(); - if (!gThread) return NS_ERROR_OUT_OF_MEMORY; + if (!gThread) { + return NS_ERROR_OUT_OF_MEMORY; + } NS_ADDREF(gThread); rv = gThread->InitLocks(); @@ -299,20 +318,25 @@ nsTimerImpl::Startup() return rv; } -void nsTimerImpl::Shutdown() +void +nsTimerImpl::Shutdown() { #ifdef DEBUG_TIMERS if (PR_LOG_TEST(GetTimerLog(), PR_LOG_DEBUG)) { double mean = 0, stddev = 0; myNS_MeanAndStdDev(sDeltaNum, sDeltaSum, sDeltaSumSquared, &mean, &stddev); - PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("sDeltaNum = %f, sDeltaSum = %f, sDeltaSumSquared = %f\n", sDeltaNum, sDeltaSum, sDeltaSumSquared)); - PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("mean: %fms, stddev: %fms\n", mean, stddev)); + PR_LOG(GetTimerLog(), PR_LOG_DEBUG, + ("sDeltaNum = %f, sDeltaSum = %f, sDeltaSumSquared = %f\n", + sDeltaNum, sDeltaSum, sDeltaSumSquared)); + PR_LOG(GetTimerLog(), PR_LOG_DEBUG, + ("mean: %fms, stddev: %fms\n", mean, stddev)); } #endif - if (!gThread) + if (!gThread) { return; + } gThread->Shutdown(); NS_RELEASE(gThread); @@ -321,18 +345,23 @@ void nsTimerImpl::Shutdown() } -nsresult nsTimerImpl::InitCommon(uint32_t aType, uint32_t aDelay) +nsresult +nsTimerImpl::InitCommon(uint32_t aType, uint32_t aDelay) { nsresult rv; - NS_ENSURE_TRUE(gThread, NS_ERROR_NOT_INITIALIZED); + if (NS_WARN_IF(!gThread)) { + return NS_ERROR_NOT_INITIALIZED; + } if (!mEventTarget) { NS_ERROR("mEventTarget is NULL"); return NS_ERROR_NOT_INITIALIZED; } rv = gThread->Init(); - NS_ENSURE_SUCCESS(rv, rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } /** * In case of re-Init, both with and without a preceding Cancel, clear the @@ -348,11 +377,12 @@ nsresult nsTimerImpl::InitCommon(uint32_t aType, uint32_t aDelay) * be cleared by another CPU whose store hasn't reached our CPU's cache), * because RemoveTimer is idempotent. */ - if (mArmed) + if (mArmed) { gThread->RemoveTimer(this); + } mCanceled = false; mTimeout = TimeStamp(); - mGeneration = PR_ATOMIC_INCREMENT(&gGenerator); + mGeneration = gGenerator++; mType = (uint8_t)aType; SetDelayInternal(aDelay); @@ -360,13 +390,16 @@ nsresult nsTimerImpl::InitCommon(uint32_t aType, uint32_t aDelay) return gThread->AddTimer(this); } -NS_IMETHODIMP nsTimerImpl::InitWithFuncCallback(nsTimerCallbackFunc aFunc, - void *aClosure, - uint32_t aDelay, - uint32_t aType) +NS_IMETHODIMP +nsTimerImpl::InitWithFuncCallback(nsTimerCallbackFunc aFunc, + void* aClosure, + uint32_t aDelay, + uint32_t aType) { - NS_ENSURE_ARG_POINTER(aFunc); - + if (NS_WARN_IF(!aFunc)) { + return NS_ERROR_INVALID_ARG; + } + ReleaseCallback(); mCallbackType = CALLBACK_TYPE_FUNC; mCallback.c = aFunc; @@ -375,11 +408,14 @@ NS_IMETHODIMP nsTimerImpl::InitWithFuncCallback(nsTimerCallbackFunc aFunc, return InitCommon(aType, aDelay); } -NS_IMETHODIMP nsTimerImpl::InitWithCallback(nsITimerCallback *aCallback, - uint32_t aDelay, - uint32_t aType) +NS_IMETHODIMP +nsTimerImpl::InitWithCallback(nsITimerCallback* aCallback, + uint32_t aDelay, + uint32_t aType) { - NS_ENSURE_ARG_POINTER(aCallback); + if (NS_WARN_IF(!aCallback)) { + return NS_ERROR_INVALID_ARG; + } ReleaseCallback(); mCallbackType = CALLBACK_TYPE_INTERFACE; @@ -389,11 +425,12 @@ NS_IMETHODIMP nsTimerImpl::InitWithCallback(nsITimerCallback *aCallback, return InitCommon(aType, aDelay); } -NS_IMETHODIMP nsTimerImpl::Init(nsIObserver *aObserver, - uint32_t aDelay, - uint32_t aType) +NS_IMETHODIMP +nsTimerImpl::Init(nsIObserver* aObserver, uint32_t aDelay, uint32_t aType) { - NS_ENSURE_ARG_POINTER(aObserver); + if (NS_WARN_IF(!aObserver)) { + return NS_ERROR_INVALID_ARG; + } ReleaseCallback(); mCallbackType = CALLBACK_TYPE_OBSERVER; @@ -403,19 +440,22 @@ NS_IMETHODIMP nsTimerImpl::Init(nsIObserver *aObserver, return InitCommon(aType, aDelay); } -NS_IMETHODIMP nsTimerImpl::Cancel() +NS_IMETHODIMP +nsTimerImpl::Cancel() { mCanceled = true; - if (gThread) + if (gThread) { gThread->RemoveTimer(this); + } ReleaseCallback(); return NS_OK; } -NS_IMETHODIMP nsTimerImpl::SetDelay(uint32_t aDelay) +NS_IMETHODIMP +nsTimerImpl::SetDelay(uint32_t aDelay) { if (mCallbackType == CALLBACK_TYPE_UNKNOWN && mType == TYPE_ONE_SHOT) { // This may happen if someone tries to re-use a one-shot timer @@ -425,21 +465,30 @@ NS_IMETHODIMP nsTimerImpl::SetDelay(uint32_t aDelay) return NS_ERROR_NOT_INITIALIZED; } + // If we're already repeating precisely, update mTimeout now so that the + // new delay takes effect in the future. + if (!mTimeout.IsNull() && mType == TYPE_REPEATING_PRECISE) { + mTimeout = TimeStamp::Now(); + } + SetDelayInternal(aDelay); - if (!mFiring && gThread) + if (!mFiring && gThread) { gThread->TimerDelayChanged(this); + } return NS_OK; } -NS_IMETHODIMP nsTimerImpl::GetDelay(uint32_t* aDelay) +NS_IMETHODIMP +nsTimerImpl::GetDelay(uint32_t* aDelay) { *aDelay = mDelay; return NS_OK; } -NS_IMETHODIMP nsTimerImpl::SetType(uint32_t aType) +NS_IMETHODIMP +nsTimerImpl::SetType(uint32_t aType) { mType = (uint8_t)aType; // XXX if this is called, we should change the actual type.. this could effect @@ -448,62 +497,87 @@ NS_IMETHODIMP nsTimerImpl::SetType(uint32_t aType) return NS_OK; } -NS_IMETHODIMP nsTimerImpl::GetType(uint32_t* aType) +NS_IMETHODIMP +nsTimerImpl::GetType(uint32_t* aType) { *aType = mType; return NS_OK; } -NS_IMETHODIMP nsTimerImpl::GetClosure(void** aClosure) +NS_IMETHODIMP +nsTimerImpl::GetClosure(void** aClosure) { *aClosure = mClosure; return NS_OK; } -NS_IMETHODIMP nsTimerImpl::GetCallback(nsITimerCallback **aCallback) +NS_IMETHODIMP +nsTimerImpl::GetCallback(nsITimerCallback** aCallback) { - if (mCallbackType == CALLBACK_TYPE_INTERFACE) + if (mCallbackType == CALLBACK_TYPE_INTERFACE) { NS_IF_ADDREF(*aCallback = mCallback.i); - else if (mTimerCallbackWhileFiring) + } else if (mTimerCallbackWhileFiring) { NS_ADDREF(*aCallback = mTimerCallbackWhileFiring); - else + } else { *aCallback = nullptr; + } return NS_OK; } -NS_IMETHODIMP nsTimerImpl::GetTarget(nsIEventTarget** aTarget) +NS_IMETHODIMP +nsTimerImpl::GetTarget(nsIEventTarget** aTarget) { NS_IF_ADDREF(*aTarget = mEventTarget); return NS_OK; } -NS_IMETHODIMP nsTimerImpl::SetTarget(nsIEventTarget* aTarget) +NS_IMETHODIMP +nsTimerImpl::SetTarget(nsIEventTarget* aTarget) { - NS_ENSURE_TRUE(mCallbackType == CALLBACK_TYPE_UNKNOWN, - NS_ERROR_ALREADY_INITIALIZED); + if (NS_WARN_IF(mCallbackType != CALLBACK_TYPE_UNKNOWN)) { + return NS_ERROR_ALREADY_INITIALIZED; + } - if (aTarget) + if (aTarget) { mEventTarget = aTarget; - else + } else { mEventTarget = static_cast(NS_GetCurrentThread()); + } return NS_OK; } -void nsTimerImpl::Fire() +void +nsTimerImpl::Fire() { - if (mCanceled) + if (mCanceled) { return; + } - PROFILER_LABEL("Timer", "Fire"); +#ifdef MOZ_NUWA_PROCESS + if (IsNuwaProcess() && IsNuwaReady()) { + // A timer event fired after Nuwa frozen can freeze main thread. + return; + } +#endif + + PROFILER_LABEL("Timer", "Fire", + js::ProfileEntry::Category::OTHER); + +#ifdef MOZ_TASK_TRACER + // mTracedTask is an instance of FakeTracedTask created by + // DispatchTracedTask(). AutoRunFakeTracedTask logs the begin/end time of the + // timer/FakeTracedTask instance in ctor/dtor. + mozilla::tasktracer::AutoRunFakeTracedTask runTracedTask(mTracedTask); +#endif - TimeStamp now = TimeStamp::Now(); #ifdef DEBUG_TIMERS + TimeStamp now = TimeStamp::Now(); if (PR_LOG_TEST(GetTimerLog(), PR_LOG_DEBUG)) { TimeDuration a = now - mStart; // actual delay in intervals TimeDuration b = TimeDuration::FromMilliseconds(mDelay); // expected delay in intervals @@ -513,10 +587,16 @@ void nsTimerImpl::Fire() sDeltaSumSquared += double(d) * double(d); sDeltaNum++; - PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("[this=%p] expected delay time %4ums\n", this, mDelay)); - PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("[this=%p] actual delay time %fms\n", this, a.ToMilliseconds())); - PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("[this=%p] (mType is %d) -------\n", this, mType)); - PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("[this=%p] delta %4dms\n", this, (a > b) ? (int32_t)d : -(int32_t)d)); + PR_LOG(GetTimerLog(), PR_LOG_DEBUG, + ("[this=%p] expected delay time %4ums\n", this, mDelay)); + PR_LOG(GetTimerLog(), PR_LOG_DEBUG, + ("[this=%p] actual delay time %fms\n", this, + a.ToMilliseconds())); + PR_LOG(GetTimerLog(), PR_LOG_DEBUG, + ("[this=%p] (mType is %d) -------\n", this, mType)); + PR_LOG(GetTimerLog(), PR_LOG_DEBUG, + ("[this=%p] delta %4dms\n", + this, (a > b) ? (int32_t)d : -(int32_t)d)); mStart = mStart2; mStart2 = TimeStamp(); @@ -530,18 +610,20 @@ void nsTimerImpl::Fire() timeout -= TimeDuration::FromMilliseconds(mDelay); } - if (mCallbackType == CALLBACK_TYPE_INTERFACE) + if (mCallbackType == CALLBACK_TYPE_INTERFACE) { mTimerCallbackWhileFiring = mCallback.i; + } mFiring = true; - + // Handle callbacks that re-init the timer, but avoid leaking. // See bug 330128. CallbackUnion callback = mCallback; unsigned callbackType = mCallbackType; - if (callbackType == CALLBACK_TYPE_INTERFACE) + if (callbackType == CALLBACK_TYPE_INTERFACE) { NS_ADDREF(callback.i); - else if (callbackType == CALLBACK_TYPE_OBSERVER) + } else if (callbackType == CALLBACK_TYPE_OBSERVER) { NS_ADDREF(callback.o); + } ReleaseCallback(); switch (callbackType) { @@ -556,7 +638,8 @@ void nsTimerImpl::Fire() NS_TIMER_CALLBACK_TOPIC, nullptr); break; - default:; + default: + ; } // If the callback didn't re-init the timer, and it's not a one-shot timer, @@ -567,10 +650,11 @@ void nsTimerImpl::Fire() mCallbackType = callbackType; } else { // The timer was a one-shot, or the callback was reinitialized. - if (callbackType == CALLBACK_TYPE_INTERFACE) + if (callbackType == CALLBACK_TYPE_INTERFACE) { NS_RELEASE(callback.i); - else if (callbackType == CALLBACK_TYPE_OBSERVER) + } else if (callbackType == CALLBACK_TYPE_OBSERVER) { NS_RELEASE(callback.o); + } } mFiring = false; @@ -584,30 +668,36 @@ void nsTimerImpl::Fire() } #endif - // Reschedule repeating timers, but make sure that we aren't armed already - // (which can happen if the callback reinitialized the timer). - if (IsRepeating() && !mArmed) { - if (mType == TYPE_REPEATING_SLACK) - SetDelayInternal(mDelay); // force mTimeout to be recomputed. For - // REPEATING_PRECISE_CAN_SKIP timers this has - // already happened. - if (gThread) + // Reschedule repeating timers, except REPEATING_PRECISE which already did + // that in PostTimerEvent, but make sure that we aren't armed already (which + // can happen if the callback reinitialized the timer). + if (IsRepeating() && mType != TYPE_REPEATING_PRECISE && !mArmed) { + if (mType == TYPE_REPEATING_SLACK) { + SetDelayInternal(mDelay); // force mTimeout to be recomputed. For + } + // REPEATING_PRECISE_CAN_SKIP timers this has + // already happened. + if (gThread) { gThread->AddTimer(this); + } } } -void nsTimerEvent::Init() +void +nsTimerEvent::Init() { sAllocator = new TimerEventAllocator(); } -void nsTimerEvent::Shutdown() +void +nsTimerEvent::Shutdown() { sCanDeleteAllocator = true; DeleteAllocatorIfNeeded(); } -void nsTimerEvent::DeleteAllocatorIfNeeded() +void +nsTimerEvent::DeleteAllocatorIfNeeded() { if (sCanDeleteAllocator && sAllocatorUsers == 0) { delete sAllocator; @@ -615,10 +705,12 @@ void nsTimerEvent::DeleteAllocatorIfNeeded() } } -NS_IMETHODIMP nsTimerEvent::Run() +NS_IMETHODIMP +nsTimerEvent::Run() { - if (mGeneration != mTimer->GetGeneration()) + if (mGeneration != mTimer->GetGeneration()) { return NS_OK; + } #ifdef DEBUG_TIMERS if (PR_LOG_TEST(GetTimerLog(), PR_LOG_DEBUG)) { @@ -639,7 +731,8 @@ NS_IMETHODIMP nsTimerEvent::Run() return NS_OK; } -already_AddRefed nsTimerImpl::PostTimerEvent(already_AddRefed aTimerRef) +already_AddRefed +nsTimerImpl::PostTimerEvent(already_AddRefed aTimerRef) { nsRefPtr timer(aTimerRef); if (!timer->mEventTarget) { @@ -658,8 +751,9 @@ already_AddRefed nsTimerImpl::PostTimerEvent(already_AddRefed event = new nsTimerEvent; - if (!event) + if (!event) { return timer.forget(); + } #ifdef DEBUG_TIMERS if (PR_LOG_TEST(GetTimerLog(), PR_LOG_DEBUG)) { @@ -668,9 +762,17 @@ already_AddRefed nsTimerImpl::PostTimerEvent(already_AddRefedIsRepeatingPrecisely()) { timer->SetDelayInternal(timer->mDelay); + + // But only re-arm REPEATING_PRECISE timers. + if (gThread && timer->mType == TYPE_REPEATING_PRECISE) { + nsresult rv = gThread->AddTimer(timer); + if (NS_FAILED(rv)) { + return timer.forget(); + } + } } nsIEventTarget* target = timer->mEventTarget; @@ -679,52 +781,64 @@ already_AddRefed nsTimerImpl::PostTimerEvent(already_AddRefedDispatch(event, NS_DISPATCH_NORMAL); if (NS_FAILED(rv)) { timer = event->ForgetTimer(); - if (gThread) + if (gThread) { gThread->RemoveTimer(timer); } return timer.forget(); + } return nullptr; } -void nsTimerImpl::SetDelayInternal(uint32_t aDelay) +void +nsTimerImpl::SetDelayInternal(uint32_t aDelay) { TimeDuration delayInterval = TimeDuration::FromMilliseconds(aDelay); mDelay = aDelay; TimeStamp now = TimeStamp::Now(); - mTimeout = now; + if (mTimeout.IsNull() || mType != TYPE_REPEATING_PRECISE) { + mTimeout = now; + } mTimeout += delayInterval; #ifdef DEBUG_TIMERS if (PR_LOG_TEST(GetTimerLog(), PR_LOG_DEBUG)) { - if (mStart.IsNull()) + if (mStart.IsNull()) { mStart = now; - else + } else { mStart2 = now; + } } #endif } +size_t +nsTimerImpl::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const +{ + return aMallocSizeOf(this); +} + // NOT FOR PUBLIC CONSUMPTION! nsresult -NS_NewTimer(nsITimer* *aResult, nsTimerCallbackFunc aCallback, void *aClosure, +NS_NewTimer(nsITimer** aResult, nsTimerCallbackFunc aCallback, void* aClosure, uint32_t aDelay, uint32_t aType) { - nsTimerImpl* timer = new nsTimerImpl(); - if (timer == nullptr) - return NS_ERROR_OUT_OF_MEMORY; - NS_ADDREF(timer); + nsTimerImpl* timer = new nsTimerImpl(); + if (!timer) { + return NS_ERROR_OUT_OF_MEMORY; + } + NS_ADDREF(timer); - nsresult rv = timer->InitWithFuncCallback(aCallback, aClosure, - aDelay, aType); - if (NS_FAILED(rv)) { - NS_RELEASE(timer); - return rv; - } + nsresult rv = timer->InitWithFuncCallback(aCallback, aClosure, + aDelay, aType); + if (NS_FAILED(rv)) { + NS_RELEASE(timer); + return rv; + } - *aResult = timer; - return NS_OK; + *aResult = timer; + return NS_OK; } diff --git a/xpcom/threads/nsTimerImpl.h b/xpcom/threads/nsTimerImpl.h index 4a2406056..27ebf8ff3 100644 --- a/xpcom/threads/nsTimerImpl.h +++ b/xpcom/threads/nsTimerImpl.h @@ -1,13 +1,12 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- - * This Source Code Form is subject to the terms of the Mozilla Public +/* -*- 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/. */ #ifndef nsTimerImpl_h___ #define nsTimerImpl_h___ -//#define FORCE_PR_LOG /* Allow logging in the release build */ - #include "nsITimer.h" #include "nsIEventTarget.h" #include "nsIObserver.h" @@ -18,8 +17,12 @@ #include "mozilla/TimeStamp.h" #include "mozilla/Attributes.h" +#ifdef MOZ_TASK_TRACER +#include "TracedTaskCommon.h" +#endif + #if defined(PR_LOGGING) -extern PRLogModuleInfo *GetTimerLog(); +extern PRLogModuleInfo* GetTimerLog(); #define DEBUG_TIMERS 1 #else #undef DEBUG_TIMERS @@ -33,22 +36,23 @@ extern PRLogModuleInfo *GetTimerLog(); {0x84, 0x27, 0xfb, 0xab, 0x44, 0xf2, 0x9b, 0xc8} \ } -enum { +enum +{ CALLBACK_TYPE_UNKNOWN = 0, CALLBACK_TYPE_INTERFACE = 1, CALLBACK_TYPE_FUNC = 2, CALLBACK_TYPE_OBSERVER = 3 }; -class nsTimerImpl MOZ_FINAL : public nsITimer +class nsTimerImpl final : public nsITimer { public: typedef mozilla::TimeStamp TimeStamp; nsTimerImpl(); - static NS_HIDDEN_(nsresult) Startup(); - static NS_HIDDEN_(void) Shutdown(); + static nsresult Startup(); + static void Shutdown(); friend class TimerThread; friend struct TimerAdditionComparator; @@ -56,13 +60,25 @@ public: void Fire(); // If a failure is encountered, the reference is returned to the caller static already_AddRefed PostTimerEvent( - already_AddRefed aTimerRef); + already_AddRefed aTimerRef); void SetDelayInternal(uint32_t aDelay); - NS_DECL_ISUPPORTS + NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSITIMER - int32_t GetGeneration() { return mGeneration; } + int32_t GetGeneration() + { + return mGeneration; + } + +#ifdef MOZ_TASK_TRACER + void DispatchTracedTask() + { + mTracedTask = mozilla::tasktracer::CreateFakeTracedTask(*(int**)(this)); + } +#endif + + virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override; private: ~nsTimerImpl(); @@ -74,33 +90,37 @@ private: // sure that we don't recurse into ReleaseCallback in case // the callback's destructor calls Cancel() or similar. uint8_t cbType = mCallbackType; - mCallbackType = CALLBACK_TYPE_UNKNOWN; + mCallbackType = CALLBACK_TYPE_UNKNOWN; - if (cbType == CALLBACK_TYPE_INTERFACE) + if (cbType == CALLBACK_TYPE_INTERFACE) { NS_RELEASE(mCallback.i); - else if (cbType == CALLBACK_TYPE_OBSERVER) + } else if (cbType == CALLBACK_TYPE_OBSERVER) { NS_RELEASE(mCallback.o); + } } - bool IsRepeating() const { + bool IsRepeating() const + { PR_STATIC_ASSERT(TYPE_ONE_SHOT < TYPE_REPEATING_SLACK); PR_STATIC_ASSERT(TYPE_REPEATING_SLACK < TYPE_REPEATING_PRECISE); PR_STATIC_ASSERT(TYPE_REPEATING_PRECISE < TYPE_REPEATING_PRECISE_CAN_SKIP); return mType >= TYPE_REPEATING_SLACK; } - bool IsRepeatingPrecisely() const { + bool IsRepeatingPrecisely() const + { return mType >= TYPE_REPEATING_PRECISE; } nsCOMPtr mEventTarget; - void * mClosure; + void* mClosure; - union CallbackUnion { + union CallbackUnion + { nsTimerCallbackFunc c; - nsITimerCallback * i; - nsIObserver * o; + nsITimerCallback* i; + nsIObserver* o; } mCallback; // Some callers expect to be able to access the callback while the @@ -131,6 +151,10 @@ private: uint32_t mDelay; TimeStamp mTimeout; +#ifdef MOZ_TASK_TRACER + nsRefPtr mTracedTask; +#endif + #ifdef DEBUG_TIMERS TimeStamp mStart, mStart2; static double sDeltaSum; -- cgit v1.2.3