summaryrefslogtreecommitdiff
path: root/xpcom/threads
diff options
context:
space:
mode:
authorPale Moon <git-repo@palemoon.org>2016-09-01 13:39:08 +0200
committerPale Moon <git-repo@palemoon.org>2016-09-01 13:39:08 +0200
commit3d8ce1a11a7347cc94a937719c4bc8df46fb8d14 (patch)
tree8c26ca375a6312751c00a27e1653fb6f189f0463 /xpcom/threads
parente449bdb1ec3a82f204bffdd9c3c54069d086eee3 (diff)
downloadpalemoon-gre-3d8ce1a11a7347cc94a937719c4bc8df46fb8d14.tar.gz
Base import of Tycho code (warning: huge commit)
Diffstat (limited to 'xpcom/threads')
-rw-r--r--xpcom/threads/BackgroundHangMonitor.cpp601
-rw-r--r--xpcom/threads/BackgroundHangMonitor.h197
-rw-r--r--xpcom/threads/HangMonitor.cpp436
-rw-r--r--xpcom/threads/HangMonitor.h166
-rw-r--r--xpcom/threads/LazyIdleThread.cpp144
-rw-r--r--xpcom/threads/LazyIdleThread.h21
-rw-r--r--xpcom/threads/Makefile.in26
-rw-r--r--xpcom/threads/SyncRunnable.h52
-rw-r--r--xpcom/threads/TimerThread.cpp273
-rw-r--r--xpcom/threads/TimerThread.h70
-rw-r--r--xpcom/threads/moz.build27
-rw-r--r--xpcom/threads/nsEnvironment.cpp204
-rw-r--r--xpcom/threads/nsEnvironment.h20
-rw-r--r--xpcom/threads/nsEventQueue.cpp109
-rw-r--r--xpcom/threads/nsEventQueue.h61
-rw-r--r--xpcom/threads/nsIProcess.idl2
-rw-r--r--xpcom/threads/nsIThreadInternal.idl54
-rw-r--r--xpcom/threads/nsIThreadManager.idl8
-rw-r--r--xpcom/threads/nsIThreadPool.idl12
-rw-r--r--xpcom/threads/nsITimer.idl38
-rw-r--r--xpcom/threads/nsMemoryPressure.cpp54
-rw-r--r--xpcom/threads/nsMemoryPressure.h77
-rw-r--r--xpcom/threads/nsProcess.h30
-rw-r--r--xpcom/threads/nsProcessCommon.cpp897
-rw-r--r--xpcom/threads/nsThread.cpp757
-rw-r--r--xpcom/threads/nsThread.h168
-rw-r--r--xpcom/threads/nsThreadManager.cpp470
-rw-r--r--xpcom/threads/nsThreadManager.h102
-rw-r--r--xpcom/threads/nsThreadPool.cpp182
-rw-r--r--xpcom/threads/nsThreadPool.h16
-rw-r--r--xpcom/threads/nsTimerImpl.cpp354
-rw-r--r--xpcom/threads/nsTimerImpl.h66
32 files changed, 4158 insertions, 1536 deletions
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 <algorithm>
+
+// 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<BackgroundHangManager*>(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<BackgroundHangManager> 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<BackgroundHangThread> 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<nsIObserverService> 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<BackgroundHangThread>
+{
+private:
+ static ThreadLocal<BackgroundHangThread*> 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<BackgroundHangManager> 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> BackgroundHangManager::sInstance;
+bool BackgroundHangManager::sProhibited = false;
+bool BackgroundHangManager::sDisabled = false;
+
+ThreadLocal<BackgroundHangThread*> 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<BackgroundHangManager> 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<nsIObserverService> 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 <stdint.h>
+
+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<BackgroundHangThread> 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 <set>
+
+#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 <windows.h>
#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<PRIntervalTime> 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<std::set<Annotator*>> 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<nsString, nsString> AnnotationType;
+ typedef std::vector<AnnotationType> 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<uintptr_t>* stack =
+ static_cast<std::vector<uintptr_t>*>(aClosure);
+ if (stack->size() == MAX_CALL_STACK_PCS) {
+ return;
+ }
+ MOZ_ASSERT(stack->size() < MAX_CALL_STACK_PCS);
+ stack->push_back(reinterpret_cast<uintptr_t>(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<uintptr_t> rawStack;
+ rawStack.reserve(MAX_CALL_STACK_PCS);
+ DWORD ret = ::SuspendThread(winMainThreadHandle);
+ if (ret == -1) {
+ return;
+ }
+ NS_StackWalk(ChromeStackWalker, /* skipFrames */ 0, /* maxFrames */ 0,
+ reinterpret_cast<void*>(&rawStack),
+ reinterpret_cast<uintptr_t>(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<int32_t>(td.ToSeconds()) - (gTimeout * 2)) / 60;
+ } else {
+ aFirefoxUptime = -1;
+ }
+}
+
+static void
+ChromeHangAnnotatorCallout(ChromeHangAnnotations& aAnnotations)
+{
+ gMonitor->AssertCurrentThreadOwns();
+ MOZ_ASSERT(gAnnotators);
+ if (!gAnnotators) {
+ return;
+ }
+ for (std::set<Annotator*>::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<ChromeHangAnnotations>();
+#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<ChromeHangAnnotations>();
+ }
+#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<Annotator*>();
+#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<nsIObserverService> 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<nsIRunnable> 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<nsIObserverService> obs =
@@ -268,12 +292,16 @@ LazyIdleThread::ShutdownThread()
nsCOMPtr<nsIRunnable> 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<nsIRunnable> 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<nsCOMPtr<nsIRunnable> >* mQueuedRunnables;
+ nsTArray<nsCOMPtr<nsIRunnable>>* 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<SyncRunnable> s(new SyncRunnable(r));
- s->DispatchToThread(thread);
+ nsRefPtr<SyncRunnable> 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<nsIRunnable> 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 <math.h>
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<TimerObserverRunnable> 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<nsTimerImpl*> 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<uint32_t>(microseconds)); // Floor is accurate enough.
- if (waitFor == 0)
- waitFor = 1; // round up, wait the minimum time we can wait
+ }
+ waitFor = PR_MicrosecondsToInterval(
+ static_cast<uint32_t>(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<bool> 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<nsIThread> mThread;
Monitor mMonitor;
bool mShutdown;
bool mWaiting;
+ bool mNotified;
bool mSleeping;
-
+ TimeStamp mLastTimerEventLoopRun;
+
nsTArray<nsTimerImpl*> 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<nsCharPtrHashKey,char*> EnvEntryType;
+typedef nsBaseHashtableET<nsCharPtrHashKey, char*> EnvEntryType;
typedef nsTHashtable<EnvEntryType> 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<nsIRunnable> event(runnable);
- bool rv = true;
- {
- ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ nsRefPtr<nsIRunnable> 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<Page *>(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<Page*>(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;
/**
@@ -56,6 +58,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<int32_t, Relaxed> 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<nsIRunnable> 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 <crt_externs.h>
#include <spawn.h>
#include <sys/wait.h>
+#include <sys/errno.h>
#endif
#include <sys/types.h>
#include <signal.h>
@@ -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<nsProcess> process = dont_AddRef(static_cast<nsProcess*>(arg));
+ nsRefPtr<nsProcess> process = dont_AddRef(static_cast<nsProcess*>(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<nsIRunnable> 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<nsIRunnable> event =
+ NS_NewRunnableMethod(process, &nsProcess::ProcessComplete);
+ NS_DispatchToMainThread(event);
+ }
}
-void nsProcess::ProcessComplete()
+void
+nsProcess::ProcessComplete()
{
- if (mThread) {
- nsCOMPtr<nsIObserverService> os =
- mozilla::services::GetObserverService();
- if (os)
- os->RemoveObserver(this, "xpcom-shutdown");
- PR_JoinThread(mThread);
- mThread = nullptr;
+ if (mThread) {
+ nsCOMPtr<nsIObserverService> 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<nsIObserver> 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<nsIObserver> 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<char*>(args[i]);
- }
+ for (uint32_t i = 0; i < aCount; ++i) {
+ my_argv[i + 1] = const_cast<char*>(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<int32_t>(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<int32_t>(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<nsIObserverService> 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<nsIObserverService> 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<nsIObserverService> 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<nsIObserverService> 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<nsIObserverService> os =
- mozilla::services::GetObserverService();
- if (os)
- os->RemoveObserver(this, "xpcom-shutdown");
- mThread = nullptr;
+ // Shutting down, drop all references
+ if (mThread) {
+ nsCOMPtr<nsIObserverService> 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 <sys/time.h>
+#include <sys/resource.h>
+#include <sched.h>
+#endif
#define HAVE_UALARM _BSD_SOURCE || (_XOPEN_SOURCE >= 500 || \
_XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED) && \
!(_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700)
-#if defined(XP_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 <unistd.h>
# include <execinfo.h>
# include <signal.h>
@@ -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<nsIClassInfo*>(&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<nsThread> 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<nsThread *>(arg); // strong reference
+ using mozilla::ipc::BackgroundChild;
+
+ nsThread* self = static_cast<nsThread*>(aArg); // strong reference
self->mThread = PR_GetCurrentThread();
+ SetupCurrentThreadForChaosMode();
// Inform the ThreadManager
nsThreadManager::get()->RegisterCurrentThread(self);
+#ifdef MOZ_NUWA_PROCESS
+ self->mThreadStatusInfo =
+ static_cast<void*>(nsThreadManager::get()->GetCurrentThreadStatusInfo());
+#endif
+
+ mozilla::IOInterposer::RegisterCurrentThread();
// Wait for and process startup event
nsCOMPtr<nsIRunnable> 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<MessageLoop> 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<nsThreadStartupEvent> startup = nsThreadStartupEvent::Create();
- NS_ENSURE_TRUE(startup, NS_ERROR_OUT_OF_MEMORY);
-
+ nsRefPtr<nsThreadStartupEvent> 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<void*>(nsThreadManager::get()->GetCurrentThreadStatusInfo());
+#endif
+
return NS_OK;
}
nsresult
-nsThread::PutEvent(nsIRunnable *event)
+nsThread::PutEvent(nsIRunnable* aEvent, nsNestedEventTarget* aTarget)
{
+ nsCOMPtr<nsIThreadObserver> 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<nsIThreadObserver> 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<nsIRunnable> 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<nsThreadSyncDispatch> 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<nsIRunnable> 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<nsIObserverService> os = services::GetObserverService();
+
+ // Use no-forward to prevent the notifications from being transferred to
+ // the children of this process.
+ NS_NAMED_LITERAL_STRING(lowMem, "low-memory-no-forward");
+ NS_NAMED_LITERAL_STRING(lowMemOngoing, "low-memory-ongoing-no-forward");
+
if (os) {
os->NotifyObservers(nullptr, "memory-pressure",
- 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<nsIThreadObserver> 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<nsIRunnable> 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<nsIRunnable> notifyAllIdleRunnable;
+ {
+ ReentrantMonitorAutoEnter mon(mThreadStatusMonitor);
+ if ((!mEvents->GetEvent(false, nullptr)) && (mRunningEvent == 0)) {
+ nsThreadManager::get()->SetThreadIsWorking(
+ static_cast<nsThreadManager::ThreadStatusInfo*>(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<nsChainedEventQueue> queue;
+ nsRefPtr<nsNestedEventTarget> target;
+
+ {
+ MutexAutoLock lock(mLock);
+
+ // Make sure we're popping the innermost event target.
+ if (NS_WARN_IF(mEvents->mEventTarget != aInnermostTarget)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ MOZ_ASSERT(mEvents != &mEventsRoot);
+
+ queue = mEvents;
+ mEvents = mEvents->mNext;
+
+ nsCOMPtr<nsIRunnable> 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<nsThreadManager::ThreadStatusInfo*>(mThreadStatusInfo),
+ true, nullptr);
+}
+
+void
+nsThread::SetIdle()
+{
+ nsThreadManager::get()->SetThreadIsWorking(
+ static_cast<nsThreadManager::ThreadStatusInfo*>(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<nsIThreadObserver> GetObserver() {
- nsIThreadObserver *obs;
+ already_AddRefed<nsIThreadObserver> GetObserver()
+ {
+ nsIThreadObserver* obs;
nsThread::GetObserver(&obs);
return already_AddRefed<nsIThreadObserver>(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<nsNestedEventTarget> 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<nsThread> 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<nsCOMPtr<nsIThreadObserver>, 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 <fcntl.h>
+#include <unistd.h>
+#endif
using namespace mozilla;
@@ -21,49 +26,158 @@ DWORD gTLSThreadIDIndex = TlsAlloc();
NS_TLS mozilla::threads::ID gTLSThreadID = mozilla::threads::Generic;
#endif
-typedef nsTArray< nsRefPtr<nsThread> > nsThreadArray;
+static mozilla::ThreadLocal<bool> 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<nsRefPtr<nsThread>> nsThreadArray;
+
+#ifdef MOZ_NUWA_PROCESS
+class NotifyAllThreadsWereIdle: public nsRunnable
+{
+public:
+
+ NotifyAllThreadsWereIdle(
+ nsTArray<nsRefPtr<nsThreadManager::AllThreadsWereIdleListener>>* aListeners)
+ : mListeners(aListeners)
+ {
+ }
+
+ virtual NS_IMETHODIMP
+ Run() {
+ // Copy listener array, which may be modified during call back.
+ nsTArray<nsRefPtr<nsThreadManager::AllThreadsWereIdleListener>> 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<nsRefPtr<nsThreadManager::AllThreadsWereIdleListener>>* mListeners;
+};
+
+struct nsThreadManager::ThreadStatusInfo {
+ Atomic<bool> mWorking;
+ Atomic<bool> mWillBeWorking;
+ bool mIgnored;
+ ThreadStatusInfo()
+ : mWorking(false)
+ , mWillBeWorking(false)
+ , mIgnored(false)
+ {
+ }
+};
+#endif // MOZ_NUWA_PROCESS
//-----------------------------------------------------------------------------
static void
-ReleaseObject(void *data)
+ReleaseObject(void* aData)
+{
+ static_cast<nsISupports*>(aData)->Release();
+}
+
+#ifdef MOZ_NUWA_PROCESS
+void
+nsThreadManager::DeleteThreadStatusInfo(void* aData)
{
- static_cast<nsISupports *>(data)->Release();
+ nsThreadManager* mgr = nsThreadManager::get();
+ nsThreadManager::ThreadStatusInfo* thrInfo =
+ static_cast<nsThreadManager::ThreadStatusInfo*>(aData);
+ {
+ ReentrantMonitorAutoEnter mon(*(mgr->mMonitor));
+ mgr->mThreadStatusInfos.RemoveElement(thrInfo);
+ if (NS_IsMainThread()) {
+ mgr->mMainThreadStatusInfo = nullptr;
+ }
+ }
+ delete thrInfo;
}
+#endif
static PLDHashOperator
-AppendAndRemoveThread(PRThread *key, nsRefPtr<nsThread> &thread, void *arg)
+AppendAndRemoveThread(PRThread* aKey, nsRefPtr<nsThread>& aThread, void* aArg)
{
- nsThreadArray *threads = static_cast<nsThreadArray *>(arg);
- threads->AppendElement(thread);
+ nsThreadArray* threads = static_cast<nsThreadArray*>(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<ReentrantMonitor>("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<nsThread *>(data);
+ void* data = PR_GetThreadPrivate(mCurThreadIndex);
+ if (data) {
+ return static_cast<nsThread*>(data);
+ }
if (!mInitialized) {
return nullptr;
@@ -195,96 +318,281 @@ nsThreadManager::GetCurrentThread()
// OK, that's fine. We'll dynamically create one :-)
nsRefPtr<nsThread> 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<ThreadStatusInfo*>(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<nsThread> 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<nsThread> 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<NotifyAllThreadsWereIdle> 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<nsPtrHashKey<PRThread>, nsThread> mThreadsByPRThread;
- unsigned mCurThreadIndex; // thread-local-storage index
+ unsigned mCurThreadIndex; // thread-local-storage index
nsRefPtr<nsThread> mMainThread;
- PRThread *mMainPRThread;
- // This is a pointer in order to allow creating nsThreadManager from
- // the static context in debug builds.
- nsAutoPtr<mozilla::Mutex> 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<bool> 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<nsRefPtr<AllThreadsWereIdleListener>> mThreadsIdledListeners;
+ nsTArray<ThreadStatusInfo*> mThreadStatusInfos;
+ mozilla::UniquePtr<mozilla::ReentrantMonitor> 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<nsIThread> 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<nsIRunnable> 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<nsIRunnable> r = NS_NewRunnableMethod(thread, &nsIThread::Shutdown);
+ nsRefPtr<nsIRunnable> 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<nsIThread> 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<nsIThread> thread;
nsThreadManager::get()->GetCurrentThread(getter_AddRefs(thread));
- NS_ENSURE_STATE(thread);
+ if (NS_WARN_IF(!thread)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
nsRefPtr<nsThreadSyncDispatch> 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<uint32_t>(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<uint32_t>(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) {
@@ -336,6 +383,22 @@ nsThreadPool::SetIdleThreadTimeout(uint32_t value)
}
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)
{
ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
@@ -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<nsIThread> mThreads;
nsEventQueue mEvents;
@@ -39,6 +40,7 @@ private:
uint32_t mIdleThreadLimit;
uint32_t mIdleThreadTimeout;
uint32_t mIdleCount;
+ uint32_t mStackSize;
nsCOMPtr<nsIThreadPoolListener> 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<int32_t> 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<nsTimerImpl> mTimer;
int32_t mGeneration;
static TimerEventAllocator* sAllocator;
- static int32_t sAllocatorUsers;
+ static Atomic<int32_t> sAllocatorUsers;
static bool sCanDeleteAllocator;
};
TimerEventAllocator* nsTimerEvent::sAllocator = nullptr;
-int32_t nsTimerEvent::sAllocatorUsers = 0;
+Atomic<int32_t> 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<nsIEventTarget*>(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> nsTimerImpl::PostTimerEvent(already_AddRefed<nsTimerImpl> aTimerRef)
+already_AddRefed<nsTimerImpl>
+nsTimerImpl::PostTimerEvent(already_AddRefed<nsTimerImpl> aTimerRef)
{
nsRefPtr<nsTimerImpl> timer(aTimerRef);
if (!timer->mEventTarget) {
@@ -658,8 +751,9 @@ already_AddRefed<nsTimerImpl> nsTimerImpl::PostTimerEvent(already_AddRefed<nsTim
// Note: We override operator new for this class, and the override is
// fallible!
nsRefPtr<nsTimerEvent> 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> nsTimerImpl::PostTimerEvent(already_AddRefed<nsTim
#endif
// If this is a repeating precise timer, we need to calculate the time for
- // the next timer to fire before we make the callback. But don't re-arm.
+ // the next timer to fire before we make the callback.
if (timer->IsRepeatingPrecisely()) {
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> nsTimerImpl::PostTimerEvent(already_AddRefed<nsTim
nsresult rv = target->Dispatch(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<nsTimerImpl> PostTimerEvent(
- already_AddRefed<nsTimerImpl> aTimerRef);
+ already_AddRefed<nsTimerImpl> 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<nsIEventTarget> 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<mozilla::tasktracer::FakeTracedTask> mTracedTask;
+#endif
+
#ifdef DEBUG_TIMERS
TimeStamp mStart, mStart2;
static double sDeltaSum;