summaryrefslogtreecommitdiffstats
path: root/xpcom/threads
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /xpcom/threads
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xpcom/threads')
-rw-r--r--xpcom/threads/AbstractThread.cpp380
-rw-r--r--xpcom/threads/AbstractThread.h134
-rw-r--r--xpcom/threads/BlockingResourceBase.cpp542
-rw-r--r--xpcom/threads/BlockingResourceBase.h328
-rw-r--r--xpcom/threads/CPUUsageWatcher.cpp256
-rw-r--r--xpcom/threads/CPUUsageWatcher.h100
-rw-r--r--xpcom/threads/CondVar.h137
-rw-r--r--xpcom/threads/DataMutex.h125
-rw-r--r--xpcom/threads/DeadlockDetector.h359
-rw-r--r--xpcom/threads/EventQueue.cpp133
-rw-r--r--xpcom/threads/EventQueue.h132
-rw-r--r--xpcom/threads/IdlePeriodState.cpp254
-rw-r--r--xpcom/threads/IdlePeriodState.h194
-rw-r--r--xpcom/threads/IdleTaskRunner.cpp181
-rw-r--r--xpcom/threads/IdleTaskRunner.h89
-rw-r--r--xpcom/threads/InputEventStatistics.cpp68
-rw-r--r--xpcom/threads/InputEventStatistics.h102
-rw-r--r--xpcom/threads/InputTaskManager.cpp77
-rw-r--r--xpcom/threads/InputTaskManager.h97
-rw-r--r--xpcom/threads/LazyIdleThread.cpp628
-rw-r--r--xpcom/threads/LazyIdleThread.h219
-rw-r--r--xpcom/threads/LeakRefPtr.h48
-rw-r--r--xpcom/threads/MainThreadIdlePeriod.cpp75
-rw-r--r--xpcom/threads/MainThreadIdlePeriod.h31
-rw-r--r--xpcom/threads/MainThreadUtils.h48
-rw-r--r--xpcom/threads/Monitor.h119
-rw-r--r--xpcom/threads/MozPromise.h1697
-rw-r--r--xpcom/threads/MozPromiseInlines.h47
-rw-r--r--xpcom/threads/Mutex.h248
-rw-r--r--xpcom/threads/PerformanceCounter.cpp73
-rw-r--r--xpcom/threads/PerformanceCounter.h139
-rw-r--r--xpcom/threads/Queue.h242
-rw-r--r--xpcom/threads/RWLock.cpp87
-rw-r--r--xpcom/threads/RWLock.h197
-rw-r--r--xpcom/threads/RecursiveMutex.cpp85
-rw-r--r--xpcom/threads/RecursiveMutex.h112
-rw-r--r--xpcom/threads/ReentrantMonitor.h242
-rw-r--r--xpcom/threads/SchedulerGroup.cpp157
-rw-r--r--xpcom/threads/SchedulerGroup.h118
-rw-r--r--xpcom/threads/SharedThreadPool.cpp232
-rw-r--r--xpcom/threads/SharedThreadPool.h128
-rw-r--r--xpcom/threads/SpinEventLoopUntil.h108
-rw-r--r--xpcom/threads/StateMirroring.h393
-rw-r--r--xpcom/threads/StateWatching.h302
-rw-r--r--xpcom/threads/SyncRunnable.h133
-rw-r--r--xpcom/threads/SynchronizedEventQueue.cpp26
-rw-r--r--xpcom/threads/SynchronizedEventQueue.h119
-rw-r--r--xpcom/threads/TaskCategory.h47
-rw-r--r--xpcom/threads/TaskController.cpp936
-rw-r--r--xpcom/threads/TaskController.h415
-rw-r--r--xpcom/threads/TaskDispatcher.h301
-rw-r--r--xpcom/threads/TaskQueue.cpp233
-rw-r--r--xpcom/threads/TaskQueue.h224
-rw-r--r--xpcom/threads/ThreadBound.h143
-rw-r--r--xpcom/threads/ThreadDelay.cpp38
-rw-r--r--xpcom/threads/ThreadDelay.h16
-rw-r--r--xpcom/threads/ThreadEventQueue.cpp262
-rw-r--r--xpcom/threads/ThreadEventQueue.h88
-rw-r--r--xpcom/threads/ThreadEventTarget.cpp135
-rw-r--r--xpcom/threads/ThreadEventTarget.h54
-rw-r--r--xpcom/threads/ThreadLocalVariables.cpp16
-rw-r--r--xpcom/threads/ThrottledEventQueue.cpp439
-rw-r--r--xpcom/threads/ThrottledEventQueue.h118
-rw-r--r--xpcom/threads/TimerThread.cpp779
-rw-r--r--xpcom/threads/TimerThread.h115
-rw-r--r--xpcom/threads/components.conf21
-rw-r--r--xpcom/threads/moz.build129
-rw-r--r--xpcom/threads/nsEnvironment.cpp152
-rw-r--r--xpcom/threads/nsEnvironment.h36
-rw-r--r--xpcom/threads/nsICancelableRunnable.h40
-rw-r--r--xpcom/threads/nsIDirectTaskDispatcher.idl57
-rw-r--r--xpcom/threads/nsIDiscardableRunnable.h41
-rw-r--r--xpcom/threads/nsIEnvironment.idl55
-rw-r--r--xpcom/threads/nsIEventTarget.idl184
-rw-r--r--xpcom/threads/nsIIdlePeriod.idl32
-rw-r--r--xpcom/threads/nsIIdleRunnable.h48
-rw-r--r--xpcom/threads/nsINamed.idl24
-rw-r--r--xpcom/threads/nsIProcess.idl112
-rw-r--r--xpcom/threads/nsIRunnable.idl39
-rw-r--r--xpcom/threads/nsISerialEventTarget.idl27
-rw-r--r--xpcom/threads/nsISupportsPriority.idl45
-rw-r--r--xpcom/threads/nsIThread.idl213
-rw-r--r--xpcom/threads/nsIThreadInternal.idl110
-rw-r--r--xpcom/threads/nsIThreadManager.idl156
-rw-r--r--xpcom/threads/nsIThreadPool.idl107
-rw-r--r--xpcom/threads/nsITimer.idl341
-rw-r--r--xpcom/threads/nsMemoryPressure.cpp49
-rw-r--r--xpcom/threads/nsMemoryPressure.h87
-rw-r--r--xpcom/threads/nsProcess.h81
-rw-r--r--xpcom/threads/nsProcessCommon.cpp566
-rw-r--r--xpcom/threads/nsProxyRelease.cpp30
-rw-r--r--xpcom/threads/nsProxyRelease.h384
-rw-r--r--xpcom/threads/nsThread.cpp1557
-rw-r--r--xpcom/threads/nsThread.h444
-rw-r--r--xpcom/threads/nsThreadManager.cpp841
-rw-r--r--xpcom/threads/nsThreadManager.h111
-rw-r--r--xpcom/threads/nsThreadPool.cpp627
-rw-r--r--xpcom/threads/nsThreadPool.h63
-rw-r--r--xpcom/threads/nsThreadSyncDispatch.h59
-rw-r--r--xpcom/threads/nsThreadUtils.cpp878
-rw-r--r--xpcom/threads/nsThreadUtils.h1968
-rw-r--r--xpcom/threads/nsTimerImpl.cpp804
-rw-r--r--xpcom/threads/nsTimerImpl.h286
103 files changed, 24904 insertions, 0 deletions
diff --git a/xpcom/threads/AbstractThread.cpp b/xpcom/threads/AbstractThread.cpp
new file mode 100644
index 0000000000..796b1539e9
--- /dev/null
+++ b/xpcom/threads/AbstractThread.cpp
@@ -0,0 +1,380 @@
+/* -*- 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/AbstractThread.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MozPromise.h" // We initialize the MozPromise logging in this file.
+#include "mozilla/StateWatching.h" // We initialize the StateWatching logging in this file.
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TaskDispatcher.h"
+#include "mozilla/TaskQueue.h"
+#include "mozilla/Unused.h"
+#include "nsContentUtils.h"
+#include "nsIDirectTaskDispatcher.h"
+#include "nsIThreadInternal.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadManager.h"
+#include "nsThreadUtils.h"
+namespace mozilla {
+
+LazyLogModule gMozPromiseLog("MozPromise");
+LazyLogModule gStateWatchingLog("StateWatching");
+
+StaticRefPtr<AbstractThread> sMainThread;
+MOZ_THREAD_LOCAL(AbstractThread*) AbstractThread::sCurrentThreadTLS;
+
+class XPCOMThreadWrapper final : public AbstractThread,
+ public nsIThreadObserver,
+ public nsIDirectTaskDispatcher {
+ public:
+ XPCOMThreadWrapper(nsIThreadInternal* aThread, bool aRequireTailDispatch,
+ bool aOnThread)
+ : AbstractThread(aRequireTailDispatch),
+ mThread(aThread),
+ mDirectTaskDispatcher(do_QueryInterface(aThread)),
+ mOnThread(aOnThread) {
+ MOZ_DIAGNOSTIC_ASSERT(mThread && mDirectTaskDispatcher);
+ MOZ_DIAGNOSTIC_ASSERT(!aOnThread || IsCurrentThreadIn());
+ if (aOnThread) {
+ MOZ_ASSERT(!sCurrentThreadTLS.get(),
+ "There can only be a single XPCOMThreadWrapper available on a "
+ "thread");
+ // Set the default current thread so that GetCurrent() never returns
+ // nullptr.
+ sCurrentThreadTLS.set(this);
+ }
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsresult Dispatch(already_AddRefed<nsIRunnable> aRunnable,
+ DispatchReason aReason = NormalDispatch) override {
+ nsCOMPtr<nsIRunnable> r = aRunnable;
+ AbstractThread* currentThread;
+ if (aReason != TailDispatch && (currentThread = GetCurrent()) &&
+ RequiresTailDispatch(currentThread) &&
+ currentThread->IsTailDispatcherAvailable()) {
+ return currentThread->TailDispatcher().AddTask(this, r.forget());
+ }
+
+ // At a certain point during shutdown, we stop processing events from the
+ // main thread event queue (this happens long after all _other_ XPCOM
+ // threads have been shut down). However, various bits of subsequent
+ // teardown logic (the media shutdown blocker and the final shutdown cycle
+ // collection) can trigger state watching and state mirroring notifications
+ // that result in dispatch to the main thread. This causes shutdown leaks,
+ // because the |Runner| wrapper below creates a guaranteed cycle
+ // (Thread->EventQueue->Runnable->Thread) until the event is processed. So
+ // if we put the event into a queue that will never be processed, we'll wind
+ // up with a leak.
+ //
+ // We opt to just release the runnable in that case. Ordinarily, this
+ // approach could cause problems for runnables that are only safe to be
+ // released on the target thread (and not the dispatching thread). This is
+ // why XPCOM thread dispatch explicitly leaks the runnable when dispatch
+ // fails, rather than releasing it. But given that this condition only
+ // applies very late in shutdown when only one thread remains operational,
+ // that concern is unlikely to apply.
+ if (gXPCOMMainThreadEventsAreDoomed) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<nsIRunnable> runner = new Runner(this, r.forget());
+ return mThread->Dispatch(runner.forget(), NS_DISPATCH_NORMAL);
+ }
+
+ // Prevent a GCC warning about the other overload of Dispatch being hidden.
+ using AbstractThread::Dispatch;
+
+ bool IsCurrentThreadIn() const override {
+ return mThread->IsOnCurrentThread();
+ }
+
+ TaskDispatcher& TailDispatcher() override {
+ MOZ_ASSERT(IsCurrentThreadIn());
+ MOZ_ASSERT(IsTailDispatcherAvailable());
+ if (!mTailDispatcher.isSome()) {
+ mTailDispatcher.emplace(mDirectTaskDispatcher,
+ /* aIsTailDispatcher = */ true);
+ mThread->AddObserver(this);
+ }
+
+ return mTailDispatcher.ref();
+ }
+
+ bool IsTailDispatcherAvailable() override {
+ // Our tail dispatching implementation relies on nsIThreadObserver
+ // callbacks. If we're not doing event processing, it won't work.
+ bool inEventLoop =
+ static_cast<nsThread*>(mThread.get())->RecursionDepth() > 0;
+ return inEventLoop;
+ }
+
+ bool MightHaveTailTasks() override { return mTailDispatcher.isSome(); }
+
+ nsIEventTarget* AsEventTarget() override { return mThread; }
+
+ //-----------------------------------------------------------------------------
+ // nsIThreadObserver
+ //-----------------------------------------------------------------------------
+ NS_IMETHOD OnDispatchedEvent() override { return NS_OK; }
+
+ NS_IMETHOD AfterProcessNextEvent(nsIThreadInternal* thread,
+ bool eventWasProcessed) override {
+ // This is the primary case.
+ MaybeFireTailDispatcher();
+ return NS_OK;
+ }
+
+ NS_IMETHOD OnProcessNextEvent(nsIThreadInternal* thread,
+ bool mayWait) override {
+ // In general, the tail dispatcher is handled at the end of the current in
+ // AfterProcessNextEvent() above. However, if start spinning a nested event
+ // loop, it's generally better to fire the tail dispatcher before the first
+ // nested event, rather than after it. This check handles that case.
+ MaybeFireTailDispatcher();
+ return NS_OK;
+ }
+
+ //-----------------------------------------------------------------------------
+ // nsIDirectTaskDispatcher
+ //-----------------------------------------------------------------------------
+ // Forward calls to nsIDirectTaskDispatcher to the underlying nsThread object.
+ // We can't use the generated NS_FORWARD_NSIDIRECTTASKDISPATCHER macro
+ // as already_AddRefed type must be moved.
+ NS_IMETHOD DispatchDirectTask(already_AddRefed<nsIRunnable> aEvent) override {
+ return mDirectTaskDispatcher->DispatchDirectTask(std::move(aEvent));
+ }
+ NS_IMETHOD DrainDirectTasks() override {
+ return mDirectTaskDispatcher->DrainDirectTasks();
+ }
+ NS_IMETHOD HaveDirectTasks(bool* aResult) override {
+ return mDirectTaskDispatcher->HaveDirectTasks(aResult);
+ }
+
+ private:
+ const RefPtr<nsIThreadInternal> mThread;
+ const nsCOMPtr<nsIDirectTaskDispatcher> mDirectTaskDispatcher;
+ Maybe<AutoTaskDispatcher> mTailDispatcher;
+ const bool mOnThread;
+
+ ~XPCOMThreadWrapper() {
+ if (mOnThread) {
+ MOZ_DIAGNOSTIC_ASSERT(IsCurrentThreadIn(),
+ "Must be destroyed on the thread it was created");
+ sCurrentThreadTLS.set(nullptr);
+ }
+ }
+
+ void MaybeFireTailDispatcher() {
+ if (mTailDispatcher.isSome()) {
+ mTailDispatcher.ref().DrainDirectTasks();
+ mThread->RemoveObserver(this);
+ mTailDispatcher.reset();
+ }
+ }
+
+ class Runner : public Runnable {
+ public:
+ explicit Runner(XPCOMThreadWrapper* aThread,
+ already_AddRefed<nsIRunnable> aRunnable)
+ : Runnable("XPCOMThreadWrapper::Runner"),
+ mThread(aThread),
+ mRunnable(aRunnable) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(mThread == AbstractThread::GetCurrent());
+ MOZ_ASSERT(mThread->IsCurrentThreadIn());
+ SerialEventTargetGuard guard(mThread);
+ return mRunnable->Run();
+ }
+
+#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+ NS_IMETHOD GetName(nsACString& aName) override {
+ aName.AssignLiteral("AbstractThread::Runner");
+ if (nsCOMPtr<nsINamed> named = do_QueryInterface(mRunnable)) {
+ nsAutoCString name;
+ named->GetName(name);
+ if (!name.IsEmpty()) {
+ aName.AppendLiteral(" for ");
+ aName.Append(name);
+ }
+ }
+ return NS_OK;
+ }
+#endif
+
+ private:
+ const RefPtr<XPCOMThreadWrapper> mThread;
+ const RefPtr<nsIRunnable> mRunnable;
+ };
+};
+NS_IMPL_ISUPPORTS_INHERITED(XPCOMThreadWrapper, AbstractThread,
+ nsIThreadObserver, nsIDirectTaskDispatcher);
+
+NS_IMPL_ISUPPORTS(AbstractThread, nsIEventTarget, nsISerialEventTarget)
+
+NS_IMETHODIMP_(bool)
+AbstractThread::IsOnCurrentThreadInfallible() { return IsCurrentThreadIn(); }
+
+NS_IMETHODIMP
+AbstractThread::IsOnCurrentThread(bool* aResult) {
+ *aResult = IsCurrentThreadIn();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AbstractThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) {
+ nsCOMPtr<nsIRunnable> event(aEvent);
+ return Dispatch(event.forget(), aFlags);
+}
+
+NS_IMETHODIMP
+AbstractThread::Dispatch(already_AddRefed<nsIRunnable> aEvent,
+ uint32_t aFlags) {
+ return Dispatch(std::move(aEvent), NormalDispatch);
+}
+
+NS_IMETHODIMP
+AbstractThread::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
+ uint32_t aDelayMs) {
+ nsCOMPtr<nsIRunnable> event = aEvent;
+ NS_ENSURE_TRUE(!!aDelayMs, NS_ERROR_UNEXPECTED);
+
+ RefPtr<DelayedRunnable> r =
+ new DelayedRunnable(do_AddRef(this), event.forget(), aDelayMs);
+ nsresult rv = r->Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+}
+
+nsresult AbstractThread::TailDispatchTasksFor(AbstractThread* aThread) {
+ if (MightHaveTailTasks()) {
+ return TailDispatcher().DispatchTasksFor(aThread);
+ }
+
+ return NS_OK;
+}
+
+bool AbstractThread::HasTailTasksFor(AbstractThread* aThread) {
+ if (!MightHaveTailTasks()) {
+ return false;
+ }
+ return TailDispatcher().HasTasksFor(aThread);
+}
+
+bool AbstractThread::RequiresTailDispatch(AbstractThread* aThread) const {
+ MOZ_ASSERT(aThread);
+ // We require tail dispatch if both the source and destination
+ // threads support it.
+ return SupportsTailDispatch() && aThread->SupportsTailDispatch();
+}
+
+bool AbstractThread::RequiresTailDispatchFromCurrentThread() const {
+ AbstractThread* current = GetCurrent();
+ return current && RequiresTailDispatch(current);
+}
+
+AbstractThread* AbstractThread::MainThread() {
+ MOZ_ASSERT(sMainThread);
+ return sMainThread;
+}
+
+void AbstractThread::InitTLS() {
+ if (!sCurrentThreadTLS.init()) {
+ MOZ_CRASH();
+ }
+}
+
+void AbstractThread::InitMainThread() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!sMainThread);
+ nsCOMPtr<nsIThreadInternal> mainThread =
+ do_QueryInterface(nsThreadManager::get().GetMainThreadWeak());
+ MOZ_DIAGNOSTIC_ASSERT(mainThread);
+
+ if (!sCurrentThreadTLS.init()) {
+ MOZ_CRASH();
+ }
+ sMainThread = new XPCOMThreadWrapper(mainThread.get(),
+ /* aRequireTailDispatch = */ true,
+ true /* onThread */);
+}
+
+void AbstractThread::ShutdownMainThread() {
+ MOZ_ASSERT(NS_IsMainThread());
+ sMainThread = nullptr;
+}
+
+void AbstractThread::DispatchStateChange(
+ already_AddRefed<nsIRunnable> aRunnable) {
+ AbstractThread* currentThread = GetCurrent();
+ MOZ_DIAGNOSTIC_ASSERT(currentThread, "An AbstractThread must exist");
+ if (currentThread->IsTailDispatcherAvailable()) {
+ currentThread->TailDispatcher().AddStateChangeTask(this,
+ std::move(aRunnable));
+ } else {
+ // If the tail dispatcher isn't available, we just avoid sending state
+ // updates.
+ //
+ // This happens, specifically (1) During async shutdown (via the media
+ // shutdown blocker), and (2) During the final shutdown cycle collection.
+ // Both of these trigger changes to various watched and mirrored state.
+ nsCOMPtr<nsIRunnable> neverDispatched = aRunnable;
+ }
+}
+
+/* static */
+void AbstractThread::DispatchDirectTask(
+ already_AddRefed<nsIRunnable> aRunnable) {
+ AbstractThread* currentThread = GetCurrent();
+ MOZ_DIAGNOSTIC_ASSERT(currentThread, "An AbstractThread must exist");
+ if (currentThread->IsTailDispatcherAvailable()) {
+ currentThread->TailDispatcher().AddDirectTask(std::move(aRunnable));
+ } else {
+ // If the tail dispatcher isn't available, we post as a regular task.
+ currentThread->Dispatch(std::move(aRunnable));
+ }
+}
+
+/* static */
+already_AddRefed<AbstractThread> AbstractThread::CreateXPCOMThreadWrapper(
+ nsIThread* aThread, bool aRequireTailDispatch, bool aOnThread) {
+ nsCOMPtr<nsIThreadInternal> internalThread = do_QueryInterface(aThread);
+ MOZ_ASSERT(internalThread, "Need an nsThread for AbstractThread");
+ RefPtr<XPCOMThreadWrapper> wrapper =
+ new XPCOMThreadWrapper(internalThread, aRequireTailDispatch, aOnThread);
+
+ bool onCurrentThread = false;
+ Unused << aThread->IsOnCurrentThread(&onCurrentThread);
+
+ if (onCurrentThread) {
+ if (!aOnThread) {
+ MOZ_ASSERT(!sCurrentThreadTLS.get(),
+ "There can only be a single XPCOMThreadWrapper available on a "
+ "thread");
+ sCurrentThreadTLS.set(wrapper);
+ }
+ return wrapper.forget();
+ }
+
+ // Set the thread-local sCurrentThreadTLS to point to the wrapper on the
+ // target thread. This ensures that sCurrentThreadTLS is as expected by
+ // AbstractThread::GetCurrent() on the target thread.
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ "AbstractThread::CreateXPCOMThreadWrapper", [wrapper]() {
+ MOZ_ASSERT(!sCurrentThreadTLS.get(),
+ "There can only be a single XPCOMThreadWrapper available on "
+ "a thread");
+ sCurrentThreadTLS.set(wrapper);
+ });
+ aThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+ return wrapper.forget();
+}
+} // namespace mozilla
diff --git a/xpcom/threads/AbstractThread.h b/xpcom/threads/AbstractThread.h
new file mode 100644
index 0000000000..ef339986dd
--- /dev/null
+++ b/xpcom/threads/AbstractThread.h
@@ -0,0 +1,134 @@
+/* -*- 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/. */
+
+#if !defined(AbstractThread_h_)
+# define AbstractThread_h_
+
+# include "mozilla/AlreadyAddRefed.h"
+# include "mozilla/ThreadLocal.h"
+# include "nscore.h"
+# include "nsISerialEventTarget.h"
+# include "nsISupports.h"
+
+class nsIEventTarget;
+class nsIRunnable;
+class nsIThread;
+
+namespace mozilla {
+
+class TaskDispatcher;
+
+/*
+ * We often want to run tasks on a target that guarantees that events will never
+ * run in parallel. There are various target types that achieve this - namely
+ * nsIThread and TaskQueue. Note that nsIThreadPool (which implements
+ * nsIEventTarget) does not have this property, so we do not want to use
+ * nsIEventTarget for this purpose. This class encapsulates the specifics of
+ * the structures we might use here and provides a consistent interface.
+ *
+ * At present, the supported AbstractThread implementations are TaskQueue,
+ * AbstractThread::MainThread() and XPCOMThreadWrapper which can wrap any
+ * nsThread.
+ *
+ * The primary use of XPCOMThreadWrapper is to allow any threads to provide
+ * Direct Task dispatching which is similar (but not identical to) the microtask
+ * semantics of JS promises. Instantiating a XPCOMThreadWrapper on the current
+ * nsThread is sufficient to enable direct task dispatching.
+ *
+ * You shouldn't use pointers when comparing AbstractThread or nsIThread to
+ * determine if you are currently on the thread, but instead use the
+ * nsISerialEventTarget::IsOnCurrentThread() method.
+ */
+class AbstractThread : public nsISerialEventTarget {
+ public:
+ // Returns the AbstractThread that the caller is currently running in, or null
+ // if the caller is not running in an AbstractThread.
+ static AbstractThread* GetCurrent() { return sCurrentThreadTLS.get(); }
+
+ AbstractThread(bool aSupportsTailDispatch)
+ : mSupportsTailDispatch(aSupportsTailDispatch) {}
+
+ // Returns an AbstractThread wrapper of a nsIThread.
+ static already_AddRefed<AbstractThread> CreateXPCOMThreadWrapper(
+ nsIThread* aThread, bool aRequireTailDispatch, bool aOnThread = false);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // We don't use NS_DECL_NSIEVENTTARGET so that we can remove the default
+ // |flags| parameter from Dispatch. Otherwise, a single-argument Dispatch call
+ // would be ambiguous.
+ NS_IMETHOD_(bool) IsOnCurrentThreadInfallible(void) override;
+ NS_IMETHOD IsOnCurrentThread(bool* _retval) override;
+ NS_IMETHOD Dispatch(already_AddRefed<nsIRunnable> event,
+ uint32_t flags) override;
+ NS_IMETHOD DispatchFromScript(nsIRunnable* event, uint32_t flags) override;
+ NS_IMETHOD DelayedDispatch(already_AddRefed<nsIRunnable> event,
+ uint32_t delay) override;
+
+ enum DispatchReason { NormalDispatch, TailDispatch };
+ virtual nsresult Dispatch(already_AddRefed<nsIRunnable> aRunnable,
+ DispatchReason aReason = NormalDispatch) = 0;
+
+ virtual bool IsCurrentThreadIn() const = 0;
+
+ // Returns a TaskDispatcher that will dispatch its tasks when the currently-
+ // running tasks pops off the stack.
+ //
+ // May only be called when running within the it is invoked up, and only on
+ // threads which support it.
+ virtual TaskDispatcher& TailDispatcher() = 0;
+
+ // Returns true if we have tail tasks scheduled, or if this isn't known.
+ // Returns false if we definitely don't have any tail tasks.
+ virtual bool MightHaveTailTasks() { return true; }
+
+ // Returns true if the tail dispatcher is available. In certain edge cases
+ // like shutdown, it might not be.
+ virtual bool IsTailDispatcherAvailable() { return true; }
+
+ // Helper functions for methods on the tail TasklDispatcher. These check
+ // HasTailTasks to avoid allocating a TailDispatcher if it isn't
+ // needed.
+ nsresult TailDispatchTasksFor(AbstractThread* aThread);
+ bool HasTailTasksFor(AbstractThread* aThread);
+
+ // Returns true if this supports the tail dispatcher.
+ bool SupportsTailDispatch() const { return mSupportsTailDispatch; }
+
+ // Returns true if this thread requires all dispatches originating from
+ // aThread go through the tail dispatcher.
+ bool RequiresTailDispatch(AbstractThread* aThread) const;
+ bool RequiresTailDispatchFromCurrentThread() const;
+
+ virtual nsIEventTarget* AsEventTarget() { MOZ_CRASH("Not an event target!"); }
+
+ // Returns the non-DocGroup version of AbstractThread on the main thread.
+ // A DocGroup-versioned one is available in
+ // DispatcherTrait::AbstractThreadFor(). Note:
+ // DispatcherTrait::AbstractThreadFor() SHALL be used when possible.
+ static AbstractThread* MainThread();
+
+ // Must be called exactly once during startup.
+ static void InitTLS();
+ static void InitMainThread();
+ static void ShutdownMainThread();
+
+ void DispatchStateChange(already_AddRefed<nsIRunnable> aRunnable);
+
+ static void DispatchDirectTask(already_AddRefed<nsIRunnable> aRunnable);
+
+ protected:
+ virtual ~AbstractThread() = default;
+ static MOZ_THREAD_LOCAL(AbstractThread*) sCurrentThreadTLS;
+
+ // True if we want to require that every task dispatched from tasks running in
+ // this queue go through our queue's tail dispatcher.
+ const bool mSupportsTailDispatch;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/xpcom/threads/BlockingResourceBase.cpp b/xpcom/threads/BlockingResourceBase.cpp
new file mode 100644
index 0000000000..218f0cf4b1
--- /dev/null
+++ b/xpcom/threads/BlockingResourceBase.cpp
@@ -0,0 +1,542 @@
+/* -*- 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/BlockingResourceBase.h"
+
+#ifdef DEBUG
+# include "prthread.h"
+
+# ifndef MOZ_CALLSTACK_DISABLED
+# include "CodeAddressService.h"
+# include "nsHashKeys.h"
+# include "mozilla/StackWalk.h"
+# include "nsTHashtable.h"
+# endif
+
+# include "mozilla/CondVar.h"
+# include "mozilla/DeadlockDetector.h"
+# include "mozilla/RecursiveMutex.h"
+# include "mozilla/ReentrantMonitor.h"
+# include "mozilla/Mutex.h"
+# include "mozilla/RWLock.h"
+# include "mozilla/UniquePtr.h"
+
+# if defined(MOZILLA_INTERNAL_API)
+# include "GeckoProfiler.h"
+# endif // MOZILLA_INTERNAL_API
+
+#endif // ifdef DEBUG
+
+namespace mozilla {
+//
+// BlockingResourceBase implementation
+//
+
+// static members
+const char* const BlockingResourceBase::kResourceTypeName[] = {
+ // needs to be kept in sync with BlockingResourceType
+ "Mutex", "ReentrantMonitor", "CondVar", "RecursiveMutex"};
+
+#ifdef DEBUG
+
+PRCallOnceType BlockingResourceBase::sCallOnce;
+MOZ_THREAD_LOCAL(BlockingResourceBase*)
+BlockingResourceBase::sResourceAcqnChainFront;
+BlockingResourceBase::DDT* BlockingResourceBase::sDeadlockDetector;
+
+void BlockingResourceBase::StackWalkCallback(uint32_t aFrameNumber, void* aPc,
+ void* aSp, void* aClosure) {
+# ifndef MOZ_CALLSTACK_DISABLED
+ AcquisitionState* state = (AcquisitionState*)aClosure;
+ state->ref().AppendElement(aPc);
+# endif
+}
+
+void BlockingResourceBase::GetStackTrace(AcquisitionState& aState) {
+# ifndef MOZ_CALLSTACK_DISABLED
+ // Skip this function and the calling function.
+ const uint32_t kSkipFrames = 2;
+
+ // Clear the array...
+ aState.reset();
+ // ...and create a new one; this also puts the state to 'acquired' status
+ // regardless of whether we obtain a stack trace or not.
+ aState.emplace();
+
+ // NB: Ignore the return value, there's nothing useful we can do if this
+ // this fails.
+ MozStackWalk(StackWalkCallback, kSkipFrames, kAcquisitionStateStackSize,
+ aState.ptr());
+# endif
+}
+
+/**
+ * PrintCycle
+ * Append to |aOut| detailed information about the circular
+ * dependency in |aCycle|. Returns true if it *appears* that this
+ * cycle may represent an imminent deadlock, but this is merely a
+ * heuristic; the value returned may be a false positive or false
+ * negative.
+ *
+ * *NOT* thread safe. Calls |Print()|.
+ *
+ * FIXME bug 456272 hack alert: because we can't write call
+ * contexts into strings, all info is written to stderr, but only
+ * some info is written into |aOut|
+ */
+static bool PrintCycle(
+ const BlockingResourceBase::DDT::ResourceAcquisitionArray& aCycle,
+ nsACString& aOut) {
+ NS_ASSERTION(aCycle.Length() > 1, "need > 1 element for cycle!");
+
+ bool maybeImminent = true;
+
+ fputs("=== Cyclical dependency starts at\n", stderr);
+ aOut += "Cyclical dependency starts at\n";
+
+ const BlockingResourceBase::DDT::ResourceAcquisitionArray::elem_type res =
+ aCycle.ElementAt(0);
+ maybeImminent &= res->Print(aOut);
+
+ BlockingResourceBase::DDT::ResourceAcquisitionArray::index_type i;
+ BlockingResourceBase::DDT::ResourceAcquisitionArray::size_type len =
+ aCycle.Length();
+ const BlockingResourceBase::DDT::ResourceAcquisitionArray::elem_type* it =
+ 1 + aCycle.Elements();
+ for (i = 1; i < len - 1; ++i, ++it) {
+ fputs("\n--- Next dependency:\n", stderr);
+ aOut += "\nNext dependency:\n";
+
+ maybeImminent &= (*it)->Print(aOut);
+ }
+
+ fputs("\n=== Cycle completed at\n", stderr);
+ aOut += "Cycle completed at\n";
+ (*it)->Print(aOut);
+
+ return maybeImminent;
+}
+
+bool BlockingResourceBase::Print(nsACString& aOut) const {
+ fprintf(stderr, "--- %s : %s", kResourceTypeName[mType], mName);
+ aOut += BlockingResourceBase::kResourceTypeName[mType];
+ aOut += " : ";
+ aOut += mName;
+
+ bool acquired = IsAcquired();
+
+ if (acquired) {
+ fputs(" (currently acquired)\n", stderr);
+ aOut += " (currently acquired)\n";
+ }
+
+ fputs(" calling context\n", stderr);
+# ifdef MOZ_CALLSTACK_DISABLED
+ fputs(" [stack trace unavailable]\n", stderr);
+# else
+ const AcquisitionState& state = acquired ? mAcquired : mFirstSeen;
+
+ CodeAddressService<> addressService;
+
+ for (uint32_t i = 0; i < state.ref().Length(); i++) {
+ const size_t kMaxLength = 1024;
+ char buffer[kMaxLength];
+ addressService.GetLocation(i + 1, state.ref()[i], buffer, kMaxLength);
+ const char* fmt = " %s\n";
+ aOut.AppendLiteral(" ");
+ aOut.Append(buffer);
+ aOut.AppendLiteral("\n");
+ fprintf(stderr, fmt, buffer);
+ }
+
+# endif
+
+ return acquired;
+}
+
+BlockingResourceBase::BlockingResourceBase(
+ const char* aName, BlockingResourceBase::BlockingResourceType aType)
+ : mName(aName),
+ mType(aType)
+# ifdef MOZ_CALLSTACK_DISABLED
+ ,
+ mAcquired(false)
+# else
+ ,
+ mAcquired()
+# endif
+{
+ MOZ_ASSERT(mName, "Name must be nonnull");
+ // PR_CallOnce guaranatees that InitStatics is called in a
+ // thread-safe way
+ if (PR_SUCCESS != PR_CallOnce(&sCallOnce, InitStatics)) {
+ MOZ_CRASH("can't initialize blocking resource static members");
+ }
+
+ mChainPrev = 0;
+ sDeadlockDetector->Add(this);
+}
+
+BlockingResourceBase::~BlockingResourceBase() {
+ // we don't check for really obviously bad things like freeing
+ // Mutexes while they're still locked. it is assumed that the
+ // base class, or its underlying primitive, will check for such
+ // stupid mistakes.
+ mChainPrev = 0; // racy only for stupidly buggy client code
+ if (sDeadlockDetector) {
+ sDeadlockDetector->Remove(this);
+ }
+}
+
+size_t BlockingResourceBase::SizeOfDeadlockDetector(
+ MallocSizeOf aMallocSizeOf) {
+ return sDeadlockDetector
+ ? sDeadlockDetector->SizeOfIncludingThis(aMallocSizeOf)
+ : 0;
+}
+
+PRStatus BlockingResourceBase::InitStatics() {
+ MOZ_ASSERT(sResourceAcqnChainFront.init());
+ sDeadlockDetector = new DDT();
+ if (!sDeadlockDetector) {
+ MOZ_CRASH("can't allocate deadlock detector");
+ }
+ return PR_SUCCESS;
+}
+
+void BlockingResourceBase::Shutdown() {
+ delete sDeadlockDetector;
+ sDeadlockDetector = 0;
+}
+
+void BlockingResourceBase::CheckAcquire() {
+ if (mType == eCondVar) {
+ MOZ_ASSERT_UNREACHABLE(
+ "FIXME bug 456272: annots. to allow CheckAcquire()ing condvars");
+ return;
+ }
+
+ BlockingResourceBase* chainFront = ResourceChainFront();
+ mozilla::UniquePtr<DDT::ResourceAcquisitionArray> cycle(
+ sDeadlockDetector->CheckAcquisition(chainFront ? chainFront : 0, this));
+ if (!cycle) {
+ return;
+ }
+
+# ifndef MOZ_CALLSTACK_DISABLED
+ // Update the current stack before printing.
+ GetStackTrace(mAcquired);
+# endif
+
+ fputs("###!!! ERROR: Potential deadlock detected:\n", stderr);
+ nsAutoCString out("Potential deadlock detected:\n");
+ bool maybeImminent = PrintCycle(*cycle, out);
+
+ if (maybeImminent) {
+ fputs("\n###!!! Deadlock may happen NOW!\n\n", stderr);
+ out.AppendLiteral("\n###!!! Deadlock may happen NOW!\n\n");
+ } else {
+ fputs("\nDeadlock may happen for some other execution\n\n", stderr);
+ out.AppendLiteral("\nDeadlock may happen for some other execution\n\n");
+ }
+
+ // Only error out if we think a deadlock is imminent.
+ if (maybeImminent) {
+ NS_ERROR(out.get());
+ } else {
+ NS_WARNING(out.get());
+ }
+}
+
+void BlockingResourceBase::Acquire() {
+ if (mType == eCondVar) {
+ MOZ_ASSERT_UNREACHABLE(
+ "FIXME bug 456272: annots. to allow Acquire()ing condvars");
+ return;
+ }
+ NS_ASSERTION(!IsAcquired(), "reacquiring already acquired resource");
+
+ ResourceChainAppend(ResourceChainFront());
+
+# ifdef MOZ_CALLSTACK_DISABLED
+ mAcquired = true;
+# else
+ // Take a stack snapshot.
+ GetStackTrace(mAcquired);
+ MOZ_ASSERT(IsAcquired());
+
+ if (!mFirstSeen) {
+ mFirstSeen = mAcquired;
+ }
+# endif
+}
+
+void BlockingResourceBase::Release() {
+ if (mType == eCondVar) {
+ MOZ_ASSERT_UNREACHABLE(
+ "FIXME bug 456272: annots. to allow Release()ing condvars");
+ return;
+ }
+
+ BlockingResourceBase* chainFront = ResourceChainFront();
+ NS_ASSERTION(chainFront && IsAcquired(),
+ "Release()ing something that hasn't been Acquire()ed");
+
+ if (chainFront == this) {
+ ResourceChainRemove();
+ } else {
+ // not an error, but makes code hard to reason about.
+ NS_WARNING("Resource acquired is being released in non-LIFO order; why?\n");
+ nsCString tmp;
+ Print(tmp);
+
+ // remove this resource from wherever it lives in the chain
+ // we walk backwards in order of acquisition:
+ // (1) ...node<-prev<-curr...
+ // / /
+ // (2) ...prev<-curr...
+ BlockingResourceBase* curr = chainFront;
+ BlockingResourceBase* prev = nullptr;
+ while (curr && (prev = curr->mChainPrev) && (prev != this)) {
+ curr = prev;
+ }
+ if (prev == this) {
+ curr->mChainPrev = prev->mChainPrev;
+ }
+ }
+
+ ClearAcquisitionState();
+}
+
+//
+// Debug implementation of (OffTheBooks)Mutex
+void OffTheBooksMutex::Lock() {
+ CheckAcquire();
+ this->lock();
+ mOwningThread = PR_GetCurrentThread();
+ Acquire();
+}
+
+bool OffTheBooksMutex::TryLock() {
+ CheckAcquire();
+ bool locked = this->tryLock();
+ if (locked) {
+ mOwningThread = PR_GetCurrentThread();
+ Acquire();
+ }
+ return locked;
+}
+
+void OffTheBooksMutex::Unlock() {
+ Release();
+ mOwningThread = nullptr;
+ this->unlock();
+}
+
+void OffTheBooksMutex::AssertCurrentThreadOwns() const {
+ MOZ_ASSERT(IsAcquired() && mOwningThread == PR_GetCurrentThread());
+}
+
+//
+// Debug implementation of RWLock
+//
+
+void RWLock::ReadLock() {
+ // All we want to ensure here is that we're not attempting to acquire the
+ // read lock while this thread is holding the write lock.
+ CheckAcquire();
+ this->ReadLockInternal();
+ MOZ_ASSERT(mOwningThread == nullptr);
+}
+
+void RWLock::ReadUnlock() {
+ MOZ_ASSERT(mOwningThread == nullptr);
+ this->ReadUnlockInternal();
+}
+
+void RWLock::WriteLock() {
+ CheckAcquire();
+ this->WriteLockInternal();
+ mOwningThread = PR_GetCurrentThread();
+ Acquire();
+}
+
+void RWLock::WriteUnlock() {
+ Release();
+ mOwningThread = nullptr;
+ this->WriteUnlockInternal();
+}
+
+//
+// Debug implementation of ReentrantMonitor
+void ReentrantMonitor::Enter() {
+ BlockingResourceBase* chainFront = ResourceChainFront();
+
+ // the code below implements monitor reentrancy semantics
+
+ if (this == chainFront) {
+ // immediately re-entered the monitor: acceptable
+ PR_EnterMonitor(mReentrantMonitor);
+ ++mEntryCount;
+ return;
+ }
+
+ // this is sort of a hack around not recording the thread that
+ // owns this monitor
+ if (chainFront) {
+ for (BlockingResourceBase* br = ResourceChainPrev(chainFront); br;
+ br = ResourceChainPrev(br)) {
+ if (br == this) {
+ NS_WARNING(
+ "Re-entering ReentrantMonitor after acquiring other resources.");
+
+ // show the caller why this is potentially bad
+ CheckAcquire();
+
+ PR_EnterMonitor(mReentrantMonitor);
+ ++mEntryCount;
+ return;
+ }
+ }
+ }
+
+ CheckAcquire();
+ PR_EnterMonitor(mReentrantMonitor);
+ NS_ASSERTION(mEntryCount == 0, "ReentrantMonitor isn't free!");
+ Acquire(); // protected by mReentrantMonitor
+ mEntryCount = 1;
+}
+
+void ReentrantMonitor::Exit() {
+ if (--mEntryCount == 0) {
+ Release(); // protected by mReentrantMonitor
+ }
+ PRStatus status = PR_ExitMonitor(mReentrantMonitor);
+ NS_ASSERTION(PR_SUCCESS == status, "bad ReentrantMonitor::Exit()");
+}
+
+nsresult ReentrantMonitor::Wait(PRIntervalTime aInterval) {
+ AssertCurrentThreadIn();
+
+ // save monitor state and reset it to empty
+ int32_t savedEntryCount = mEntryCount;
+ AcquisitionState savedAcquisitionState = GetAcquisitionState();
+ BlockingResourceBase* savedChainPrev = mChainPrev;
+ mEntryCount = 0;
+ ClearAcquisitionState();
+ mChainPrev = 0;
+
+ nsresult rv;
+ {
+# if defined(MOZILLA_INTERNAL_API)
+ AUTO_PROFILER_THREAD_SLEEP;
+# endif
+ // give up the monitor until we're back from Wait()
+ rv = PR_Wait(mReentrantMonitor, aInterval) == PR_SUCCESS ? NS_OK
+ : NS_ERROR_FAILURE;
+ }
+
+ // restore saved state
+ mEntryCount = savedEntryCount;
+ SetAcquisitionState(savedAcquisitionState);
+ mChainPrev = savedChainPrev;
+
+ return rv;
+}
+
+//
+// Debug implementation of RecursiveMutex
+void RecursiveMutex::Lock() {
+ BlockingResourceBase* chainFront = ResourceChainFront();
+
+ // the code below implements mutex reentrancy semantics
+
+ if (this == chainFront) {
+ // immediately re-entered the mutex: acceptable
+ LockInternal();
+ ++mEntryCount;
+ return;
+ }
+
+ // this is sort of a hack around not recording the thread that
+ // owns this monitor
+ if (chainFront) {
+ for (BlockingResourceBase* br = ResourceChainPrev(chainFront); br;
+ br = ResourceChainPrev(br)) {
+ if (br == this) {
+ NS_WARNING(
+ "Re-entering RecursiveMutex after acquiring other resources.");
+
+ // show the caller why this is potentially bad
+ CheckAcquire();
+
+ LockInternal();
+ ++mEntryCount;
+ return;
+ }
+ }
+ }
+
+ CheckAcquire();
+ LockInternal();
+ NS_ASSERTION(mEntryCount == 0, "RecursiveMutex isn't free!");
+ Acquire(); // protected by us
+ mOwningThread = PR_GetCurrentThread();
+ mEntryCount = 1;
+}
+
+void RecursiveMutex::Unlock() {
+ if (--mEntryCount == 0) {
+ Release(); // protected by us
+ mOwningThread = nullptr;
+ }
+ UnlockInternal();
+}
+
+void RecursiveMutex::AssertCurrentThreadIn() {
+ MOZ_ASSERT(IsAcquired() && mOwningThread == PR_GetCurrentThread());
+}
+
+//
+// Debug implementation of CondVar
+void OffTheBooksCondVar::Wait() {
+ // Forward to the timed version of OffTheBooksCondVar::Wait to avoid code
+ // duplication.
+ CVStatus status = Wait(TimeDuration::Forever());
+ MOZ_ASSERT(status == CVStatus::NoTimeout);
+}
+
+CVStatus OffTheBooksCondVar::Wait(TimeDuration aDuration) {
+ AssertCurrentThreadOwnsMutex();
+
+ // save mutex state and reset to empty
+ AcquisitionState savedAcquisitionState = mLock->GetAcquisitionState();
+ BlockingResourceBase* savedChainPrev = mLock->mChainPrev;
+ PRThread* savedOwningThread = mLock->mOwningThread;
+ mLock->ClearAcquisitionState();
+ mLock->mChainPrev = 0;
+ mLock->mOwningThread = nullptr;
+
+ // give up mutex until we're back from Wait()
+ CVStatus status;
+ {
+# if defined(MOZILLA_INTERNAL_API)
+ AUTO_PROFILER_THREAD_SLEEP;
+# endif
+ status = mImpl.wait_for(*mLock, aDuration);
+ }
+
+ // restore saved state
+ mLock->SetAcquisitionState(savedAcquisitionState);
+ mLock->mChainPrev = savedChainPrev;
+ mLock->mOwningThread = savedOwningThread;
+
+ return status;
+}
+
+#endif // ifdef DEBUG
+
+} // namespace mozilla
diff --git a/xpcom/threads/BlockingResourceBase.h b/xpcom/threads/BlockingResourceBase.h
new file mode 100644
index 0000000000..b5501599f1
--- /dev/null
+++ b/xpcom/threads/BlockingResourceBase.h
@@ -0,0 +1,328 @@
+/* -*- 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_BlockingResourceBase_h
+#define mozilla_BlockingResourceBase_h
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/ThreadLocal.h"
+
+#include "nscore.h"
+#include "nsDebug.h"
+
+#include "prtypes.h"
+
+#ifdef DEBUG
+
+// NB: Comment this out to enable callstack tracking.
+# define MOZ_CALLSTACK_DISABLED
+
+# include "prinit.h"
+
+# ifndef MOZ_CALLSTACK_DISABLED
+# include "mozilla/Maybe.h"
+# include "nsTArray.h"
+# endif
+
+#endif
+
+//
+// This header is not meant to be included by client code.
+//
+
+namespace mozilla {
+
+#ifdef DEBUG
+template <class T>
+class DeadlockDetector;
+#endif
+
+/**
+ * BlockingResourceBase
+ * Base class of resources that might block clients trying to acquire them.
+ * Does debugging and deadlock detection in DEBUG builds.
+ **/
+class BlockingResourceBase {
+ public:
+ // Needs to be kept in sync with kResourceTypeNames.
+ enum BlockingResourceType {
+ eMutex,
+ eReentrantMonitor,
+ eCondVar,
+ eRecursiveMutex
+ };
+
+ /**
+ * kResourceTypeName
+ * Human-readable version of BlockingResourceType enum.
+ */
+ static const char* const kResourceTypeName[];
+
+#ifdef DEBUG
+
+ static size_t SizeOfDeadlockDetector(MallocSizeOf aMallocSizeOf);
+
+ /**
+ * Print
+ * Write a description of this blocking resource to |aOut|. If
+ * the resource appears to be currently acquired, the current
+ * acquisition context is printed and true is returned.
+ * Otherwise, we print the context from |aFirstSeen|, the
+ * first acquisition from which the code calling |Print()|
+ * became interested in us, and return false.
+ *
+ * *NOT* thread safe. Reads |mAcquisitionContext| without
+ * synchronization, but this will not cause correctness
+ * problems.
+ *
+ * FIXME bug 456272: hack alert: because we can't write call
+ * contexts into strings, all info is written to stderr, but
+ * only some info is written into |aOut|
+ */
+ bool Print(nsACString& aOut) const;
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ // NB: |mName| is not reported as it's expected to be a static string.
+ // If we switch to a nsString it should be added to the tally.
+ // |mChainPrev| is not reported because its memory is not owned.
+ size_t n = aMallocSizeOf(this);
+ return n;
+ }
+
+ // ``DDT'' = ``Deadlock Detector Type''
+ typedef DeadlockDetector<BlockingResourceBase> DDT;
+
+ protected:
+# ifdef MOZ_CALLSTACK_DISABLED
+ typedef bool AcquisitionState;
+# else
+ // Using maybe to use emplacement as the acquisition state flag; we may not
+ // always get a stack trace because of possible stack walk suppression or
+ // errors, hence can't use !IsEmpty() on the array itself as indication.
+ static size_t const kAcquisitionStateStackSize = 24;
+ typedef Maybe<AutoTArray<void*, kAcquisitionStateStackSize> >
+ AcquisitionState;
+# endif
+
+ /**
+ * BlockingResourceBase
+ * Initialize this blocking resource. Also hooks the resource into
+ * instrumentation code.
+ *
+ * Thread safe.
+ *
+ * @param aName A meaningful, unique name that can be used in
+ * error messages, et al.
+ * @param aType The specific type of |this|, if any.
+ **/
+ BlockingResourceBase(const char* aName, BlockingResourceType aType);
+
+ ~BlockingResourceBase();
+
+ /**
+ * CheckAcquire
+ *
+ * Thread safe.
+ **/
+ void CheckAcquire();
+
+ /**
+ * Acquire
+ *
+ * *NOT* thread safe. Requires ownership of underlying resource.
+ **/
+ void Acquire(); // NS_NEEDS_RESOURCE(this)
+
+ /**
+ * Release
+ * Remove this resource from the current thread's acquisition chain.
+ * The resource does not have to be at the front of the chain, although
+ * it is confusing to release resources in a different order than they
+ * are acquired. This generates a warning.
+ *
+ * *NOT* thread safe. Requires ownership of underlying resource.
+ **/
+ void Release(); // NS_NEEDS_RESOURCE(this)
+
+ /**
+ * ResourceChainFront
+ *
+ * Thread safe.
+ *
+ * @return the front of the resource acquisition chain, i.e., the last
+ * resource acquired.
+ */
+ static BlockingResourceBase* ResourceChainFront() {
+ return sResourceAcqnChainFront.get();
+ }
+
+ /**
+ * ResourceChainPrev
+ *
+ * *NOT* thread safe. Requires ownership of underlying resource.
+ */
+ static BlockingResourceBase* ResourceChainPrev(
+ const BlockingResourceBase* aResource) {
+ return aResource->mChainPrev;
+ } // NS_NEEDS_RESOURCE(this)
+
+ /**
+ * ResourceChainAppend
+ * Set |this| to the front of the resource acquisition chain, and link
+ * |this| to |aPrev|.
+ *
+ * *NOT* thread safe. Requires ownership of underlying resource.
+ */
+ void ResourceChainAppend(BlockingResourceBase* aPrev) {
+ mChainPrev = aPrev;
+ sResourceAcqnChainFront.set(this);
+ } // NS_NEEDS_RESOURCE(this)
+
+ /**
+ * ResourceChainRemove
+ * Remove |this| from the front of the resource acquisition chain.
+ *
+ * *NOT* thread safe. Requires ownership of underlying resource.
+ */
+ void ResourceChainRemove() {
+ NS_ASSERTION(this == ResourceChainFront(), "not at chain front");
+ sResourceAcqnChainFront.set(mChainPrev);
+ } // NS_NEEDS_RESOURCE(this)
+
+ /**
+ * GetAcquisitionState
+ * Return whether or not this resource was acquired.
+ *
+ * *NOT* thread safe. Requires ownership of underlying resource.
+ */
+ AcquisitionState GetAcquisitionState() { return mAcquired; }
+
+ /**
+ * SetAcquisitionState
+ * Set whether or not this resource was acquired.
+ *
+ * *NOT* thread safe. Requires ownership of underlying resource.
+ */
+ void SetAcquisitionState(const AcquisitionState& aAcquisitionState) {
+ mAcquired = aAcquisitionState;
+ }
+
+ /**
+ * ClearAcquisitionState
+ * Indicate this resource is not acquired.
+ *
+ * *NOT* thread safe. Requires ownership of underlying resource.
+ */
+ void ClearAcquisitionState() {
+# ifdef MOZ_CALLSTACK_DISABLED
+ mAcquired = false;
+# else
+ mAcquired.reset();
+# endif
+ }
+
+ /**
+ * IsAcquired
+ * Indicates if this resource is acquired.
+ *
+ * *NOT* thread safe. Requires ownership of underlying resource.
+ */
+ bool IsAcquired() const { return (bool)mAcquired; }
+
+ /**
+ * mChainPrev
+ * A series of resource acquisitions creates a chain of orders. This
+ * chain is implemented as a linked list; |mChainPrev| points to the
+ * resource most recently Acquire()'d before this one.
+ **/
+ BlockingResourceBase* mChainPrev;
+
+ private:
+ /**
+ * mName
+ * A descriptive name for this resource. Used in error
+ * messages etc.
+ */
+ const char* mName;
+
+ /**
+ * mType
+ * The more specific type of this resource. Used to implement
+ * special semantics (e.g., reentrancy of monitors).
+ **/
+ BlockingResourceType mType;
+
+ /**
+ * mAcquired
+ * Indicates if this resource is currently acquired.
+ */
+ AcquisitionState mAcquired;
+
+# ifndef MOZ_CALLSTACK_DISABLED
+ /**
+ * mFirstSeen
+ * Inidicates where this resource was first acquired.
+ */
+ AcquisitionState mFirstSeen;
+# endif
+
+ /**
+ * sCallOnce
+ * Ensures static members are initialized only once, and in a
+ * thread-safe way.
+ */
+ static PRCallOnceType sCallOnce;
+
+ /**
+ * Thread-private pointer to the front of each thread's resource
+ * acquisition chain.
+ */
+ static MOZ_THREAD_LOCAL(BlockingResourceBase*) sResourceAcqnChainFront;
+
+ /**
+ * sDeadlockDetector
+ * Does as named.
+ */
+ static DDT* sDeadlockDetector;
+
+ /**
+ * InitStatics
+ * Inititialize static members of BlockingResourceBase that can't
+ * be statically initialized.
+ *
+ * *NOT* thread safe.
+ */
+ static PRStatus InitStatics();
+
+ /**
+ * Shutdown
+ * Free static members.
+ *
+ * *NOT* thread safe.
+ */
+ static void Shutdown();
+
+ static void StackWalkCallback(uint32_t aFrameNumber, void* aPc, void* aSp,
+ void* aClosure);
+ static void GetStackTrace(AcquisitionState& aState);
+
+# ifdef MOZILLA_INTERNAL_API
+ // so it can call BlockingResourceBase::Shutdown()
+ friend void LogTerm();
+# endif // ifdef MOZILLA_INTERNAL_API
+
+#else // non-DEBUG implementation
+
+ BlockingResourceBase(const char* aName, BlockingResourceType aType) {}
+
+ ~BlockingResourceBase() {}
+
+#endif
+};
+
+} // namespace mozilla
+
+#endif // mozilla_BlockingResourceBase_h
diff --git a/xpcom/threads/CPUUsageWatcher.cpp b/xpcom/threads/CPUUsageWatcher.cpp
new file mode 100644
index 0000000000..03d213ebc9
--- /dev/null
+++ b/xpcom/threads/CPUUsageWatcher.cpp
@@ -0,0 +1,256 @@
+/* -*- 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/CPUUsageWatcher.h"
+
+#include "prsystem.h"
+
+#ifdef XP_MACOSX
+# include <sys/resource.h>
+# include <mach/clock.h>
+# include <mach/mach_host.h>
+#endif
+
+#ifdef CPU_USAGE_WATCHER_ACTIVE
+# include "mozilla/BackgroundHangMonitor.h"
+#endif
+
+namespace mozilla {
+
+#ifdef CPU_USAGE_WATCHER_ACTIVE
+
+// Even if the machine only has one processor, tolerate up to 50%
+// external CPU usage.
+static const float kTolerableExternalCPUUsageFloor = 0.5f;
+
+struct CPUStats {
+ // The average CPU usage time, which can be summed across all cores in the
+ // system, or averaged between them. Whichever it is, it needs to be in the
+ // same units as updateTime.
+ uint64_t usageTime;
+ // A monotonically increasing value in the same units as usageTime, which can
+ // be used to determine the percentage of active vs idle time
+ uint64_t updateTime;
+};
+
+# ifdef XP_MACOSX
+
+static const uint64_t kMicrosecondsPerSecond = 1000000LL;
+static const uint64_t kNanosecondsPerMicrosecond = 1000LL;
+static const uint64_t kCPUCheckInterval = kMicrosecondsPerSecond / 2LL;
+
+static uint64_t GetMicroseconds(timeval time) {
+ return ((uint64_t)time.tv_sec) * kMicrosecondsPerSecond +
+ (uint64_t)time.tv_usec;
+}
+
+static uint64_t GetMicroseconds(mach_timespec_t time) {
+ return ((uint64_t)time.tv_sec) * kMicrosecondsPerSecond +
+ ((uint64_t)time.tv_nsec) / kNanosecondsPerMicrosecond;
+}
+
+static Result<CPUStats, CPUUsageWatcherError> GetProcessCPUStats(
+ int32_t numCPUs) {
+ CPUStats result = {};
+ rusage usage;
+ int32_t rusageResult = getrusage(RUSAGE_SELF, &usage);
+ if (rusageResult == -1) {
+ return Err(GetProcessTimesError);
+ }
+ result.usageTime =
+ GetMicroseconds(usage.ru_utime) + GetMicroseconds(usage.ru_stime);
+
+ clock_serv_t realtimeClock;
+ kern_return_t errorResult =
+ host_get_clock_service(mach_host_self(), REALTIME_CLOCK, &realtimeClock);
+ if (errorResult != KERN_SUCCESS) {
+ return Err(GetProcessTimesError);
+ }
+ mach_timespec_t time;
+ errorResult = clock_get_time(realtimeClock, &time);
+ if (errorResult != KERN_SUCCESS) {
+ return Err(GetProcessTimesError);
+ }
+ result.updateTime = GetMicroseconds(time);
+
+ // getrusage will give us the sum of the values across all
+ // of our cores. Divide by the number of CPUs to get an average.
+ result.usageTime /= numCPUs;
+ return result;
+}
+
+static Result<CPUStats, CPUUsageWatcherError> GetGlobalCPUStats() {
+ CPUStats result = {};
+ host_cpu_load_info_data_t loadInfo;
+ mach_msg_type_number_t loadInfoCount = HOST_CPU_LOAD_INFO_COUNT;
+ kern_return_t statsResult =
+ host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO,
+ (host_info_t)&loadInfo, &loadInfoCount);
+ if (statsResult != KERN_SUCCESS) {
+ return Err(HostStatisticsError);
+ }
+
+ result.usageTime = loadInfo.cpu_ticks[CPU_STATE_USER] +
+ loadInfo.cpu_ticks[CPU_STATE_NICE] +
+ loadInfo.cpu_ticks[CPU_STATE_SYSTEM];
+ result.updateTime = result.usageTime + loadInfo.cpu_ticks[CPU_STATE_IDLE];
+ return result;
+}
+
+# endif // XP_MACOSX
+
+# ifdef XP_WIN
+
+// A FILETIME represents the number of 100-nanosecond ticks since 1/1/1601 UTC
+static const uint64_t kFILETIMETicksPerSecond = 10000000;
+static const uint64_t kCPUCheckInterval = kFILETIMETicksPerSecond / 2;
+
+uint64_t FiletimeToInteger(FILETIME filetime) {
+ return ((uint64_t)filetime.dwLowDateTime) | (uint64_t)filetime.dwHighDateTime
+ << 32;
+}
+
+Result<CPUStats, CPUUsageWatcherError> GetProcessCPUStats(int32_t numCPUs) {
+ CPUStats result = {};
+ FILETIME creationFiletime;
+ FILETIME exitFiletime;
+ FILETIME kernelFiletime;
+ FILETIME userFiletime;
+ bool success = GetProcessTimes(GetCurrentProcess(), &creationFiletime,
+ &exitFiletime, &kernelFiletime, &userFiletime);
+ if (!success) {
+ return Err(GetProcessTimesError);
+ }
+
+ result.usageTime =
+ FiletimeToInteger(kernelFiletime) + FiletimeToInteger(userFiletime);
+
+ FILETIME nowFiletime;
+ GetSystemTimeAsFileTime(&nowFiletime);
+ result.updateTime = FiletimeToInteger(nowFiletime);
+
+ result.usageTime /= numCPUs;
+
+ return result;
+}
+
+Result<CPUStats, CPUUsageWatcherError> GetGlobalCPUStats() {
+ CPUStats result = {};
+ FILETIME idleFiletime;
+ FILETIME kernelFiletime;
+ FILETIME userFiletime;
+ bool success = GetSystemTimes(&idleFiletime, &kernelFiletime, &userFiletime);
+
+ if (!success) {
+ return Err(GetSystemTimesError);
+ }
+
+ result.usageTime =
+ FiletimeToInteger(kernelFiletime) + FiletimeToInteger(userFiletime);
+ result.updateTime = result.usageTime + FiletimeToInteger(idleFiletime);
+
+ return result;
+}
+
+# endif // XP_WIN
+
+Result<Ok, CPUUsageWatcherError> CPUUsageWatcher::Init() {
+ mNumCPUs = PR_GetNumberOfProcessors();
+ if (mNumCPUs <= 0) {
+ mExternalUsageThreshold = 1.0f;
+ return Err(GetNumberOfProcessorsError);
+ }
+ mExternalUsageThreshold =
+ std::max(1.0f - 1.0f / (float)mNumCPUs, kTolerableExternalCPUUsageFloor);
+
+ CPUStats processTimes;
+ MOZ_TRY_VAR(processTimes, GetProcessCPUStats(mNumCPUs));
+ mProcessUpdateTime = processTimes.updateTime;
+ mProcessUsageTime = processTimes.usageTime;
+
+ CPUStats globalTimes;
+ MOZ_TRY_VAR(globalTimes, GetGlobalCPUStats());
+ mGlobalUpdateTime = globalTimes.updateTime;
+ mGlobalUsageTime = globalTimes.usageTime;
+
+ mInitialized = true;
+
+ CPUUsageWatcher* self = this;
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "CPUUsageWatcher::Init",
+ [=]() { BackgroundHangMonitor::RegisterAnnotator(*self); }));
+
+ return Ok();
+}
+
+void CPUUsageWatcher::Uninit() {
+ if (mInitialized) {
+ BackgroundHangMonitor::UnregisterAnnotator(*this);
+ }
+ mInitialized = false;
+}
+
+Result<Ok, CPUUsageWatcherError> CPUUsageWatcher::CollectCPUUsage() {
+ if (!mInitialized) {
+ return Ok();
+ }
+
+ mExternalUsageRatio = 0.0f;
+
+ CPUStats processTimes;
+ MOZ_TRY_VAR(processTimes, GetProcessCPUStats(mNumCPUs));
+ CPUStats globalTimes;
+ MOZ_TRY_VAR(globalTimes, GetGlobalCPUStats());
+
+ uint64_t processUsageDelta = processTimes.usageTime - mProcessUsageTime;
+ uint64_t processUpdateDelta = processTimes.updateTime - mProcessUpdateTime;
+ float processUsageNormalized =
+ processUsageDelta > 0
+ ? (float)processUsageDelta / (float)processUpdateDelta
+ : 0.0f;
+
+ uint64_t globalUsageDelta = globalTimes.usageTime - mGlobalUsageTime;
+ uint64_t globalUpdateDelta = globalTimes.updateTime - mGlobalUpdateTime;
+ float globalUsageNormalized =
+ globalUsageDelta > 0 ? (float)globalUsageDelta / (float)globalUpdateDelta
+ : 0.0f;
+
+ mProcessUsageTime = processTimes.usageTime;
+ mProcessUpdateTime = processTimes.updateTime;
+ mGlobalUsageTime = globalTimes.usageTime;
+ mGlobalUpdateTime = globalTimes.updateTime;
+
+ mExternalUsageRatio =
+ std::max(0.0f, globalUsageNormalized - processUsageNormalized);
+
+ return Ok();
+}
+
+void CPUUsageWatcher::AnnotateHang(BackgroundHangAnnotations& aAnnotations) {
+ if (!mInitialized) {
+ return;
+ }
+
+ if (mExternalUsageRatio > mExternalUsageThreshold) {
+ aAnnotations.AddAnnotation(u"ExternalCPUHigh"_ns, true);
+ }
+}
+
+#else // !CPU_USAGE_WATCHER_ACTIVE
+
+Result<Ok, CPUUsageWatcherError> CPUUsageWatcher::Init() { return Ok(); }
+
+void CPUUsageWatcher::Uninit() {}
+
+Result<Ok, CPUUsageWatcherError> CPUUsageWatcher::CollectCPUUsage() {
+ return Ok();
+}
+
+void CPUUsageWatcher::AnnotateHang(BackgroundHangAnnotations& aAnnotations) {}
+
+#endif // CPU_USAGE_WATCHER_ACTIVE
+
+} // namespace mozilla
diff --git a/xpcom/threads/CPUUsageWatcher.h b/xpcom/threads/CPUUsageWatcher.h
new file mode 100644
index 0000000000..c3a643378a
--- /dev/null
+++ b/xpcom/threads/CPUUsageWatcher.h
@@ -0,0 +1,100 @@
+/* -*- 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_CPUUsageWatcher_h
+#define mozilla_CPUUsageWatcher_h
+
+#include <stdint.h>
+
+#include "mozilla/HangAnnotations.h"
+#include "mozilla/Result.h"
+
+// We only support OSX and Windows, because on Linux we're forced to read
+// from /proc/stat in order to get global CPU values. We would prefer to not
+// eat that cost for this.
+#if defined(NIGHTLY_BUILD) && (defined(XP_WIN) || defined(XP_MACOSX))
+# define CPU_USAGE_WATCHER_ACTIVE
+#endif
+
+namespace mozilla {
+
+// Start error values at 1 to allow using the UnusedZero Result
+// optimization.
+enum CPUUsageWatcherError : uint8_t {
+ ClockGetTimeError = 1,
+ GetNumberOfProcessorsError,
+ GetProcessTimesError,
+ GetSystemTimesError,
+ HostStatisticsError,
+ ProcStatError,
+};
+
+namespace detail {
+
+template <>
+struct UnusedZero<CPUUsageWatcherError> : UnusedZeroEnum<CPUUsageWatcherError> {
+};
+
+} // namespace detail
+
+class CPUUsageHangAnnotator : public BackgroundHangAnnotator {
+ public:
+};
+
+class CPUUsageWatcher : public BackgroundHangAnnotator {
+ public:
+#ifdef CPU_USAGE_WATCHER_ACTIVE
+ CPUUsageWatcher()
+ : mInitialized(false),
+ mExternalUsageThreshold(0),
+ mExternalUsageRatio(0),
+ mProcessUsageTime(0),
+ mProcessUpdateTime(0),
+ mGlobalUsageTime(0),
+ mGlobalUpdateTime(0),
+ mNumCPUs(0) {}
+#endif
+
+ Result<Ok, CPUUsageWatcherError> Init();
+
+ void Uninit();
+
+ // Updates necessary values to allow AnnotateHang to function. This must be
+ // called on some semi-regular basis, as it will calculate the mean CPU
+ // usage values between now and the last time it was called.
+ Result<Ok, CPUUsageWatcherError> CollectCPUUsage();
+
+ void AnnotateHang(BackgroundHangAnnotations& aAnnotations) final;
+
+ private:
+#ifdef CPU_USAGE_WATCHER_ACTIVE
+ bool mInitialized;
+ // The threshold above which we will mark a hang as occurring under high
+ // external CPU usage conditions
+ float mExternalUsageThreshold;
+ // The CPU usage (0-1) external to our process, averaged between the two
+ // most recent monitor thread runs
+ float mExternalUsageRatio;
+ // The total cumulative CPU usage time by our process as of the last
+ // CollectCPUUsage or Startup
+ uint64_t mProcessUsageTime;
+ // A time value in the same units as mProcessUsageTime used to
+ // determine the ratio of CPU usage time to idle time
+ uint64_t mProcessUpdateTime;
+ // The total cumulative CPU usage time by all processes as of the last
+ // CollectCPUUsage or Startup
+ uint64_t mGlobalUsageTime;
+ // A time value in the same units as mGlobalUsageTime used to
+ // determine the ratio of CPU usage time to idle time
+ uint64_t mGlobalUpdateTime;
+ // The number of virtual cores on our machine
+ uint64_t mNumCPUs;
+#endif
+};
+
+} // namespace mozilla
+
+#endif // mozilla_CPUUsageWatcher_h
diff --git a/xpcom/threads/CondVar.h b/xpcom/threads/CondVar.h
new file mode 100644
index 0000000000..890e1c21a0
--- /dev/null
+++ b/xpcom/threads/CondVar.h
@@ -0,0 +1,137 @@
+/* -*- 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_CondVar_h
+#define mozilla_CondVar_h
+
+#include "mozilla/BlockingResourceBase.h"
+#include "mozilla/PlatformConditionVariable.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/TimeStamp.h"
+
+#if defined(MOZILLA_INTERNAL_API) && !defined(DEBUG)
+# include "GeckoProfiler.h"
+#endif // defined( MOZILLA_INTERNAL_API) && !defined(DEBUG)
+
+namespace mozilla {
+
+/**
+ * Similarly to OffTheBooksMutex, OffTheBooksCondvar is identical to CondVar,
+ * except that OffTheBooksCondVar doesn't include leak checking. Sometimes
+ * you want to intentionally "leak" a CondVar until shutdown; in these cases,
+ * OffTheBooksCondVar is for you.
+ */
+class OffTheBooksCondVar : BlockingResourceBase {
+ public:
+ /**
+ * OffTheBooksCondVar
+ *
+ * The CALLER owns |aLock|.
+ *
+ * @param aLock A Mutex to associate with this condition variable.
+ * @param aName A name which can reference this monitor
+ * @returns If failure, nullptr.
+ * If success, a valid Monitor* which must be destroyed
+ * by Monitor::DestroyMonitor()
+ **/
+ OffTheBooksCondVar(OffTheBooksMutex& aLock, const char* aName)
+ : BlockingResourceBase(aName, eCondVar), mLock(&aLock) {}
+
+ /**
+ * ~OffTheBooksCondVar
+ * Clean up after this OffTheBooksCondVar, but NOT its associated Mutex.
+ **/
+ ~OffTheBooksCondVar() = default;
+
+ /**
+ * Wait
+ * @see prcvar.h
+ **/
+#ifndef DEBUG
+ void Wait() {
+# ifdef MOZILLA_INTERNAL_API
+ AUTO_PROFILER_THREAD_SLEEP;
+# endif // MOZILLA_INTERNAL_API
+ mImpl.wait(*mLock);
+ }
+
+ CVStatus Wait(TimeDuration aDuration) {
+# ifdef MOZILLA_INTERNAL_API
+ AUTO_PROFILER_THREAD_SLEEP;
+# endif // MOZILLA_INTERNAL_API
+ return mImpl.wait_for(*mLock, aDuration);
+ }
+#else
+ // NOTE: debug impl is in BlockingResourceBase.cpp
+ void Wait();
+ CVStatus Wait(TimeDuration aDuration);
+#endif
+
+ /**
+ * Notify
+ * @see prcvar.h
+ **/
+ void Notify() { mImpl.notify_one(); }
+
+ /**
+ * NotifyAll
+ * @see prcvar.h
+ **/
+ void NotifyAll() { mImpl.notify_all(); }
+
+#ifdef DEBUG
+ /**
+ * AssertCurrentThreadOwnsMutex
+ * @see Mutex::AssertCurrentThreadOwns
+ **/
+ void AssertCurrentThreadOwnsMutex() { mLock->AssertCurrentThreadOwns(); }
+
+ /**
+ * AssertNotCurrentThreadOwnsMutex
+ * @see Mutex::AssertNotCurrentThreadOwns
+ **/
+ void AssertNotCurrentThreadOwnsMutex() {
+ mLock->AssertNotCurrentThreadOwns();
+ }
+
+#else
+ void AssertCurrentThreadOwnsMutex() {}
+ void AssertNotCurrentThreadOwnsMutex() {}
+
+#endif // ifdef DEBUG
+
+ private:
+ OffTheBooksCondVar();
+ OffTheBooksCondVar(const OffTheBooksCondVar&) = delete;
+ OffTheBooksCondVar& operator=(const OffTheBooksCondVar&) = delete;
+
+ OffTheBooksMutex* mLock;
+ detail::ConditionVariableImpl mImpl;
+};
+
+/**
+ * CondVar
+ * Vanilla condition variable. Please don't use this unless you have a
+ * compelling reason --- Monitor provides a simpler API.
+ */
+class CondVar : public OffTheBooksCondVar {
+ public:
+ CondVar(OffTheBooksMutex& aLock, const char* aName)
+ : OffTheBooksCondVar(aLock, aName) {
+ MOZ_COUNT_CTOR(CondVar);
+ }
+
+ MOZ_COUNTED_DTOR(CondVar)
+
+ private:
+ CondVar();
+ CondVar(const CondVar&);
+ CondVar& operator=(const CondVar&);
+};
+
+} // namespace mozilla
+
+#endif // ifndef mozilla_CondVar_h
diff --git a/xpcom/threads/DataMutex.h b/xpcom/threads/DataMutex.h
new file mode 100644
index 0000000000..0a768f925a
--- /dev/null
+++ b/xpcom/threads/DataMutex.h
@@ -0,0 +1,125 @@
+/* -*- 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 DataMutex_h__
+#define DataMutex_h__
+
+#include <utility>
+#include "mozilla/Mutex.h"
+#include "mozilla/StaticMutex.h"
+
+namespace mozilla {
+
+// A template to wrap a type with a mutex so that accesses to the type's
+// data are required to take the lock before accessing it. This ensures
+// that a mutex is explicitly associated with the data that it protects,
+// and makes it impossible to access the data without first taking the
+// associated mutex.
+//
+// This is based on Rust's std::sync::Mutex, which operates under the
+// strategy of locking data, rather than code.
+//
+// Examples:
+//
+// DataMutex<uint32_t> u32DataMutex(1, "u32DataMutex");
+// auto x = u32DataMutex.Lock();
+// *x = 4;
+// assert(*x, 4u);
+//
+// DataMutex<nsTArray<uint32_t>> arrayDataMutex("arrayDataMutex");
+// auto a = arrayDataMutex.Lock();
+// auto& x = a.ref();
+// x.AppendElement(1u);
+// assert(x[0], 1u);
+//
+template <typename T, typename MutexType>
+class DataMutexBase {
+ public:
+ class MOZ_STACK_CLASS AutoLock {
+ public:
+ T* operator->() const& { return &ref(); }
+ T* operator->() const&& = delete;
+
+ T& operator*() const& { return ref(); }
+ T& operator*() const&& = delete;
+
+ // Like RefPtr, make this act like its underlying raw pointer type
+ // whenever it is used in a context where a raw pointer is expected.
+ operator T*() const& { return &ref(); }
+
+ // Like RefPtr, don't allow implicit conversion of temporary to raw pointer.
+ operator T*() const&& = delete;
+
+ T& ref() const& {
+ MOZ_ASSERT(mOwner);
+ return mOwner->mValue;
+ }
+ T& ref() const&& = delete;
+
+ AutoLock(AutoLock&& aOther) : mOwner(aOther.mOwner) {
+ aOther.mOwner = nullptr;
+ }
+
+ ~AutoLock() {
+ if (mOwner) {
+ mOwner->mMutex.Unlock();
+ mOwner = nullptr;
+ }
+ }
+
+ private:
+ friend class DataMutexBase;
+
+ AutoLock(const AutoLock& aOther) = delete;
+
+ explicit AutoLock(DataMutexBase<T, MutexType>* aDataMutex)
+ : mOwner(aDataMutex) {
+ MOZ_ASSERT(!!mOwner);
+ mOwner->mMutex.Lock();
+ }
+
+ DataMutexBase<T, MutexType>* mOwner;
+ };
+
+ explicit DataMutexBase(const char* aName) : mMutex(aName) {}
+
+ DataMutexBase(T&& aValue, const char* aName)
+ : mMutex(aName), mValue(std::move(aValue)) {}
+
+ AutoLock Lock() { return AutoLock(this); }
+
+ const MutexType& Mutex() const { return mMutex; }
+
+ private:
+ MutexType mMutex;
+ T mValue;
+};
+
+// Craft a version of StaticMutex that takes a const char* in its ctor.
+// We need this so it works interchangeably with Mutex which requires a const
+// char* aName in its ctor.
+class StaticMutexNameless : public StaticMutex {
+ public:
+ explicit StaticMutexNameless(const char* aName) : StaticMutex() {}
+
+ private:
+ // Disallow copy construction, `=`, `new`, and `delete` like BaseStaticMutex.
+#ifdef DEBUG
+ StaticMutexNameless(StaticMutexNameless& aOther);
+#endif // DEBUG
+ StaticMutexNameless& operator=(StaticMutexNameless* aRhs);
+ static void* operator new(size_t) noexcept(true);
+ static void operator delete(void*);
+};
+
+template <typename T>
+using DataMutex = DataMutexBase<T, Mutex>;
+template <typename T>
+using StaticDataMutex = DataMutexBase<T, StaticMutexNameless>;
+
+} // namespace mozilla
+
+#endif // DataMutex_h__
diff --git a/xpcom/threads/DeadlockDetector.h b/xpcom/threads/DeadlockDetector.h
new file mode 100644
index 0000000000..d8b9c79c02
--- /dev/null
+++ b/xpcom/threads/DeadlockDetector.h
@@ -0,0 +1,359 @@
+/* -*- 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_DeadlockDetector_h
+#define mozilla_DeadlockDetector_h
+
+#include "mozilla/Attributes.h"
+
+#include <stdlib.h>
+
+#include "prlock.h"
+
+#include "nsClassHashtable.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+/**
+ * DeadlockDetector
+ *
+ * The following is an approximate description of how the deadlock detector
+ * works.
+ *
+ * The deadlock detector ensures that all blocking resources are
+ * acquired according to a partial order P. One type of blocking
+ * resource is a lock. If a lock l1 is acquired (locked) before l2,
+ * then we say that |l1 <_P l2|. The detector flags an error if two
+ * locks l1 and l2 have an inconsistent ordering in P; that is, if
+ * both |l1 <_P l2| and |l2 <_P l1|. This is a potential error
+ * because a thread acquiring l1,l2 according to the first order might
+ * race with a thread acquiring them according to the second order.
+ * If this happens under the right conditions, then the acquisitions
+ * will deadlock.
+ *
+ * This deadlock detector doesn't know at compile-time what P is. So,
+ * it tries to discover the order at run time. More precisely, it
+ * finds <i>some</i> order P, then tries to find chains of resource
+ * acquisitions that violate P. An example acquisition sequence, and
+ * the orders they impose, is
+ * l1.lock() // current chain: [ l1 ]
+ * // order: { }
+ *
+ * l2.lock() // current chain: [ l1, l2 ]
+ * // order: { l1 <_P l2 }
+ *
+ * l3.lock() // current chain: [ l1, l2, l3 ]
+ * // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3 }
+ * // (note: <_P is transitive, so also |l1 <_P l3|)
+ *
+ * l2.unlock() // current chain: [ l1, l3 ]
+ * // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3 }
+ * // (note: it's OK, but weird, that l2 was unlocked out
+ * // of order. we still have l1 <_P l3).
+ *
+ * l2.lock() // current chain: [ l1, l3, l2 ]
+ * // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3,
+ * l3 <_P l2 (!!!) }
+ * BEEP BEEP! Here the detector will flag a potential error, since
+ * l2 and l3 were used inconsistently (and potentially in ways that
+ * would deadlock).
+ */
+template <typename T>
+class DeadlockDetector {
+ public:
+ typedef nsTArray<const T*> ResourceAcquisitionArray;
+
+ private:
+ struct OrderingEntry;
+ typedef nsTArray<OrderingEntry*> HashEntryArray;
+ typedef typename HashEntryArray::index_type index_type;
+ typedef typename HashEntryArray::size_type size_type;
+ static const index_type NoIndex = HashEntryArray::NoIndex;
+
+ /**
+ * Value type for the ordering table. Contains the other
+ * resources on which an ordering constraint |key < other|
+ * exists. The catch is that we also store the calling context at
+ * which the other resource was acquired; this improves the
+ * quality of error messages when potential deadlock is detected.
+ */
+ struct OrderingEntry {
+ explicit OrderingEntry(const T* aResource)
+ : mOrderedLT() // FIXME bug 456272: set to empirical dep size?
+ ,
+ mExternalRefs(),
+ mResource(aResource) {}
+ ~OrderingEntry() {}
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t n = aMallocSizeOf(this);
+ n += mOrderedLT.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ n += mExternalRefs.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ return n;
+ }
+
+ HashEntryArray mOrderedLT; // this <_o Other
+ HashEntryArray mExternalRefs; // hash entries that reference this
+ const T* mResource;
+ };
+
+ // Throwaway RAII lock to make the following code safer.
+ struct PRAutoLock {
+ explicit PRAutoLock(PRLock* aLock) : mLock(aLock) { PR_Lock(mLock); }
+ ~PRAutoLock() { PR_Unlock(mLock); }
+ PRLock* mLock;
+ };
+
+ public:
+ static const uint32_t kDefaultNumBuckets;
+
+ /**
+ * DeadlockDetector
+ * Create a new deadlock detector.
+ *
+ * @param aNumResourcesGuess Guess at approximate number of resources
+ * that will be checked.
+ */
+ explicit DeadlockDetector(uint32_t aNumResourcesGuess = kDefaultNumBuckets)
+ : mOrdering(aNumResourcesGuess) {
+ mLock = PR_NewLock();
+ if (!mLock) {
+ MOZ_CRASH("couldn't allocate deadlock detector lock");
+ }
+ }
+
+ /**
+ * ~DeadlockDetector
+ *
+ * *NOT* thread safe.
+ */
+ ~DeadlockDetector() { PR_DestroyLock(mLock); }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t n = aMallocSizeOf(this);
+
+ {
+ PRAutoLock _(mLock);
+ n += mOrdering.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto iter = mOrdering.ConstIter(); !iter.Done(); iter.Next()) {
+ // NB: Key is accounted for in the entry.
+ n += iter.Data()->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ }
+
+ return n;
+ }
+
+ /**
+ * Add
+ * Make the deadlock detector aware of |aResource|.
+ *
+ * WARNING: The deadlock detector owns |aResource|.
+ *
+ * Thread safe.
+ *
+ * @param aResource Resource to make deadlock detector aware of.
+ */
+ void Add(const T* aResource) {
+ PRAutoLock _(mLock);
+ mOrdering.Put(aResource, new OrderingEntry(aResource));
+ }
+
+ void Remove(const T* aResource) {
+ PRAutoLock _(mLock);
+
+ OrderingEntry* entry = mOrdering.Get(aResource);
+
+ // Iterate the external refs and remove the entry from them.
+ HashEntryArray& refs = entry->mExternalRefs;
+ for (index_type i = 0; i < refs.Length(); i++) {
+ refs[i]->mOrderedLT.RemoveElementSorted(entry);
+ }
+
+ // Iterate orders and remove this entry from their refs.
+ HashEntryArray& orders = entry->mOrderedLT;
+ for (index_type i = 0; i < orders.Length(); i++) {
+ orders[i]->mExternalRefs.RemoveElementSorted(entry);
+ }
+
+ // Now the entry can be safely removed.
+ mOrdering.Remove(aResource);
+ }
+
+ /**
+ * CheckAcquisition This method is called after acquiring |aLast|,
+ * but before trying to acquire |aProposed|.
+ * It determines whether actually trying to acquire |aProposed|
+ * will create problems. It is OK if |aLast| is nullptr; this is
+ * interpreted as |aProposed| being the thread's first acquisition
+ * of its current chain.
+ *
+ * Iff acquiring |aProposed| may lead to deadlock for some thread
+ * interleaving (including the current one!), the cyclical
+ * dependency from which this was deduced is returned. Otherwise,
+ * 0 is returned.
+ *
+ * If a potential deadlock is detected and a resource cycle is
+ * returned, it is the *caller's* responsibility to free it.
+ *
+ * Thread safe.
+ *
+ * @param aLast Last resource acquired by calling thread (or 0).
+ * @param aProposed Resource calling thread proposes to acquire.
+ */
+ ResourceAcquisitionArray* CheckAcquisition(const T* aLast,
+ const T* aProposed) {
+ if (!aLast) {
+ // don't check if |0 < aProposed|; just vamoose
+ return 0;
+ }
+
+ NS_ASSERTION(aProposed, "null resource");
+ PRAutoLock _(mLock);
+
+ OrderingEntry* proposed = mOrdering.Get(aProposed);
+ NS_ASSERTION(proposed, "missing ordering entry");
+
+ OrderingEntry* current = mOrdering.Get(aLast);
+ NS_ASSERTION(current, "missing ordering entry");
+
+ // this is the crux of the deadlock detector algorithm
+
+ if (current == proposed) {
+ // reflexive deadlock. fastpath b/c InTransitiveClosure is
+ // not applicable here.
+ ResourceAcquisitionArray* cycle = new ResourceAcquisitionArray();
+ if (!cycle) {
+ MOZ_CRASH("can't allocate dep. cycle array");
+ }
+ cycle->AppendElement(current->mResource);
+ cycle->AppendElement(aProposed);
+ return cycle;
+ }
+ if (InTransitiveClosure(current, proposed)) {
+ // we've already established |aLast < aProposed|. all is well.
+ return 0;
+ }
+ if (InTransitiveClosure(proposed, current)) {
+ // the order |aProposed < aLast| has been deduced, perhaps
+ // transitively. we're attempting to violate that
+ // constraint by acquiring resources in the order
+ // |aLast < aProposed|, and thus we may deadlock under the
+ // right conditions.
+ ResourceAcquisitionArray* cycle = GetDeductionChain(proposed, current);
+ // show how acquiring |aProposed| would complete the cycle
+ cycle->AppendElement(aProposed);
+ return cycle;
+ }
+ // |aLast|, |aProposed| are unordered according to our
+ // poset. this is fine, but we now need to add this
+ // ordering constraint.
+ current->mOrderedLT.InsertElementSorted(proposed);
+ proposed->mExternalRefs.InsertElementSorted(current);
+ return 0;
+ }
+
+ /**
+ * Return true iff |aTarget| is in the transitive closure of |aStart|
+ * over the ordering relation `<_this'.
+ *
+ * @precondition |aStart != aTarget|
+ */
+ bool InTransitiveClosure(const OrderingEntry* aStart,
+ const OrderingEntry* aTarget) const {
+ // NB: Using a static comparator rather than default constructing one shows
+ // a 9% improvement in scalability tests on some systems.
+ static nsDefaultComparator<const OrderingEntry*, const OrderingEntry*> comp;
+ if (aStart->mOrderedLT.BinaryIndexOf(aTarget, comp) != NoIndex) {
+ return true;
+ }
+
+ index_type i = 0;
+ size_type len = aStart->mOrderedLT.Length();
+ for (auto it = aStart->mOrderedLT.Elements(); i < len; ++i, ++it) {
+ if (InTransitiveClosure(*it, aTarget)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return an array of all resource acquisitions
+ * aStart <_this r1 <_this r2 <_ ... <_ aTarget
+ * from which |aStart <_this aTarget| was deduced, including
+ * |aStart| and |aTarget|.
+ *
+ * Nb: there may be multiple deductions of |aStart <_this
+ * aTarget|. This function returns the first ordering found by
+ * depth-first search.
+ *
+ * Nb: |InTransitiveClosure| could be replaced by this function.
+ * However, this one is more expensive because we record the DFS
+ * search stack on the heap whereas the other doesn't.
+ *
+ * @precondition |aStart != aTarget|
+ */
+ ResourceAcquisitionArray* GetDeductionChain(const OrderingEntry* aStart,
+ const OrderingEntry* aTarget) {
+ ResourceAcquisitionArray* chain = new ResourceAcquisitionArray();
+ if (!chain) {
+ MOZ_CRASH("can't allocate dep. cycle array");
+ }
+ chain->AppendElement(aStart->mResource);
+
+ NS_ASSERTION(GetDeductionChain_Helper(aStart, aTarget, chain),
+ "GetDeductionChain called when there's no deadlock");
+ return chain;
+ }
+
+ // precondition: |aStart != aTarget|
+ // invariant: |aStart| is the last element in |aChain|
+ bool GetDeductionChain_Helper(const OrderingEntry* aStart,
+ const OrderingEntry* aTarget,
+ ResourceAcquisitionArray* aChain) {
+ if (aStart->mOrderedLT.BinaryIndexOf(aTarget) != NoIndex) {
+ aChain->AppendElement(aTarget->mResource);
+ return true;
+ }
+
+ index_type i = 0;
+ size_type len = aStart->mOrderedLT.Length();
+ for (auto it = aStart->mOrderedLT.Elements(); i < len; ++i, ++it) {
+ aChain->AppendElement((*it)->mResource);
+ if (GetDeductionChain_Helper(*it, aTarget, aChain)) {
+ return true;
+ }
+ aChain->RemoveLastElement();
+ }
+ return false;
+ }
+
+ /**
+ * The partial order on resource acquisitions used by the deadlock
+ * detector.
+ */
+ nsClassHashtable<nsPtrHashKey<const T>, OrderingEntry> mOrdering;
+
+ /**
+ * Protects contentious methods.
+ * Nb: can't use mozilla::Mutex since we are used as its deadlock
+ * detector.
+ */
+ PRLock* mLock;
+
+ private:
+ DeadlockDetector(const DeadlockDetector& aDD) = delete;
+ DeadlockDetector& operator=(const DeadlockDetector& aDD) = delete;
+};
+
+template <typename T>
+// FIXME bug 456272: tune based on average workload
+const uint32_t DeadlockDetector<T>::kDefaultNumBuckets = 32;
+
+} // namespace mozilla
+
+#endif // ifndef mozilla_DeadlockDetector_h
diff --git a/xpcom/threads/EventQueue.cpp b/xpcom/threads/EventQueue.cpp
new file mode 100644
index 0000000000..fc263032b6
--- /dev/null
+++ b/xpcom/threads/EventQueue.cpp
@@ -0,0 +1,133 @@
+/* -*- 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/EventQueue.h"
+
+#include "GeckoProfiler.h"
+#include "InputTaskManager.h"
+#include "nsIRunnable.h"
+#include "TaskController.h"
+
+using namespace mozilla;
+using namespace mozilla::detail;
+
+template <size_t ItemsPerPage>
+void EventQueueInternal<ItemsPerPage>::PutEvent(
+ already_AddRefed<nsIRunnable>&& aEvent, EventQueuePriority aPriority,
+ const MutexAutoLock& aProofOfLock, mozilla::TimeDuration* aDelay) {
+ nsCOMPtr<nsIRunnable> event(aEvent);
+
+ static_assert(static_cast<uint32_t>(nsIRunnablePriority::PRIORITY_IDLE) ==
+ static_cast<uint32_t>(EventQueuePriority::Idle));
+ static_assert(static_cast<uint32_t>(nsIRunnablePriority::PRIORITY_NORMAL) ==
+ static_cast<uint32_t>(EventQueuePriority::Normal));
+ static_assert(
+ static_cast<uint32_t>(nsIRunnablePriority::PRIORITY_MEDIUMHIGH) ==
+ static_cast<uint32_t>(EventQueuePriority::MediumHigh));
+ static_assert(
+ static_cast<uint32_t>(nsIRunnablePriority::PRIORITY_INPUT_HIGH) ==
+ static_cast<uint32_t>(EventQueuePriority::InputHigh));
+ static_assert(static_cast<uint32_t>(nsIRunnablePriority::PRIORITY_HIGH) ==
+ static_cast<uint32_t>(EventQueuePriority::High));
+
+ if (mForwardToTC) {
+ TaskController* tc = TaskController::Get();
+
+ TaskManager* manager = nullptr;
+ if (aPriority == EventQueuePriority::InputHigh) {
+ if (InputTaskManager::Get()->State() ==
+ InputTaskManager::STATE_DISABLED) {
+ aPriority = EventQueuePriority::Normal;
+ } else {
+ manager = InputTaskManager::Get();
+ }
+ } else if (aPriority == EventQueuePriority::DeferredTimers ||
+ aPriority == EventQueuePriority::Idle) {
+ manager = TaskController::Get()->GetIdleTaskManager();
+ }
+
+ tc->DispatchRunnable(event.forget(), static_cast<uint32_t>(aPriority),
+ manager);
+ return;
+ }
+
+#ifdef MOZ_GECKO_PROFILER
+ // Sigh, this doesn't check if this thread is being profiled
+ if (profiler_is_active()) {
+ // check to see if the profiler has been enabled since the last PutEvent
+ while (mDispatchTimes.Count() < mQueue.Count()) {
+ mDispatchTimes.Push(TimeStamp());
+ }
+ mDispatchTimes.Push(aDelay ? TimeStamp::Now() - *aDelay : TimeStamp::Now());
+ }
+#endif
+
+ mQueue.Push(std::move(event));
+}
+
+template <size_t ItemsPerPage>
+already_AddRefed<nsIRunnable> EventQueueInternal<ItemsPerPage>::GetEvent(
+ const MutexAutoLock& aProofOfLock, mozilla::TimeDuration* aLastEventDelay) {
+ if (mQueue.IsEmpty()) {
+ if (aLastEventDelay) {
+ *aLastEventDelay = TimeDuration();
+ }
+ return nullptr;
+ }
+
+#ifdef MOZ_GECKO_PROFILER
+ // We always want to clear the dispatch times, even if the profiler is turned
+ // off, because we want to empty the (previously-collected) dispatch times, if
+ // any, from when the profiler was turned on. We only want to do something
+ // interesting with the dispatch times if the profiler is turned on, though.
+ if (!mDispatchTimes.IsEmpty()) {
+ TimeStamp dispatch_time = mDispatchTimes.Pop();
+ if (profiler_is_active()) {
+ if (!dispatch_time.IsNull()) {
+ if (aLastEventDelay) {
+ *aLastEventDelay = TimeStamp::Now() - dispatch_time;
+ }
+ }
+ }
+ } else if (profiler_is_active()) {
+ if (aLastEventDelay) {
+ // if we just turned on the profiler, we don't have dispatch
+ // times for events already in the queue.
+ *aLastEventDelay = TimeDuration();
+ }
+ }
+#endif
+
+ nsCOMPtr<nsIRunnable> result = mQueue.Pop();
+ return result.forget();
+}
+
+template <size_t ItemsPerPage>
+bool EventQueueInternal<ItemsPerPage>::IsEmpty(
+ const MutexAutoLock& aProofOfLock) {
+ return mQueue.IsEmpty();
+}
+
+template <size_t ItemsPerPage>
+bool EventQueueInternal<ItemsPerPage>::HasReadyEvent(
+ const MutexAutoLock& aProofOfLock) {
+ return !IsEmpty(aProofOfLock);
+}
+
+template <size_t ItemsPerPage>
+size_t EventQueueInternal<ItemsPerPage>::Count(
+ const MutexAutoLock& aProofOfLock) const {
+ return mQueue.Count();
+}
+
+namespace mozilla {
+template class EventQueueSized<16>; // Used by ThreadEventQueue
+template class EventQueueSized<64>; // Used by ThrottledEventQueue
+namespace detail {
+template class EventQueueInternal<16>; // Used by ThreadEventQueue
+template class EventQueueInternal<64>; // Used by ThrottledEventQueue
+} // namespace detail
+} // namespace mozilla
diff --git a/xpcom/threads/EventQueue.h b/xpcom/threads/EventQueue.h
new file mode 100644
index 0000000000..6301a0f6fd
--- /dev/null
+++ b/xpcom/threads/EventQueue.h
@@ -0,0 +1,132 @@
+/* -*- 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_EventQueue_h
+#define mozilla_EventQueue_h
+
+#include "mozilla/Mutex.h"
+#include "mozilla/Queue.h"
+#include "mozilla/TimeStamp.h"
+#include "nsCOMPtr.h"
+
+class nsIRunnable;
+
+namespace mozilla {
+
+enum class EventQueuePriority {
+ Idle,
+ DeferredTimers,
+ InputLow,
+ Normal,
+ MediumHigh,
+ InputHigh,
+ High,
+
+ Count
+};
+
+class IdlePeriodState;
+
+namespace detail {
+
+// EventQueue is our unsynchronized event queue implementation. It is a queue
+// of runnables used for non-main thread, as well as optionally providing
+// forwarding to TaskController.
+//
+// Since EventQueue is unsynchronized, it should be wrapped in an outer
+// SynchronizedEventQueue implementation (like ThreadEventQueue).
+template <size_t ItemsPerPage>
+class EventQueueInternal {
+ public:
+ explicit EventQueueInternal(bool aForwardToTC) : mForwardToTC(aForwardToTC) {}
+
+ // Add an event to the end of the queue. Implementors are free to use
+ // aPriority however they wish. If the runnable supports
+ // nsIRunnablePriority and the implementing class supports
+ // prioritization, aPriority represents the result of calling
+ // nsIRunnablePriority::GetPriority(). *aDelay is time the event has
+ // already been delayed (used when moving an event from one queue to
+ // another)
+ void PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
+ EventQueuePriority aPriority, const MutexAutoLock& aProofOfLock,
+ mozilla::TimeDuration* aDelay = nullptr);
+
+ // Get an event from the front of the queue. This should return null if the
+ // queue is non-empty but the event in front is not ready to run.
+ // *aLastEventDelay is the time the event spent in queues before being
+ // retrieved.
+ already_AddRefed<nsIRunnable> GetEvent(
+ const MutexAutoLock& aProofOfLock,
+ mozilla::TimeDuration* aLastEventDelay = nullptr);
+
+ // Returns true if the queue is empty. Implies !HasReadyEvent().
+ bool IsEmpty(const MutexAutoLock& aProofOfLock);
+
+ // Returns true if the queue is non-empty and if the event in front is ready
+ // to run. Implies !IsEmpty(). This should return true iff GetEvent returns a
+ // non-null value.
+ bool HasReadyEvent(const MutexAutoLock& aProofOfLock);
+
+ // Returns the number of events in the queue.
+ size_t Count(const MutexAutoLock& aProofOfLock) const;
+ // For some reason, if we put this in the .cpp file the linker can't find it
+ already_AddRefed<nsIRunnable> PeekEvent(const MutexAutoLock& aProofOfLock) {
+ if (mQueue.IsEmpty()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIRunnable> result = mQueue.FirstElement();
+ return result.forget();
+ }
+
+ void EnableInputEventPrioritization(const MutexAutoLock& aProofOfLock) {}
+ void FlushInputEventPrioritization(const MutexAutoLock& aProofOfLock) {}
+ void SuspendInputEventPrioritization(const MutexAutoLock& aProofOfLock) {}
+ void ResumeInputEventPrioritization(const MutexAutoLock& aProofOfLock) {}
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t size = mQueue.ShallowSizeOfExcludingThis(aMallocSizeOf);
+#ifdef MOZ_GECKO_PROFILER
+ size += mDispatchTimes.ShallowSizeOfExcludingThis(aMallocSizeOf);
+#endif
+ return size;
+ }
+
+ private:
+ mozilla::Queue<nsCOMPtr<nsIRunnable>, ItemsPerPage> mQueue;
+#ifdef MOZ_GECKO_PROFILER
+ // This queue is only populated when the profiler is turned on.
+ mozilla::Queue<mozilla::TimeStamp, ItemsPerPage> mDispatchTimes;
+ TimeDuration mLastEventDelay;
+#endif
+ // This indicates PutEvent forwards runnables to the TaskController. This
+ // should be true for the top level event queue on the main thread.
+ bool mForwardToTC;
+};
+
+} // namespace detail
+
+class EventQueue final : public mozilla::detail::EventQueueInternal<16> {
+ public:
+ explicit EventQueue(bool aForwardToTC = false)
+ : mozilla::detail::EventQueueInternal<16>(aForwardToTC) {}
+};
+
+template <size_t ItemsPerPage = 16>
+class EventQueueSized final
+ : public mozilla::detail::EventQueueInternal<ItemsPerPage> {
+ public:
+ explicit EventQueueSized(bool aForwardToTC = false)
+ : mozilla::detail::EventQueueInternal<ItemsPerPage>(aForwardToTC) {}
+};
+
+} // namespace mozilla
+
+#endif // mozilla_EventQueue_h
diff --git a/xpcom/threads/IdlePeriodState.cpp b/xpcom/threads/IdlePeriodState.cpp
new file mode 100644
index 0000000000..95f155e339
--- /dev/null
+++ b/xpcom/threads/IdlePeriodState.cpp
@@ -0,0 +1,254 @@
+/* -*- 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/IdlePeriodState.h"
+#include "mozilla/StaticPrefs_idle_period.h"
+#include "mozilla/ipc/IdleSchedulerChild.h"
+#include "nsIIdlePeriod.h"
+#include "nsThreadManager.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOM.h"
+#include "nsXULAppAPI.h"
+
+static uint64_t sIdleRequestCounter = 0;
+
+namespace mozilla {
+
+IdlePeriodState::IdlePeriodState(already_AddRefed<nsIIdlePeriod>&& aIdlePeriod)
+ : mIdlePeriod(aIdlePeriod) {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Why are we touching idle state off the main thread?");
+}
+
+IdlePeriodState::~IdlePeriodState() {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Why are we touching idle state off the main thread?");
+ if (mIdleScheduler) {
+ mIdleScheduler->Disconnect();
+ }
+}
+
+size_t IdlePeriodState::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t n = 0;
+ if (mIdlePeriod) {
+ n += aMallocSizeOf(mIdlePeriod);
+ }
+
+ return n;
+}
+
+void IdlePeriodState::FlagNotIdle() {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Why are we touching idle state off the main thread?");
+
+ EnsureIsActive();
+ if (mIdleToken && mIdleToken < TimeStamp::Now()) {
+ ClearIdleToken();
+ }
+}
+
+void IdlePeriodState::RanOutOfTasks(const MutexAutoUnlock& aProofOfUnlock) {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Why are we touching idle state off the main thread?");
+ MOZ_ASSERT(!mHasPendingEventsPromisedIdleEvent);
+ EnsureIsPaused(aProofOfUnlock);
+ ClearIdleToken();
+}
+
+TimeStamp IdlePeriodState::GetIdleDeadlineInternal(
+ bool aIsPeek, const MutexAutoUnlock& aProofOfUnlock) {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Why are we touching idle state off the main thread?");
+
+ bool shuttingDown;
+ TimeStamp localIdleDeadline =
+ GetLocalIdleDeadline(shuttingDown, aProofOfUnlock);
+ if (!localIdleDeadline) {
+ if (!aIsPeek) {
+ EnsureIsPaused(aProofOfUnlock);
+ ClearIdleToken();
+ }
+ return TimeStamp();
+ }
+
+ TimeStamp idleDeadline =
+ mHasPendingEventsPromisedIdleEvent || shuttingDown
+ ? localIdleDeadline
+ : GetIdleToken(localIdleDeadline, aProofOfUnlock);
+ if (!idleDeadline) {
+ if (!aIsPeek) {
+ EnsureIsPaused(aProofOfUnlock);
+
+ // Don't call ClearIdleToken() here, since we may have a pending
+ // request already.
+ //
+ // RequestIdleToken can do all sorts of IPC stuff that might
+ // take mutexes. This is one reason why we need the
+ // MutexAutoUnlock reference!
+ RequestIdleToken(localIdleDeadline);
+ }
+ return TimeStamp();
+ }
+
+ if (!aIsPeek) {
+ EnsureIsActive();
+ }
+ return idleDeadline;
+}
+
+TimeStamp IdlePeriodState::GetLocalIdleDeadline(
+ bool& aShuttingDown, const MutexAutoUnlock& aProofOfUnlock) {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Why are we touching idle state off the main thread?");
+ // If we are shutting down, we won't honor the idle period, and we will
+ // always process idle runnables. This will ensure that the idle queue
+ // gets exhausted at shutdown time to prevent intermittently leaking
+ // some runnables inside that queue and even worse potentially leaving
+ // some important cleanup work unfinished.
+ if (gXPCOMThreadsShutDown ||
+ nsThreadManager::get().GetCurrentThread()->ShuttingDown()) {
+ aShuttingDown = true;
+ return TimeStamp::Now();
+ }
+
+ aShuttingDown = false;
+ TimeStamp idleDeadline;
+ // This GetIdlePeriodHint() call is the reason we need a MutexAutoUnlock here.
+ mIdlePeriod->GetIdlePeriodHint(&idleDeadline);
+
+ // If HasPendingEvents() has been called and it has returned true because of
+ // pending idle events, there is a risk that we may decide here that we aren't
+ // idle and return null, in which case HasPendingEvents() has effectively
+ // lied. Since we can't go back and fix the past, we have to adjust what we
+ // do here and forcefully pick the idle queue task here. Note that this means
+ // that we are choosing to run a task from the idle queue when we would
+ // normally decide that we aren't in an idle period, but this can only happen
+ // if we fall out of the idle period in between the call to HasPendingEvents()
+ // and here, which should hopefully be quite rare. We are effectively
+ // choosing to prioritize the sanity of our API semantics over the optimal
+ // scheduling.
+ if (!mHasPendingEventsPromisedIdleEvent &&
+ (!idleDeadline || idleDeadline < TimeStamp::Now())) {
+ return TimeStamp();
+ }
+ if (mHasPendingEventsPromisedIdleEvent && !idleDeadline) {
+ // If HasPendingEvents() has been called and it has returned true, but we're
+ // no longer in the idle period, we must return a valid timestamp to pretend
+ // that we are still in the idle period.
+ return TimeStamp::Now();
+ }
+ return idleDeadline;
+}
+
+TimeStamp IdlePeriodState::GetIdleToken(TimeStamp aLocalIdlePeriodHint,
+ const MutexAutoUnlock& aProofOfUnlock) {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Why are we touching idle state off the main thread?");
+
+ if (!ShouldGetIdleToken()) {
+ return aLocalIdlePeriodHint;
+ }
+
+ if (mIdleToken) {
+ TimeStamp now = TimeStamp::Now();
+ if (mIdleToken < now) {
+ ClearIdleToken();
+ return mIdleToken;
+ }
+ return mIdleToken < aLocalIdlePeriodHint ? mIdleToken
+ : aLocalIdlePeriodHint;
+ }
+ return TimeStamp();
+}
+
+void IdlePeriodState::RequestIdleToken(TimeStamp aLocalIdlePeriodHint) {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Why are we touching idle state off the main thread?");
+ MOZ_ASSERT(!mActive);
+
+ if (!mIdleSchedulerInitialized) {
+ mIdleSchedulerInitialized = true;
+ if (ShouldGetIdleToken()) {
+ // For now cross-process idle scheduler is supported only on the main
+ // threads of the child processes.
+ mIdleScheduler = ipc::IdleSchedulerChild::GetMainThreadIdleScheduler();
+ if (mIdleScheduler) {
+ mIdleScheduler->Init(this);
+ }
+ }
+ }
+
+ if (mIdleScheduler && !mIdleRequestId) {
+ TimeStamp now = TimeStamp::Now();
+ if (aLocalIdlePeriodHint <= now) {
+ return;
+ }
+
+ mIdleRequestId = ++sIdleRequestCounter;
+ mIdleScheduler->SendRequestIdleTime(mIdleRequestId,
+ aLocalIdlePeriodHint - now);
+ }
+}
+
+void IdlePeriodState::SetIdleToken(uint64_t aId, TimeDuration aDuration) {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Why are we touching idle state off the main thread?");
+
+ // We check the request ID. It's possible that the server may be granting a
+ // an ealier request that the client has since cancelled and re-requested.
+ if (mIdleRequestId == aId) {
+ mIdleToken = TimeStamp::Now() + aDuration;
+ }
+}
+
+void IdlePeriodState::SetActive() {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Why are we touching idle state off the main thread?");
+ MOZ_ASSERT(!mActive);
+ if (mIdleScheduler) {
+ mIdleScheduler->SetActive();
+ }
+ mActive = true;
+}
+
+void IdlePeriodState::SetPaused(const MutexAutoUnlock& aProofOfUnlock) {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Why are we touching idle state off the main thread?");
+ MOZ_ASSERT(mActive);
+ if (mIdleScheduler && mIdleScheduler->SetPaused()) {
+ // We may have gotten a free cpu core for running idle tasks.
+ // We don't try to catch the case when there are prioritized processes
+ // running.
+
+ // This SendSchedule call is why we need the MutexAutoUnlock here, because
+ // IPC can do weird things with mutexes.
+ mIdleScheduler->SendSchedule();
+ }
+ mActive = false;
+}
+
+void IdlePeriodState::ClearIdleToken() {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Why are we touching idle state off the main thread?");
+
+ if (mIdleRequestId) {
+ if (mIdleScheduler) {
+ // This SendIdleTimeUsed call is why we need to not be holding
+ // any locks here, because IPC can do weird things with mutexes.
+ // Ideally we'd have a MutexAutoUnlock& reference here, but some
+ // callers end up here while just not holding any locks at all.
+ mIdleScheduler->SendIdleTimeUsed(mIdleRequestId);
+ }
+ mIdleRequestId = 0;
+ mIdleToken = TimeStamp();
+ }
+}
+
+bool IdlePeriodState::ShouldGetIdleToken() {
+ return StaticPrefs::idle_period_cross_process_scheduling() &&
+ XRE_IsContentProcess();
+}
+} // namespace mozilla
diff --git a/xpcom/threads/IdlePeriodState.h b/xpcom/threads/IdlePeriodState.h
new file mode 100644
index 0000000000..58303b454b
--- /dev/null
+++ b/xpcom/threads/IdlePeriodState.h
@@ -0,0 +1,194 @@
+/* -*- 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_IdlePeriodState_h
+#define mozilla_IdlePeriodState_h
+
+/**
+ * A class for tracking the state of our idle period. This includes keeping
+ * track of both the state of our process-local idle period estimate and, for
+ * content processes, managing communication with the parent process for
+ * cross-pprocess idle detection.
+ */
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "nsCOMPtr.h"
+
+#include <stdint.h>
+
+class nsIIdlePeriod;
+
+namespace mozilla {
+class TaskManager;
+namespace ipc {
+class IdleSchedulerChild;
+} // namespace ipc
+
+class IdlePeriodState {
+ public:
+ explicit IdlePeriodState(already_AddRefed<nsIIdlePeriod>&& aIdlePeriod);
+
+ ~IdlePeriodState();
+
+ // Integration with memory reporting.
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
+
+ // Notification that whoever we are tracking idle state for has found a
+ // non-idle task to process.
+ //
+ // Must not be called while holding any locks.
+ void FlagNotIdle();
+
+ // Notification that whoever we are tracking idle state for has no more
+ // tasks (idle or not) to process.
+ //
+ // aProofOfUnlock is the proof that our caller unlocked its mutex.
+ void RanOutOfTasks(const MutexAutoUnlock& aProofOfUnlock);
+
+ // Notification that whoever we are tracking idle state has idle tasks that
+ // they are considering ready to run and that we should keep claiming they are
+ // ready to run until they call ForgetPendingTaskGuarantee().
+ void EnforcePendingTaskGuarantee() {
+ mHasPendingEventsPromisedIdleEvent = true;
+ }
+
+ // Notification that whoever we are tracking idle state for is done with our
+ // "we have an idle event ready to run" guarantee. When this happens, we can
+ // reset mHasPendingEventsPromisedIdleEvent to false, because we have
+ // fulfilled our contract.
+ void ForgetPendingTaskGuarantee() {
+ mHasPendingEventsPromisedIdleEvent = false;
+ }
+
+ // Update our cached idle deadline so consumers can use it while holding
+ // locks. Consumers must ClearCachedIdleDeadline() once they are done.
+ void UpdateCachedIdleDeadline(const MutexAutoUnlock& aProofOfUnlock) {
+ mCachedIdleDeadline = GetIdleDeadlineInternal(false, aProofOfUnlock);
+ }
+
+ // Reset our cached idle deadline, so we stop allowing idle runnables to run.
+ void ClearCachedIdleDeadline() { mCachedIdleDeadline = TimeStamp(); }
+
+ // Get the current cached idle deadline. This may return a null timestamp.
+ TimeStamp GetCachedIdleDeadline() { return mCachedIdleDeadline; }
+
+ // Peek our current idle deadline into mCachedIdleDeadline. This can cause
+ // mCachedIdleDeadline to be a null timestamp (which means we are not idle
+ // right now). This method does not have any side-effects on our state, apart
+ // from guaranteeing that if it returns non-null then GetDeadlineForIdleTask
+ // will return non-null until ForgetPendingTaskGuarantee() is called, and its
+ // effects on mCachedIdleDeadline.
+ //
+ // aProofOfUnlock is the proof that our caller unlocked its mutex.
+ void CachePeekedIdleDeadline(const MutexAutoUnlock& aProofOfUnlock) {
+ mCachedIdleDeadline = GetIdleDeadlineInternal(true, aProofOfUnlock);
+ }
+
+ void SetIdleToken(uint64_t aId, TimeDuration aDuration);
+
+ bool IsActive() { return mActive; }
+
+ protected:
+ void EnsureIsActive() {
+ if (!mActive) {
+ SetActive();
+ }
+ }
+
+ void EnsureIsPaused(const MutexAutoUnlock& aProofOfUnlock) {
+ if (mActive) {
+ SetPaused(aProofOfUnlock);
+ }
+ }
+
+ // Returns a null TimeStamp if we're not in the idle period.
+ TimeStamp GetLocalIdleDeadline(bool& aShuttingDown,
+ const MutexAutoUnlock& aProofOfUnlock);
+
+ // Gets the idle token, which is the end time of the idle period.
+ //
+ // aProofOfUnlock is the proof that our caller unlocked its mutex.
+ TimeStamp GetIdleToken(TimeStamp aLocalIdlePeriodHint,
+ const MutexAutoUnlock& aProofOfUnlock);
+
+ // In case of child processes, requests idle time from the cross-process
+ // idle scheduler.
+ void RequestIdleToken(TimeStamp aLocalIdlePeriodHint);
+
+ // Mark that we don't have idle time to use, nor are expecting to get an idle
+ // token from the idle scheduler. This must be called while not holding any
+ // locks, but some of the callers aren't holding locks to start with, so
+ // consumers just need to make sure they are not holding locks when they call
+ // this.
+ void ClearIdleToken();
+
+ // SetActive should be called when the event queue is running any type of
+ // tasks.
+ void SetActive();
+ // SetPaused should be called once the event queue doesn't have more
+ // tasks to process, or is waiting for the idle token.
+ //
+ // aProofOfUnlock is the proof that our caller unlocked its mutex.
+ void SetPaused(const MutexAutoUnlock& aProofOfUnlock);
+
+ // Get or peek our idle deadline. When peeking, we generally don't change any
+ // of our internal state. When getting, we may request an idle token as
+ // needed.
+ //
+ // aProofOfUnlock is the proof that our caller unlocked its mutex.
+ TimeStamp GetIdleDeadlineInternal(bool aIsPeek,
+ const MutexAutoUnlock& aProofOfUnlock);
+
+ // Whether we should be getting an idle token (i.e. are a content process
+ // and are using cross process idle scheduling).
+ bool ShouldGetIdleToken();
+
+ // Set to true if we have claimed we have a ready-to-run idle task when asked.
+ // In that case, we will ensure that we allow at least one task to run when
+ // someone tries to run a task, even if we have run out of idle period at that
+ // point. This ensures that we never fail to produce a task to run if we
+ // claim we have a task ready to run.
+ bool mHasPendingEventsPromisedIdleEvent = false;
+
+ // mIdlePeriod keeps track of the current idle period. Calling
+ // mIdlePeriod->GetIdlePeriodHint() will give an estimate of when
+ // the current idle period will end.
+ nsCOMPtr<nsIIdlePeriod> mIdlePeriod;
+
+ // If non-null, this timestamp represents the end time of the idle period. An
+ // idle period starts when we get the idle token from the parent process and
+ // ends when either there are no more things we want to run at idle priority
+ // or mIdleToken < TimeStamp::Now(), so we have reached our idle deadline.
+ TimeStamp mIdleToken;
+
+ // The id of the last idle request to the cross-process idle scheduler.
+ uint64_t mIdleRequestId = 0;
+
+ // If we're in a content process, we use mIdleScheduler to communicate with
+ // the parent process for purposes of cross-process idle tracking.
+ RefPtr<mozilla::ipc::IdleSchedulerChild> mIdleScheduler;
+
+ // Our cached idle deadline. This is set by UpdateCachedIdleDeadline() and
+ // cleared by ClearCachedIdleDeadline(). Consumers should do the former while
+ // not holding any locks, but may do the latter while holding locks.
+ TimeStamp mCachedIdleDeadline;
+
+ // mIdleSchedulerInitialized is true if our mIdleScheduler has been
+ // initialized. It may be null even after initialiazation, in various
+ // situations.
+ bool mIdleSchedulerInitialized = false;
+
+ // mActive is true when the PrioritizedEventQueue or TaskController we are
+ // associated with is running tasks.
+ bool mActive = true;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_IdlePeriodState_h
diff --git a/xpcom/threads/IdleTaskRunner.cpp b/xpcom/threads/IdleTaskRunner.cpp
new file mode 100644
index 0000000000..404410bec8
--- /dev/null
+++ b/xpcom/threads/IdleTaskRunner.cpp
@@ -0,0 +1,181 @@
+/* -*- 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 "IdleTaskRunner.h"
+#include "nsRefreshDriver.h"
+#include "nsComponentManagerUtils.h"
+
+namespace mozilla {
+
+already_AddRefed<IdleTaskRunner> IdleTaskRunner::Create(
+ const CallbackType& aCallback, const char* aRunnableName,
+ uint32_t aMaxDelay, int64_t aNonIdleBudget, bool aRepeating,
+ const MayStopProcessingCallbackType& aMayStopProcessing) {
+ if (aMayStopProcessing && aMayStopProcessing()) {
+ return nullptr;
+ }
+
+ RefPtr<IdleTaskRunner> runner =
+ new IdleTaskRunner(aCallback, aRunnableName, aMaxDelay, aNonIdleBudget,
+ aRepeating, aMayStopProcessing);
+ runner->Schedule(false); // Initial scheduling shouldn't use idle dispatch.
+ return runner.forget();
+}
+
+IdleTaskRunner::IdleTaskRunner(
+ const CallbackType& aCallback, const char* aRunnableName,
+ uint32_t aMaxDelay, int64_t aNonIdleBudget, bool aRepeating,
+ const MayStopProcessingCallbackType& aMayStopProcessing)
+ : CancelableIdleRunnable(aRunnableName),
+ mCallback(aCallback),
+ mDelay(aMaxDelay),
+ mBudget(TimeDuration::FromMilliseconds(aNonIdleBudget)),
+ mRepeating(aRepeating),
+ mTimerActive(false),
+ mMayStopProcessing(aMayStopProcessing),
+ mName(aRunnableName) {}
+
+NS_IMETHODIMP
+IdleTaskRunner::Run() {
+ if (!mCallback) {
+ return NS_OK;
+ }
+
+ // Deadline is null when called from timer.
+ TimeStamp now = TimeStamp::Now();
+ bool deadLineWasNull = mDeadline.IsNull();
+ bool didRun = false;
+ bool allowIdleDispatch = false;
+ if (deadLineWasNull || ((now + mBudget) < mDeadline)) {
+ CancelTimer();
+ didRun = mCallback(mDeadline);
+ // If we didn't do meaningful work, don't schedule using immediate
+ // idle dispatch, since that could lead to a loop until the idle
+ // period ends.
+ allowIdleDispatch = didRun;
+ } else if (now >= mDeadline) {
+ allowIdleDispatch = true;
+ }
+
+ if (mCallback && (mRepeating || !didRun)) {
+ Schedule(allowIdleDispatch);
+ } else {
+ mCallback = nullptr;
+ }
+
+ return NS_OK;
+}
+
+static void TimedOut(nsITimer* aTimer, void* aClosure) {
+ RefPtr<IdleTaskRunner> runnable = static_cast<IdleTaskRunner*>(aClosure);
+ runnable->Run();
+}
+
+void IdleTaskRunner::SetDeadline(mozilla::TimeStamp aDeadline) {
+ mDeadline = aDeadline;
+}
+
+void IdleTaskRunner::SetBudget(int64_t aBudget) {
+ mBudget = TimeDuration::FromMilliseconds(aBudget);
+}
+
+void IdleTaskRunner::SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aTarget->IsOnCurrentThread());
+ // aTarget is always the main thread event target provided from
+ // NS_DispatchToCurrentThreadQueue(). We ignore aTarget here to ensure that
+ // CollectorRunner always run specifically the main thread.
+ SetTimerInternal(aDelay);
+}
+
+nsresult IdleTaskRunner::Cancel() {
+ CancelTimer();
+ mTimer = nullptr;
+ mScheduleTimer = nullptr;
+ mCallback = nullptr;
+ return NS_OK;
+}
+
+static void ScheduleTimedOut(nsITimer* aTimer, void* aClosure) {
+ RefPtr<IdleTaskRunner> runnable = static_cast<IdleTaskRunner*>(aClosure);
+ runnable->Schedule(true);
+}
+
+void IdleTaskRunner::Schedule(bool aAllowIdleDispatch) {
+ if (!mCallback) {
+ return;
+ }
+
+ if (mMayStopProcessing && mMayStopProcessing()) {
+ Cancel();
+ return;
+ }
+
+ mDeadline = TimeStamp();
+ TimeStamp now = TimeStamp::Now();
+ TimeStamp hint = nsRefreshDriver::GetIdleDeadlineHint(now);
+ if (hint != now) {
+ // RefreshDriver is ticking, let it schedule the idle dispatch.
+ nsRefreshDriver::DispatchIdleRunnableAfterTickUnlessExists(this, mDelay);
+ // Ensure we get called at some point, even if RefreshDriver is stopped.
+ SetTimerInternal(mDelay);
+ } else {
+ // RefreshDriver doesn't seem to be running.
+ if (aAllowIdleDispatch) {
+ nsCOMPtr<nsIRunnable> runnable = this;
+ SetTimerInternal(mDelay);
+ NS_DispatchToCurrentThreadQueue(runnable.forget(),
+ EventQueuePriority::Idle);
+ } else {
+ if (!mScheduleTimer) {
+ mScheduleTimer = NS_NewTimer();
+ if (!mScheduleTimer) {
+ return;
+ }
+ } else {
+ mScheduleTimer->Cancel();
+ }
+ // We weren't allowed to do idle dispatch immediately, do it after a
+ // short timeout.
+ mScheduleTimer->InitWithNamedFuncCallback(
+ ScheduleTimedOut, this, 16 /* ms */,
+ nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, mName);
+ }
+ }
+}
+
+IdleTaskRunner::~IdleTaskRunner() { CancelTimer(); }
+
+void IdleTaskRunner::CancelTimer() {
+ nsRefreshDriver::CancelIdleRunnable(this);
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+ if (mScheduleTimer) {
+ mScheduleTimer->Cancel();
+ }
+ mTimerActive = false;
+}
+
+void IdleTaskRunner::SetTimerInternal(uint32_t aDelay) {
+ if (mTimerActive) {
+ return;
+ }
+
+ if (!mTimer) {
+ mTimer = NS_NewTimer();
+ } else {
+ mTimer->Cancel();
+ }
+
+ if (mTimer) {
+ mTimer->InitWithNamedFuncCallback(TimedOut, this, aDelay,
+ nsITimer::TYPE_ONE_SHOT, mName);
+ mTimerActive = true;
+ }
+}
+
+} // end of namespace mozilla
diff --git a/xpcom/threads/IdleTaskRunner.h b/xpcom/threads/IdleTaskRunner.h
new file mode 100644
index 0000000000..93a55b3d7f
--- /dev/null
+++ b/xpcom/threads/IdleTaskRunner.h
@@ -0,0 +1,89 @@
+/* -*- 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 IdleTaskRunner_h
+#define IdleTaskRunner_h
+
+#include "mozilla/TimeStamp.h"
+#include "mozilla/TaskCategory.h"
+#include "nsThreadUtils.h"
+#include <functional>
+
+namespace mozilla {
+
+// A general purpose repeating callback runner (it can be configured to a
+// one-time runner, too.) If it is running repeatedly, one has to either
+// explicitly Cancel() the runner or have MayStopProcessing() callback return
+// true to completely remove the runner.
+class IdleTaskRunner final : public CancelableIdleRunnable {
+ public:
+ // Return true if some meaningful work was done.
+ using CallbackType = std::function<bool(TimeStamp aDeadline)>;
+
+ // A callback for "stop processing" decision. Return true to
+ // stop processing. This can be an alternative to Cancel() or
+ // work together in different way.
+ using MayStopProcessingCallbackType = std::function<bool()>;
+
+ public:
+ // An IdleTaskRunner will attempt to run in idle time, with a budget computed
+ // based on a (capped) estimate for how much idle time is available. If there
+ // is no idle time within `aMaxDelay` ms, it will fall back to running using
+ // a specified `aNonIdleBudget`.
+ static already_AddRefed<IdleTaskRunner> Create(
+ const CallbackType& aCallback, const char* aRunnableName,
+ uint32_t aMaxDelay, int64_t aNonIdleBudget, bool aRepeating,
+ const MayStopProcessingCallbackType& aMayStopProcessing);
+
+ NS_IMETHOD Run() override;
+
+ // (Used by the task triggering code.) Record the end of the current idle
+ // period, or null if not running during idle time.
+ void SetDeadline(mozilla::TimeStamp aDeadline) override;
+
+ void SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) override;
+
+ // Update the non-idle time budgeted for this callback. This really only
+ // makes sense for a repeating runner.
+ void SetBudget(int64_t aBudget);
+
+ nsresult Cancel() override;
+ void Schedule(bool aAllowIdleDispatch);
+
+ private:
+ explicit IdleTaskRunner(
+ const CallbackType& aCallback, const char* aRunnableName,
+ uint32_t aMaxDelay, int64_t aNonIdleBudget, bool aRepeating,
+ const MayStopProcessingCallbackType& aMayStopProcessing);
+ ~IdleTaskRunner();
+ void CancelTimer();
+ void SetTimerInternal(uint32_t aDelay);
+
+ nsCOMPtr<nsITimer> mTimer;
+ nsCOMPtr<nsITimer> mScheduleTimer;
+ CallbackType mCallback;
+
+ // Wait this long for idle time before giving up and running a non-idle
+ // callback.
+ uint32_t mDelay;
+
+ // If running during idle time, the expected end of the current idle period.
+ // The null timestamp when the run is triggered by aMaxDelay instead of idle.
+ TimeStamp mDeadline;
+
+ // The expected amount of time the callback will run for, when not running
+ // during idle time.
+ TimeDuration mBudget;
+
+ bool mRepeating;
+ bool mTimerActive;
+ MayStopProcessingCallbackType mMayStopProcessing;
+ const char* mName;
+};
+
+} // end of namespace mozilla.
+
+#endif
diff --git a/xpcom/threads/InputEventStatistics.cpp b/xpcom/threads/InputEventStatistics.cpp
new file mode 100644
index 0000000000..66391d17cb
--- /dev/null
+++ b/xpcom/threads/InputEventStatistics.cpp
@@ -0,0 +1,68 @@
+/* -*- 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 "InputEventStatistics.h"
+
+#include "mozilla/Preferences.h"
+#include "nsRefreshDriver.h"
+
+namespace mozilla {
+
+/*static*/ InputEventStatistics& InputEventStatistics::Get() {
+ static UniquePtr<InputEventStatistics> sInstance;
+ if (!sInstance) {
+ sInstance = MakeUnique<InputEventStatistics>(ConstructorCookie());
+ ClearOnShutdown(&sInstance);
+ }
+ return *sInstance;
+}
+
+TimeDuration InputEventStatistics::TimeDurationCircularBuffer::GetMean() {
+ return mTotal / (int64_t)mSize;
+}
+
+InputEventStatistics::InputEventStatistics(ConstructorCookie&&)
+ : mEnable(false) {
+ MOZ_ASSERT(Preferences::IsServiceAvailable());
+ uint32_t inputDuration = Preferences::GetUint(
+ "input_event_queue.default_duration_per_event", sDefaultInputDuration);
+
+ TimeDuration defaultDuration = TimeDuration::FromMilliseconds(inputDuration);
+
+ uint32_t count = Preferences::GetUint(
+ "input_event_queue.count_for_prediction", sInputCountForPrediction);
+
+ mLastInputDurations =
+ MakeUnique<TimeDurationCircularBuffer>(count, defaultDuration);
+
+ uint32_t maxDuration = Preferences::GetUint("input_event_queue.duration.max",
+ sMaxReservedTimeForHandlingInput);
+
+ uint32_t minDuration = Preferences::GetUint("input_event_queue.duration.min",
+ sMinReservedTimeForHandlingInput);
+
+ mMaxInputDuration = TimeDuration::FromMilliseconds(maxDuration);
+ mMinInputDuration = TimeDuration::FromMilliseconds(minDuration);
+}
+
+TimeStamp InputEventStatistics::GetInputHandlingStartTime(
+ uint32_t aInputCount) {
+ MOZ_ASSERT(mEnable);
+ Maybe<TimeStamp> nextTickHint = nsRefreshDriver::GetNextTickHint();
+
+ if (nextTickHint.isNothing()) {
+ // Return a past time to process input events immediately.
+ return TimeStamp::Now() - TimeDuration::FromMilliseconds(1);
+ }
+ TimeDuration inputCost = mLastInputDurations->GetMean() * aInputCount;
+ inputCost = inputCost > mMaxInputDuration ? mMaxInputDuration
+ : inputCost < mMinInputDuration ? mMinInputDuration
+ : inputCost;
+
+ return nextTickHint.value() - inputCost;
+}
+
+} // namespace mozilla
diff --git a/xpcom/threads/InputEventStatistics.h b/xpcom/threads/InputEventStatistics.h
new file mode 100644
index 0000000000..e5531302b5
--- /dev/null
+++ b/xpcom/threads/InputEventStatistics.h
@@ -0,0 +1,102 @@
+/* -*- 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/. */
+
+#if !defined(InputEventStatistics_h_)
+# define InputEventStatistics_h_
+
+# include "mozilla/TimeStamp.h"
+# include "mozilla/UniquePtr.h"
+# include "nsTArray.h"
+
+namespace mozilla {
+
+class InputEventStatistics {
+ // The default amount of time (milliseconds) required for handling a input
+ // event.
+ static const uint16_t sDefaultInputDuration = 1;
+
+ // The number of processed input events we use to predict the amount of time
+ // required to process the following input events.
+ static const uint16_t sInputCountForPrediction = 9;
+
+ // The default maximum and minimum time (milliseconds) we reserve for handling
+ // input events in each frame.
+ static const uint16_t sMaxReservedTimeForHandlingInput = 8;
+ static const uint16_t sMinReservedTimeForHandlingInput = 1;
+
+ class TimeDurationCircularBuffer {
+ int16_t mSize;
+ int16_t mCurrentIndex;
+ nsTArray<TimeDuration> mBuffer;
+ TimeDuration mTotal;
+
+ public:
+ TimeDurationCircularBuffer(uint32_t aSize, TimeDuration& aDefaultValue)
+ : mSize(aSize), mCurrentIndex(0) {
+ mSize = mSize == 0 ? sInputCountForPrediction : mSize;
+ for (int16_t index = 0; index < mSize; ++index) {
+ mBuffer.AppendElement(aDefaultValue);
+ mTotal += aDefaultValue;
+ }
+ }
+
+ void Insert(TimeDuration& aDuration) {
+ mTotal += (aDuration - mBuffer[mCurrentIndex]);
+ mBuffer[mCurrentIndex++] = aDuration;
+ if (mCurrentIndex == mSize) {
+ mCurrentIndex = 0;
+ }
+ }
+
+ TimeDuration GetMean();
+ };
+
+ UniquePtr<TimeDurationCircularBuffer> mLastInputDurations;
+ TimeDuration mMaxInputDuration;
+ TimeDuration mMinInputDuration;
+ bool mEnable;
+
+ // We'd like to have our constructor and destructor be private to enforce our
+ // singleton, but because UniquePtr needs to be able to destruct our class we
+ // can't. This is a trick that ensures that we're the only code that can
+ // construct ourselves: nobody else can access ConstructorCookie and therefore
+ // nobody else can construct an InputEventStatistics.
+ struct ConstructorCookie {};
+
+ public:
+ explicit InputEventStatistics(ConstructorCookie&&);
+ ~InputEventStatistics() = default;
+
+ static InputEventStatistics& Get();
+
+ void UpdateInputDuration(TimeDuration aDuration) {
+ if (!mEnable) {
+ return;
+ }
+ mLastInputDurations->Insert(aDuration);
+ }
+
+ TimeStamp GetInputHandlingStartTime(uint32_t aInputCount);
+
+ void SetEnable(bool aEnable) { mEnable = aEnable; }
+};
+
+class MOZ_RAII AutoTimeDurationHelper final {
+ public:
+ AutoTimeDurationHelper() { mStartTime = TimeStamp::Now(); }
+
+ ~AutoTimeDurationHelper() {
+ InputEventStatistics::Get().UpdateInputDuration(TimeStamp::Now() -
+ mStartTime);
+ }
+
+ private:
+ TimeStamp mStartTime;
+};
+
+} // namespace mozilla
+
+#endif // InputEventStatistics_h_
diff --git a/xpcom/threads/InputTaskManager.cpp b/xpcom/threads/InputTaskManager.cpp
new file mode 100644
index 0000000000..8464978268
--- /dev/null
+++ b/xpcom/threads/InputTaskManager.cpp
@@ -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/. */
+
+#include "InputTaskManager.h"
+
+namespace mozilla {
+
+StaticRefPtr<InputTaskManager> InputTaskManager::gInputTaskManager;
+
+void InputTaskManager::EnableInputEventPrioritization() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mInputQueueState == STATE_DISABLED);
+ mInputQueueState = STATE_ENABLED;
+ mInputHandlingStartTime = TimeStamp();
+}
+
+void InputTaskManager::FlushInputEventPrioritization() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mInputQueueState == STATE_ENABLED ||
+ mInputQueueState == STATE_SUSPEND);
+ mInputQueueState =
+ mInputQueueState == STATE_ENABLED ? STATE_FLUSHING : STATE_SUSPEND;
+}
+
+void InputTaskManager::SuspendInputEventPrioritization() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mInputQueueState == STATE_ENABLED ||
+ mInputQueueState == STATE_FLUSHING);
+ mInputQueueState = STATE_SUSPEND;
+}
+
+void InputTaskManager::ResumeInputEventPrioritization() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mInputQueueState == STATE_SUSPEND);
+ mInputQueueState = STATE_ENABLED;
+}
+
+int32_t InputTaskManager::GetPriorityModifierForEventLoopTurn(
+ const MutexAutoLock& aProofOfLock) {
+ size_t inputCount = PendingTaskCount();
+ if (State() == STATE_ENABLED && InputHandlingStartTime().IsNull() &&
+ inputCount > 0) {
+ SetInputHandlingStartTime(
+ InputEventStatistics::Get().GetInputHandlingStartTime(inputCount));
+ }
+
+ if (inputCount > 0 && (State() == InputTaskManager::STATE_FLUSHING ||
+ (State() == InputTaskManager::STATE_ENABLED &&
+ !InputHandlingStartTime().IsNull() &&
+ TimeStamp::Now() > InputHandlingStartTime()))) {
+ return 0;
+ }
+
+ int32_t modifier = static_cast<int32_t>(EventQueuePriority::InputLow) -
+ static_cast<int32_t>(EventQueuePriority::MediumHigh);
+ return modifier;
+}
+
+void InputTaskManager::WillRunTask() {
+ TaskManager::WillRunTask();
+ mStartTimes.AppendElement(TimeStamp::Now());
+}
+
+void InputTaskManager::DidRunTask() {
+ TaskManager::DidRunTask();
+ MOZ_ASSERT(!mStartTimes.IsEmpty());
+ TimeStamp start = mStartTimes.PopLastElement();
+ InputEventStatistics::Get().UpdateInputDuration(TimeStamp::Now() - start);
+}
+
+// static
+void InputTaskManager::Init() { gInputTaskManager = new InputTaskManager(); }
+
+} // namespace mozilla
diff --git a/xpcom/threads/InputTaskManager.h b/xpcom/threads/InputTaskManager.h
new file mode 100644
index 0000000000..ac3e44b516
--- /dev/null
+++ b/xpcom/threads/InputTaskManager.h
@@ -0,0 +1,97 @@
+/* -*- 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_InputTaskManager_h
+#define mozilla_InputTaskManager_h
+
+#include "TaskController.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/StaticPrefs_dom.h"
+
+namespace mozilla {
+
+class InputTaskManager : public TaskManager {
+ public:
+ int32_t GetPriorityModifierForEventLoopTurn(
+ const MutexAutoLock& aProofOfLock) final;
+ void WillRunTask() final;
+ void DidRunTask() final;
+
+ enum InputEventQueueState {
+ STATE_DISABLED,
+ STATE_FLUSHING,
+ STATE_SUSPEND,
+ STATE_ENABLED
+ };
+
+ void EnableInputEventPrioritization();
+ void FlushInputEventPrioritization();
+ void SuspendInputEventPrioritization();
+ void ResumeInputEventPrioritization();
+
+ InputEventQueueState State() { return mInputQueueState; }
+
+ void SetState(InputEventQueueState aState) { mInputQueueState = aState; }
+
+ TimeStamp InputHandlingStartTime() { return mInputHandlingStartTime; }
+
+ void SetInputHandlingStartTime(TimeStamp aStartTime) {
+ mInputHandlingStartTime = aStartTime;
+ }
+
+ static InputTaskManager* Get() { return gInputTaskManager.get(); }
+ static void Cleanup() { gInputTaskManager = nullptr; }
+ static void Init();
+
+ bool IsSuspended(const MutexAutoLock& aProofOfLock) override {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mSuspensionLevel > 0;
+ }
+
+ bool IsSuspended() {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mSuspensionLevel > 0;
+ }
+
+ void IncSuspensionLevel() {
+ MOZ_ASSERT(NS_IsMainThread());
+ ++mSuspensionLevel;
+ }
+
+ void DecSuspensionLevel() {
+ MOZ_ASSERT(NS_IsMainThread());
+ --mSuspensionLevel;
+ }
+
+ static bool CanSuspendInputEvent() {
+ // Ensure it's content process because InputTaskManager only
+ // works in e10s.
+ //
+ // Input tasks will have nullptr as their task manager when the
+ // event queue state is STATE_DISABLED, so we can't suspend
+ // input events.
+ return XRE_IsContentProcess() &&
+ StaticPrefs::dom_input_events_canSuspendInBCG_enabled() &&
+ InputTaskManager::Get()->State() !=
+ InputEventQueueState::STATE_DISABLED;
+ }
+
+ private:
+ InputTaskManager() : mInputQueueState(STATE_DISABLED) {}
+
+ TimeStamp mInputHandlingStartTime;
+ Atomic<InputEventQueueState> mInputQueueState;
+ AutoTArray<TimeStamp, 4> mStartTimes;
+
+ static StaticRefPtr<InputTaskManager> gInputTaskManager;
+
+ // Number of BCGs have asked InputTaskManager to suspend input events
+ uint32_t mSuspensionLevel = 0;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_InputTaskManager_h
diff --git a/xpcom/threads/LazyIdleThread.cpp b/xpcom/threads/LazyIdleThread.cpp
new file mode 100644
index 0000000000..f4f93de3ce
--- /dev/null
+++ b/xpcom/threads/LazyIdleThread.cpp
@@ -0,0 +1,628 @@
+/* -*- 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 "LazyIdleThread.h"
+
+#include "nsIObserverService.h"
+
+#include "GeckoProfiler.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Services.h"
+
+#ifdef DEBUG
+# define ASSERT_OWNING_THREAD() \
+ do { \
+ MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread()); \
+ } while (0)
+#else
+# define ASSERT_OWNING_THREAD() /* nothing */
+#endif
+
+namespace mozilla {
+
+LazyIdleThread::LazyIdleThread(uint32_t aIdleTimeoutMS, const nsACString& aName,
+ ShutdownMethod aShutdownMethod,
+ nsIObserver* aIdleObserver)
+ : mMutex("LazyIdleThread::mMutex"),
+ mOwningEventTarget(GetCurrentSerialEventTarget()),
+ mIdleObserver(aIdleObserver),
+ mQueuedRunnables(nullptr),
+ mIdleTimeoutMS(aIdleTimeoutMS),
+ mPendingEventCount(0),
+ mIdleNotificationCount(0),
+ mShutdownMethod(aShutdownMethod),
+ mShutdown(false),
+ mThreadIsShuttingDown(false),
+ mIdleTimeoutEnabled(true),
+ mName(aName) {
+ MOZ_ASSERT(mOwningEventTarget, "Need owning thread!");
+}
+
+LazyIdleThread::~LazyIdleThread() {
+ ASSERT_OWNING_THREAD();
+
+ Shutdown();
+}
+
+void LazyIdleThread::SetWeakIdleObserver(nsIObserver* aObserver) {
+ ASSERT_OWNING_THREAD();
+
+ if (mShutdown) {
+ NS_WARNING_ASSERTION(!aObserver,
+ "Setting an observer after Shutdown was called!");
+ return;
+ }
+
+ mIdleObserver = aObserver;
+}
+
+void LazyIdleThread::DisableIdleTimeout() {
+ ASSERT_OWNING_THREAD();
+ if (!mIdleTimeoutEnabled) {
+ return;
+ }
+ mIdleTimeoutEnabled = false;
+
+ if (mIdleTimer && NS_FAILED(mIdleTimer->Cancel())) {
+ NS_WARNING("Failed to cancel timer!");
+ }
+
+ MutexAutoLock lock(mMutex);
+
+ // Pretend we have a pending event to keep the idle timer from firing.
+ MOZ_ASSERT(mPendingEventCount < UINT32_MAX, "Way too many!");
+ mPendingEventCount++;
+}
+
+void LazyIdleThread::EnableIdleTimeout() {
+ ASSERT_OWNING_THREAD();
+ if (mIdleTimeoutEnabled) {
+ return;
+ }
+ mIdleTimeoutEnabled = true;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ MOZ_ASSERT(mPendingEventCount, "Mismatched calls to observer methods!");
+ --mPendingEventCount;
+ }
+
+ if (mThread) {
+ nsCOMPtr<nsIRunnable> runnable(new Runnable("LazyIdleThreadDummyRunnable"));
+ if (NS_FAILED(Dispatch(runnable.forget(), NS_DISPATCH_NORMAL))) {
+ NS_WARNING("Failed to dispatch!");
+ }
+ }
+}
+
+void LazyIdleThread::PreDispatch() {
+ MutexAutoLock lock(mMutex);
+
+ MOZ_ASSERT(mPendingEventCount < UINT32_MAX, "Way too many!");
+ mPendingEventCount++;
+}
+
+nsresult LazyIdleThread::EnsureThread() {
+ ASSERT_OWNING_THREAD();
+
+ if (mShutdown) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mThread) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(!mPendingEventCount, "Shouldn't have events yet!");
+ MOZ_ASSERT(!mIdleNotificationCount, "Shouldn't have idle events yet!");
+ MOZ_ASSERT(!mIdleTimer, "Should have killed this long ago!");
+ MOZ_ASSERT(!mThreadIsShuttingDown, "Should have cleared that!");
+
+ nsresult rv;
+
+ if (mShutdownMethod == AutomaticShutdown && NS_IsMainThread()) {
+ nsCOMPtr<nsIObserverService> obs =
+ do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = obs->AddObserver(this, "xpcom-shutdown-threads", false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ mIdleTimer = NS_NewTimer();
+ if (NS_WARN_IF(!mIdleTimer)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod(
+ "LazyIdleThread::InitThread", this, &LazyIdleThread::InitThread);
+ if (NS_WARN_IF(!runnable)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ rv = NS_NewNamedThread(mName, getter_AddRefs(mThread), runnable);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void LazyIdleThread::InitThread() {
+ // Happens on mThread but mThread may not be set yet...
+
+ nsCOMPtr<nsIThreadInternal> thread(do_QueryInterface(NS_GetCurrentThread()));
+ MOZ_ASSERT(thread, "This should always succeed!");
+
+ if (NS_FAILED(thread->SetObserver(this))) {
+ NS_WARNING("Failed to set thread observer!");
+ }
+}
+
+void LazyIdleThread::CleanupThread() {
+ nsCOMPtr<nsIThreadInternal> thread(do_QueryInterface(NS_GetCurrentThread()));
+ MOZ_ASSERT(thread, "This should always succeed!");
+
+ if (NS_FAILED(thread->SetObserver(nullptr))) {
+ NS_WARNING("Failed to set thread observer!");
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ MOZ_ASSERT(!mThreadIsShuttingDown, "Shouldn't be true ever!");
+ mThreadIsShuttingDown = true;
+ }
+}
+
+void LazyIdleThread::ScheduleTimer() {
+ ASSERT_OWNING_THREAD();
+
+ bool shouldSchedule;
+ {
+ MutexAutoLock lock(mMutex);
+
+ MOZ_ASSERT(mIdleNotificationCount, "Should have at least one!");
+ --mIdleNotificationCount;
+
+ shouldSchedule = !mIdleNotificationCount && !mPendingEventCount;
+ }
+
+ 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!");
+ }
+ }
+}
+
+nsresult LazyIdleThread::ShutdownThread() {
+ ASSERT_OWNING_THREAD();
+
+ // Before calling Shutdown() on the real thread we need to put a queue in
+ // place in case a runnable is posted to the thread while it's in the
+ // process of shutting down. This will be our queue.
+ AutoTArray<nsCOMPtr<nsIRunnable>, 10> queuedRunnables;
+
+ 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 =
+ mozilla::services::GetObserverService();
+ NS_WARNING_ASSERTION(obs, "Failed to get observer service!");
+
+ if (obs &&
+ NS_FAILED(obs->RemoveObserver(this, "xpcom-shutdown-threads"))) {
+ NS_WARNING("Failed to remove observer!");
+ }
+ }
+
+ if (mIdleObserver) {
+ mIdleObserver->Observe(static_cast<nsIThread*>(this), IDLE_THREAD_TOPIC,
+ nullptr);
+ }
+
+#ifdef DEBUG
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(!mThreadIsShuttingDown, "Huh?!");
+ }
+#endif
+
+ nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod(
+ "LazyIdleThread::CleanupThread", this, &LazyIdleThread::CleanupThread);
+ if (NS_WARN_IF(!runnable)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ PreDispatch();
+
+ rv = mThread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Put the temporary queue in place before calling Shutdown().
+ mQueuedRunnables = &queuedRunnables;
+
+ if (NS_FAILED(mThread->Shutdown())) {
+ NS_ERROR("Failed to shutdown the thread!");
+ }
+
+ // Now unset the queue.
+ mQueuedRunnables = nullptr;
+
+ mThread = nullptr;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ MOZ_ASSERT(!mPendingEventCount, "Huh?!");
+ MOZ_ASSERT(!mIdleNotificationCount, "Huh?!");
+ MOZ_ASSERT(mThreadIsShuttingDown, "Huh?!");
+ mThreadIsShuttingDown = false;
+ }
+ }
+
+ // 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.
+ if (mShutdown) {
+ NS_ERROR("Runnables dispatched to LazyIdleThread will never run!");
+ return NS_OK;
+ }
+
+ // Re-dispatch the queued runnables.
+ for (uint32_t index = 0; index < queuedRunnables.Length(); index++) {
+ nsCOMPtr<nsIRunnable> runnable;
+ runnable.swap(queuedRunnables[index]);
+ MOZ_ASSERT(runnable, "Null runnable?!");
+
+ if (NS_FAILED(Dispatch(runnable.forget(), NS_DISPATCH_NORMAL))) {
+ NS_ERROR("Failed to re-dispatch queued runnable!");
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+void LazyIdleThread::SelfDestruct() {
+ MOZ_ASSERT(mRefCnt == 1, "Bad refcount!");
+ delete this;
+}
+
+NS_IMPL_ADDREF(LazyIdleThread)
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+LazyIdleThread::Release() {
+ nsrefcnt count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "LazyIdleThread");
+
+ if (!count) {
+ // Stabilize refcount.
+ mRefCnt = 1;
+
+ nsCOMPtr<nsIRunnable> runnable = NewNonOwningRunnableMethod(
+ "LazyIdleThread::SelfDestruct", this, &LazyIdleThread::SelfDestruct);
+ NS_WARNING_ASSERTION(runnable, "Couldn't make runnable!");
+
+ if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
+ MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+ // The only way this could fail is if we're in shutdown, and in that case
+ // threads should have been joined already. Deleting here isn't dangerous
+ // anymore because we won't spin the event loop waiting to join the
+ // thread.
+ SelfDestruct();
+ }
+ }
+
+ return count;
+}
+
+NS_IMPL_QUERY_INTERFACE(LazyIdleThread, nsIThread, nsIEventTarget,
+ nsISerialEventTarget, nsITimerCallback,
+ nsIThreadObserver, nsIObserver, nsINamed)
+
+NS_IMETHODIMP
+LazyIdleThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) {
+ nsCOMPtr<nsIRunnable> event(aEvent);
+ return Dispatch(event.forget(), aFlags);
+}
+
+NS_IMETHODIMP
+LazyIdleThread::Dispatch(already_AddRefed<nsIRunnable> aEvent,
+ uint32_t aFlags) {
+ ASSERT_OWNING_THREAD();
+ nsCOMPtr<nsIRunnable> event(aEvent); // avoid leaks
+
+ // LazyIdleThread can't always support synchronous dispatch currently.
+ 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.
+ if (UseRunnableQueue()) {
+ mQueuedRunnables->AppendElement(event);
+ return NS_OK;
+ }
+
+ nsresult rv = EnsureThread();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ PreDispatch();
+
+ return mThread->Dispatch(event.forget(), aFlags);
+}
+
+NS_IMETHODIMP
+LazyIdleThread::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+LazyIdleThread::GetRunningEventDelay(TimeDuration* aDelay, TimeStamp* aStart) {
+ if (mThread) {
+ return mThread->GetRunningEventDelay(aDelay, aStart);
+ }
+ *aDelay = TimeDuration();
+ *aStart = TimeStamp();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LazyIdleThread::SetRunningEventDelay(TimeDuration aDelay, TimeStamp aStart) {
+ if (mThread) {
+ return mThread->SetRunningEventDelay(aDelay, aStart);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LazyIdleThread::IsOnCurrentThread(bool* aIsOnCurrentThread) {
+ if (mThread) {
+ return mThread->IsOnCurrentThread(aIsOnCurrentThread);
+ }
+
+ *aIsOnCurrentThread = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(bool)
+LazyIdleThread::IsOnCurrentThreadInfallible() {
+ if (mThread) {
+ return mThread->IsOnCurrentThread();
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP
+LazyIdleThread::GetPRThread(PRThread** aPRThread) {
+ if (mThread) {
+ return mThread->GetPRThread(aPRThread);
+ }
+
+ *aPRThread = nullptr;
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+LazyIdleThread::GetCanInvokeJS(bool* aCanInvokeJS) {
+ *aCanInvokeJS = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LazyIdleThread::SetCanInvokeJS(bool aCanInvokeJS) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+LazyIdleThread::GetLastLongTaskEnd(TimeStamp* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+LazyIdleThread::GetLastLongNonIdleTaskEnd(TimeStamp* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+LazyIdleThread::SetNameForWakeupTelemetry(const nsACString& aName) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+LazyIdleThread::AsyncShutdown() {
+ ASSERT_OWNING_THREAD();
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+LazyIdleThread::Shutdown() {
+ ASSERT_OWNING_THREAD();
+
+ mShutdown = true;
+
+ nsresult rv = ShutdownThread();
+ MOZ_ASSERT(!mThread, "Should have destroyed this by now!");
+
+ mIdleObserver = nullptr;
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LazyIdleThread::HasPendingEvents(bool* aHasPendingEvents) {
+ // This is only supposed to be called from the thread itself so it's not
+ // implemented here.
+ MOZ_ASSERT_UNREACHABLE("Shouldn't ever call this!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+LazyIdleThread::HasPendingHighPriorityEvents(bool* aHasPendingEvents) {
+ // This is only supposed to be called from the thread itself so it's not
+ // implemented here.
+ MOZ_ASSERT_UNREACHABLE("Shouldn't ever call this!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+LazyIdleThread::DispatchToQueue(already_AddRefed<nsIRunnable> aEvent,
+ EventQueuePriority aQueue) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+LazyIdleThread::ProcessNextEvent(bool aMayWait, bool* aEventWasProcessed) {
+ // This is only supposed to be called from the thread itself so it's not
+ // implemented here.
+ MOZ_ASSERT_UNREACHABLE("Shouldn't ever call this!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+LazyIdleThread::Notify(nsITimer* aTimer) {
+ ASSERT_OWNING_THREAD();
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (mPendingEventCount || mIdleNotificationCount) {
+ // Another event was scheduled since this timer was set. Don't do
+ // anything and wait for the timer to fire again.
+ return NS_OK;
+ }
+ }
+
+ nsresult rv = ShutdownThread();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LazyIdleThread::GetName(nsACString& aName) {
+ aName.Assign(mName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LazyIdleThread::OnDispatchedEvent() {
+ MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LazyIdleThread::OnProcessNextEvent(nsIThreadInternal* /* aThread */,
+ bool /* aMayWait */) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LazyIdleThread::AfterProcessNextEvent(nsIThreadInternal* /* aThread */,
+ bool aEventWasProcessed) {
+ bool shouldNotifyIdle;
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (aEventWasProcessed) {
+ MOZ_ASSERT(mPendingEventCount, "Mismatched calls to observer methods!");
+ --mPendingEventCount;
+ }
+
+ if (mThreadIsShuttingDown) {
+ // We're shutting down, no need to fire any timer.
+ return NS_OK;
+ }
+
+ shouldNotifyIdle = !mPendingEventCount;
+ if (shouldNotifyIdle) {
+ MOZ_ASSERT(mIdleNotificationCount < UINT32_MAX, "Way too many!");
+ mIdleNotificationCount++;
+ }
+ }
+
+ if (shouldNotifyIdle) {
+ nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod(
+ "LazyIdleThread::ScheduleTimer", this, &LazyIdleThread::ScheduleTimer);
+ if (NS_WARN_IF(!runnable)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv =
+ mOwningEventTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LazyIdleThread::Observe(nsISupports* /* aSubject */, const char* aTopic,
+ const char16_t* /* aData */) {
+ MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+ MOZ_ASSERT(mShutdownMethod == AutomaticShutdown,
+ "Should not receive notifications if not AutomaticShutdown!");
+ MOZ_ASSERT(!strcmp("xpcom-shutdown-threads", aTopic), "Bad topic!");
+
+ Shutdown();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LazyIdleThread::GetEventTarget(nsIEventTarget** aEventTarget) {
+ nsCOMPtr<nsIEventTarget> target = this;
+ target.forget(aEventTarget);
+ return NS_OK;
+}
+
+nsIEventTarget* LazyIdleThread::EventTarget() { return this; }
+
+nsISerialEventTarget* LazyIdleThread::SerialEventTarget() { return this; }
+
+} // namespace mozilla
diff --git a/xpcom/threads/LazyIdleThread.h b/xpcom/threads/LazyIdleThread.h
new file mode 100644
index 0000000000..43a660b92f
--- /dev/null
+++ b/xpcom/threads/LazyIdleThread.h
@@ -0,0 +1,219 @@
+/* -*- 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_lazyidlethread_h__
+#define mozilla_lazyidlethread_h__
+
+#ifndef MOZILLA_INTERNAL_API
+# error "This header is only usable from within libxul (MOZILLA_INTERNAL_API)."
+#endif
+
+#include "nsINamed.h"
+#include "nsIObserver.h"
+#include "nsIThreadInternal.h"
+#include "nsITimer.h"
+
+#include "mozilla/Mutex.h"
+#include "nsCOMPtr.h"
+#include "nsTArrayForwardDeclare.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+
+#define IDLE_THREAD_TOPIC "thread-shutting-down"
+
+namespace mozilla {
+
+/**
+ * This class provides a basic event target that creates its thread lazily and
+ * destroys its thread after a period of inactivity. It may be created on any
+ * thread but it may only be used from the thread on which it is created. If it
+ * is created on the main thread then it will automatically join its thread on
+ * XPCOM shutdown using the Observer Service.
+ */
+class LazyIdleThread final : public nsIThread,
+ public nsITimerCallback,
+ public nsIThreadObserver,
+ public nsIObserver,
+ public nsINamed {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIEVENTTARGET_FULL
+ NS_DECL_NSITHREAD
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSITHREADOBSERVER
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSINAMED
+
+ enum ShutdownMethod { AutomaticShutdown = 0, ManualShutdown };
+
+ /**
+ * Create a new LazyIdleThread that will destroy its thread after the given
+ * number of milliseconds.
+ */
+ LazyIdleThread(uint32_t aIdleTimeoutMS, const nsACString& aName,
+ ShutdownMethod aShutdownMethod = AutomaticShutdown,
+ nsIObserver* aIdleObserver = nullptr);
+
+ /**
+ * Add an observer that will be notified when the thread is idle and about to
+ * be shut down. The aSubject argument can be QueryInterface'd to an nsIThread
+ * that can be used to post cleanup events. The aTopic argument will be
+ * IDLE_THREAD_TOPIC, and aData will be null. The LazyIdleThread does not add
+ * a reference to the observer to avoid circular references as it is assumed
+ * to be the owner. It is the caller's responsibility to clear this observer
+ * if the pointer becomes invalid.
+ */
+ void SetWeakIdleObserver(nsIObserver* aObserver);
+
+ /**
+ * Disable the idle timeout for this thread. No effect if the timeout is
+ * already disabled.
+ */
+ void DisableIdleTimeout();
+
+ /**
+ * Enable the idle timeout. No effect if the timeout is already enabled.
+ */
+ void EnableIdleTimeout();
+
+ private:
+ /**
+ * Calls Shutdown().
+ */
+ ~LazyIdleThread();
+
+ /**
+ * Called just before dispatching to mThread.
+ */
+ void PreDispatch();
+
+ /**
+ * Makes sure a valid thread lives in mThread.
+ */
+ nsresult EnsureThread();
+
+ /**
+ * Called on mThread to set up the thread observer.
+ */
+ void InitThread();
+
+ /**
+ * Called on mThread to clean up the thread observer.
+ */
+ void CleanupThread();
+
+ /**
+ * Called on the main thread when mThread believes itself to be idle. Sets up
+ * the idle timer.
+ */
+ void ScheduleTimer();
+
+ /**
+ * Called when we are shutting down mThread.
+ */
+ nsresult ShutdownThread();
+
+ /**
+ * Deletes this object. Used to delay calling mThread->Shutdown() during the
+ * final release (during a GC, for instance).
+ */
+ void SelfDestruct();
+
+ /**
+ * Returns true if events should be queued rather than immediately dispatched
+ * to mThread. Currently only happens when the thread is shutting down.
+ */
+ bool UseRunnableQueue() { return !!mQueuedRunnables; }
+
+ /**
+ * Protects data that is accessed on both threads.
+ */
+ mozilla::Mutex mMutex;
+
+ /**
+ * Touched on both threads but set before mThread is created. Used to direct
+ * timer events to the owning thread.
+ */
+ nsCOMPtr<nsISerialEventTarget> mOwningEventTarget;
+
+ /**
+ * Only accessed on the owning thread. Set by EnsureThread().
+ */
+ nsCOMPtr<nsIThread> mThread;
+
+ /**
+ * Protected by mMutex. Created when mThread has no pending events and fired
+ * at mOwningThread. Any thread that dispatches to mThread will take ownership
+ * of the timer and fire a separate cancel event to the owning thread.
+ */
+ nsCOMPtr<nsITimer> mIdleTimer;
+
+ /**
+ * Idle observer. Called when the thread is about to be shut down. Released
+ * only when Shutdown() is called.
+ */
+ nsIObserver* MOZ_UNSAFE_REF(
+ "See the documentation for SetWeakIdleObserver for "
+ "how the owner of LazyIdleThread should manage the "
+ "lifetime information of this field") mIdleObserver;
+
+ /**
+ * 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;
+
+ /**
+ * The number of milliseconds a thread should be idle before dying.
+ */
+ const uint32_t mIdleTimeoutMS;
+
+ /**
+ * The number of events that are pending on mThread. A nonzero value means
+ * that the thread cannot be cleaned up.
+ */
+ uint32_t mPendingEventCount;
+
+ /**
+ * The number of times that mThread has dispatched an idle notification. Any
+ * timer that fires while this count is nonzero can safely be ignored as
+ * another timer will be on the way.
+ */
+ uint32_t mIdleNotificationCount;
+
+ /**
+ * Whether or not the thread should automatically shutdown. If the owner
+ * specified ManualShutdown at construction time then the owner should take
+ * care to call Shutdown() manually when appropriate.
+ */
+ ShutdownMethod mShutdownMethod;
+
+ /**
+ * Only accessed on the owning thread. Set to true when Shutdown() has been
+ * called and prevents EnsureThread() from recreating mThread.
+ */
+ bool mShutdown;
+
+ /**
+ * Set from CleanupThread and lasting until the thread has shut down. Prevents
+ * further idle notifications during the shutdown process.
+ */
+ bool mThreadIsShuttingDown;
+
+ /**
+ * Whether or not the idle timeout is enabled.
+ */
+ bool mIdleTimeoutEnabled;
+
+ /**
+ * Name of the thread, set on the actual thread after it gets created.
+ */
+ nsCString mName;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_lazyidlethread_h__
diff --git a/xpcom/threads/LeakRefPtr.h b/xpcom/threads/LeakRefPtr.h
new file mode 100644
index 0000000000..ee260dad7e
--- /dev/null
+++ b/xpcom/threads/LeakRefPtr.h
@@ -0,0 +1,48 @@
+/* -*- 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/. */
+
+/* Smart pointer which leaks its owning refcounted object by default. */
+
+#ifndef LeakRefPtr_h
+#define LeakRefPtr_h
+
+#include "mozilla/AlreadyAddRefed.h"
+
+namespace mozilla {
+
+/**
+ * Instance of this class behaves like a raw pointer which leaks the
+ * resource it's owning if not explicitly released.
+ */
+template <class T>
+class LeakRefPtr {
+ public:
+ explicit LeakRefPtr(already_AddRefed<T>&& aPtr) : mRawPtr(aPtr.take()) {}
+
+ explicit operator bool() const { return !!mRawPtr; }
+
+ LeakRefPtr<T>& operator=(already_AddRefed<T>&& aPtr) {
+ mRawPtr = aPtr.take();
+ return *this;
+ }
+
+ T* get() const { return mRawPtr; }
+
+ already_AddRefed<T> take() {
+ T* rawPtr = mRawPtr;
+ mRawPtr = nullptr;
+ return already_AddRefed<T>(rawPtr);
+ }
+
+ void release() { NS_RELEASE(mRawPtr); }
+
+ private:
+ T* MOZ_OWNING_REF mRawPtr;
+};
+
+} // namespace mozilla
+
+#endif // LeakRefPtr_h
diff --git a/xpcom/threads/MainThreadIdlePeriod.cpp b/xpcom/threads/MainThreadIdlePeriod.cpp
new file mode 100644
index 0000000000..9080a8850e
--- /dev/null
+++ b/xpcom/threads/MainThreadIdlePeriod.cpp
@@ -0,0 +1,75 @@
+/* -*- 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 "MainThreadIdlePeriod.h"
+
+#include "mozilla/Maybe.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_idle_period.h"
+#include "mozilla/dom/Document.h"
+#include "VRManagerChild.h"
+#include "nsRefreshDriver.h"
+#include "nsThreadUtils.h"
+
+// The amount of idle time (milliseconds) reserved for a long idle period.
+static const double kLongIdlePeriodMS = 50.0;
+
+// The minimum amount of time (milliseconds) required for an idle period to be
+// scheduled on the main thread. N.B. layout.idle_period.time_limit adds
+// padding at the end of the idle period, which makes the point in time that we
+// expect to become busy again be:
+// now + idle_period.min + layout.idle_period.time_limit
+// or during page load
+// now + idle_period.during_page_load.min + layout.idle_period.time_limit
+
+static const uint32_t kMaxTimerThreadBound = 5; // milliseconds
+static const uint32_t kMaxTimerThreadBoundClamp = 15; // milliseconds
+
+namespace mozilla {
+
+NS_IMETHODIMP
+MainThreadIdlePeriod::GetIdlePeriodHint(TimeStamp* aIdleDeadline) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aIdleDeadline);
+
+ TimeStamp now = TimeStamp::Now();
+ TimeStamp currentGuess =
+ now + TimeDuration::FromMilliseconds(kLongIdlePeriodMS);
+
+ currentGuess = nsRefreshDriver::GetIdleDeadlineHint(currentGuess);
+ if (XRE_IsContentProcess()) {
+ currentGuess = gfx::VRManagerChild::GetIdleDeadlineHint(currentGuess);
+ }
+ currentGuess = NS_GetTimerDeadlineHintOnCurrentThread(currentGuess,
+ kMaxTimerThreadBound);
+
+ // If the idle period is too small, then just return a null time
+ // to indicate we are busy. Otherwise return the actual deadline.
+ TimeDuration minIdlePeriod =
+ TimeDuration::FromMilliseconds(StaticPrefs::idle_period_min());
+ bool busySoon = currentGuess.IsNull() ||
+ (now >= (currentGuess - minIdlePeriod)) ||
+ currentGuess < mLastIdleDeadline;
+
+ // During page load use higher minimum idle period.
+ if (!busySoon && XRE_IsContentProcess() &&
+ mozilla::dom::Document::HasRecentlyStartedForegroundLoads()) {
+ TimeDuration minIdlePeriod = TimeDuration::FromMilliseconds(
+ StaticPrefs::idle_period_during_page_load_min());
+ busySoon = (now >= (currentGuess - minIdlePeriod));
+ }
+
+ if (!busySoon) {
+ *aIdleDeadline = mLastIdleDeadline = currentGuess;
+ }
+
+ return NS_OK;
+}
+
+/* static */
+float MainThreadIdlePeriod::GetLongIdlePeriod() { return kLongIdlePeriodMS; }
+
+} // namespace mozilla
diff --git a/xpcom/threads/MainThreadIdlePeriod.h b/xpcom/threads/MainThreadIdlePeriod.h
new file mode 100644
index 0000000000..7e382f6771
--- /dev/null
+++ b/xpcom/threads/MainThreadIdlePeriod.h
@@ -0,0 +1,31 @@
+/* -*- 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_dom_mainthreadidleperiod_h
+#define mozilla_dom_mainthreadidleperiod_h
+
+#include "mozilla/TimeStamp.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+
+class MainThreadIdlePeriod final : public IdlePeriod {
+ public:
+ NS_DECL_NSIIDLEPERIOD
+
+ MainThreadIdlePeriod() : mLastIdleDeadline(TimeStamp::Now()) {}
+
+ static float GetLongIdlePeriod();
+
+ private:
+ virtual ~MainThreadIdlePeriod() = default;
+
+ TimeStamp mLastIdleDeadline;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_dom_mainthreadidleperiod_h
diff --git a/xpcom/threads/MainThreadUtils.h b/xpcom/threads/MainThreadUtils.h
new file mode 100644
index 0000000000..aac81e88b2
--- /dev/null
+++ b/xpcom/threads/MainThreadUtils.h
@@ -0,0 +1,48 @@
+/* -*- 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 MainThreadUtils_h_
+#define MainThreadUtils_h_
+
+#include "nscore.h"
+
+class nsIThread;
+
+/**
+ * Get a reference to the main thread.
+ *
+ * @param aResult
+ * The resulting nsIThread object.
+ */
+extern nsresult NS_GetMainThread(nsIThread** aResult);
+
+#ifdef MOZILLA_INTERNAL_API
+// Fast access to the current thread. Do not release the returned pointer! If
+// you want to use this pointer from some other thread, then you will need to
+// AddRef it. Otherwise, you should only consider this pointer valid from code
+// running on the current thread.
+extern nsIThread* NS_GetCurrentThread();
+#endif
+
+#ifdef MOZILLA_INTERNAL_API
+bool NS_IsMainThreadTLSInitialized();
+extern "C" {
+bool NS_IsMainThread();
+}
+
+namespace mozilla {
+
+# ifdef DEBUG
+void AssertIsOnMainThread();
+# else
+inline void AssertIsOnMainThread() {}
+# endif
+
+} // namespace mozilla
+
+#endif
+
+#endif // MainThreadUtils_h_
diff --git a/xpcom/threads/Monitor.h b/xpcom/threads/Monitor.h
new file mode 100644
index 0000000000..ff8204b5ad
--- /dev/null
+++ b/xpcom/threads/Monitor.h
@@ -0,0 +1,119 @@
+/* -*- 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_Monitor_h
+#define mozilla_Monitor_h
+
+#include "mozilla/CondVar.h"
+#include "mozilla/Mutex.h"
+
+namespace mozilla {
+
+/**
+ * Monitor provides a *non*-reentrant monitor: *not* a Java-style
+ * monitor. If your code needs support for reentrancy, use
+ * ReentrantMonitor instead. (Rarely should reentrancy be needed.)
+ *
+ * Instead of directly calling Monitor methods, it's safer and simpler
+ * to instead use the RAII wrappers MonitorAutoLock and
+ * MonitorAutoUnlock.
+ */
+class Monitor {
+ public:
+ explicit Monitor(const char* aName)
+ : mMutex(aName), mCondVar(mMutex, "[Monitor.mCondVar]") {}
+
+ ~Monitor() = default;
+
+ void Lock() { mMutex.Lock(); }
+ [[nodiscard]] bool TryLock() { return mMutex.TryLock(); }
+ void Unlock() { mMutex.Unlock(); }
+
+ void Wait() { mCondVar.Wait(); }
+ CVStatus Wait(TimeDuration aDuration) { return mCondVar.Wait(aDuration); }
+
+ void Notify() { mCondVar.Notify(); }
+ void NotifyAll() { mCondVar.NotifyAll(); }
+
+ void AssertCurrentThreadOwns() const { mMutex.AssertCurrentThreadOwns(); }
+
+ void AssertNotCurrentThreadOwns() const {
+ mMutex.AssertNotCurrentThreadOwns();
+ }
+
+ private:
+ Monitor();
+ Monitor(const Monitor&);
+ Monitor& operator=(const Monitor&);
+
+ Mutex mMutex;
+ CondVar mCondVar;
+};
+
+/**
+ * Lock the monitor for the lexical scope instances of this class are
+ * bound to (except for MonitorAutoUnlock in nested scopes).
+ *
+ * The monitor must be unlocked when instances of this class are
+ * created.
+ */
+class MOZ_STACK_CLASS MonitorAutoLock {
+ public:
+ explicit MonitorAutoLock(Monitor& aMonitor) : mMonitor(&aMonitor) {
+ mMonitor->Lock();
+ }
+
+ ~MonitorAutoLock() { mMonitor->Unlock(); }
+
+ void Wait() { mMonitor->Wait(); }
+ CVStatus Wait(TimeDuration aDuration) { return mMonitor->Wait(aDuration); }
+
+ void Notify() { mMonitor->Notify(); }
+ void NotifyAll() { mMonitor->NotifyAll(); }
+
+ private:
+ MonitorAutoLock();
+ MonitorAutoLock(const MonitorAutoLock&);
+ MonitorAutoLock& operator=(const MonitorAutoLock&);
+ static void* operator new(size_t) noexcept(true);
+
+ friend class MonitorAutoUnlock;
+
+ Monitor* mMonitor;
+};
+
+/**
+ * Unlock the monitor for the lexical scope instances of this class
+ * are bound to (except for MonitorAutoLock in nested scopes).
+ *
+ * The monitor must be locked by the current thread when instances of
+ * this class are created.
+ */
+class MOZ_STACK_CLASS MonitorAutoUnlock {
+ public:
+ explicit MonitorAutoUnlock(Monitor& aMonitor) : mMonitor(&aMonitor) {
+ mMonitor->Unlock();
+ }
+
+ explicit MonitorAutoUnlock(MonitorAutoLock& aMonitorLock)
+ : mMonitor(aMonitorLock.mMonitor) {
+ mMonitor->Unlock();
+ }
+
+ ~MonitorAutoUnlock() { mMonitor->Lock(); }
+
+ private:
+ MonitorAutoUnlock();
+ MonitorAutoUnlock(const MonitorAutoUnlock&);
+ MonitorAutoUnlock& operator=(const MonitorAutoUnlock&);
+ static void* operator new(size_t) noexcept(true);
+
+ Monitor* mMonitor;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_Monitor_h
diff --git a/xpcom/threads/MozPromise.h b/xpcom/threads/MozPromise.h
new file mode 100644
index 0000000000..39d8919882
--- /dev/null
+++ b/xpcom/threads/MozPromise.h
@@ -0,0 +1,1697 @@
+/* -*- 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/. */
+
+#if !defined(MozPromise_h_)
+# define MozPromise_h_
+
+# include <type_traits>
+# include <utility>
+
+# include "mozilla/Logging.h"
+# include "mozilla/Maybe.h"
+# include "mozilla/Monitor.h"
+# include "mozilla/Mutex.h"
+# include "mozilla/RefPtr.h"
+# include "mozilla/UniquePtr.h"
+# include "mozilla/Variant.h"
+# include "nsIDirectTaskDispatcher.h"
+# include "nsISerialEventTarget.h"
+# include "nsTArray.h"
+# include "nsThreadUtils.h"
+
+# ifdef MOZ_WIDGET_ANDROID
+# include "mozilla/jni/GeckoResultUtils.h"
+# endif
+
+# if MOZ_DIAGNOSTIC_ASSERT_ENABLED
+# define PROMISE_DEBUG
+# endif
+
+# ifdef PROMISE_DEBUG
+# define PROMISE_ASSERT MOZ_RELEASE_ASSERT
+# else
+# define PROMISE_ASSERT(...) \
+ do { \
+ } while (0)
+# endif
+
+# if DEBUG
+# include "nsPrintfCString.h"
+# endif
+
+namespace mozilla {
+
+namespace dom {
+class Promise;
+}
+
+extern LazyLogModule gMozPromiseLog;
+
+# define PROMISE_LOG(x, ...) \
+ MOZ_LOG(gMozPromiseLog, mozilla::LogLevel::Debug, (x, ##__VA_ARGS__))
+
+namespace detail {
+template <typename F>
+struct MethodTraitsHelper : MethodTraitsHelper<decltype(&F::operator())> {};
+template <typename ThisType, typename Ret, typename... ArgTypes>
+struct MethodTraitsHelper<Ret (ThisType::*)(ArgTypes...)> {
+ using ReturnType = Ret;
+ static const size_t ArgSize = sizeof...(ArgTypes);
+};
+template <typename ThisType, typename Ret, typename... ArgTypes>
+struct MethodTraitsHelper<Ret (ThisType::*)(ArgTypes...) const> {
+ using ReturnType = Ret;
+ static const size_t ArgSize = sizeof...(ArgTypes);
+};
+template <typename ThisType, typename Ret, typename... ArgTypes>
+struct MethodTraitsHelper<Ret (ThisType::*)(ArgTypes...) volatile> {
+ using ReturnType = Ret;
+ static const size_t ArgSize = sizeof...(ArgTypes);
+};
+template <typename ThisType, typename Ret, typename... ArgTypes>
+struct MethodTraitsHelper<Ret (ThisType::*)(ArgTypes...) const volatile> {
+ using ReturnType = Ret;
+ static const size_t ArgSize = sizeof...(ArgTypes);
+};
+template <typename T>
+struct MethodTrait : MethodTraitsHelper<std::remove_reference_t<T>> {};
+
+} // namespace detail
+
+template <typename MethodType>
+using TakesArgument =
+ std::integral_constant<bool, detail::MethodTrait<MethodType>::ArgSize != 0>;
+
+template <typename MethodType, typename TargetType>
+using ReturnTypeIs =
+ std::is_convertible<typename detail::MethodTrait<MethodType>::ReturnType,
+ TargetType>;
+
+template <typename ResolveValueT, typename RejectValueT, bool IsExclusive>
+class MozPromise;
+
+template <typename Return>
+struct IsMozPromise : std::false_type {};
+
+template <typename ResolveValueT, typename RejectValueT, bool IsExclusive>
+struct IsMozPromise<MozPromise<ResolveValueT, RejectValueT, IsExclusive>>
+ : std::true_type {};
+
+/*
+ * A promise manages an asynchronous request that may or may not be able to be
+ * fulfilled immediately. When an API returns a promise, the consumer may attach
+ * callbacks to be invoked (asynchronously, on a specified thread) when the
+ * request is either completed (resolved) or cannot be completed (rejected).
+ * Whereas JS promise callbacks are dispatched from Microtask checkpoints,
+ * MozPromises resolution/rejection make a normal round-trip through the event
+ * loop, which simplifies their ordering semantics relative to other native
+ * code.
+ *
+ * MozPromises attempt to mirror the spirit of JS Promises to the extent that
+ * is possible (and desirable) in C++. While the intent is that MozPromises
+ * feel familiar to programmers who are accustomed to their JS-implemented
+ * cousin, we don't shy away from imposing restrictions and adding features that
+ * make sense for the use cases we encounter.
+ *
+ * A MozPromise is ThreadSafe, and may be ->Then()ed on any thread. The Then()
+ * call accepts resolve and reject callbacks, and returns a magic object which
+ * will be implicitly converted to a MozPromise::Request or a MozPromise object
+ * depending on how the return value is used. The magic object serves several
+ * purposes for the consumer.
+ *
+ * (1) When converting to a MozPromise::Request, it allows the caller to
+ * cancel the delivery of the resolve/reject value if it has not already
+ * occurred, via Disconnect() (this must be done on the target thread to
+ * avoid racing).
+ *
+ * (2) When converting to a MozPromise (which is called a completion promise),
+ * it allows promise chaining so ->Then() can be called again to attach
+ * more resolve and reject callbacks. If the resolve/reject callback
+ * returns a new MozPromise, that promise is chained to the completion
+ * promise, such that its resolve/reject value will be forwarded along
+ * when it arrives. If the resolve/reject callback returns void, the
+ * completion promise is resolved/rejected with the same value that was
+ * passed to the callback.
+ *
+ * The MozPromise APIs skirt traditional XPCOM convention by returning nsRefPtrs
+ * (rather than already_AddRefed) from various methods. This is done to allow
+ * elegant chaining of calls without cluttering up the code with intermediate
+ * variables, and without introducing separate API variants for callers that
+ * want a return value (from, say, ->Then()) from those that don't.
+ *
+ * When IsExclusive is true, the MozPromise does a release-mode assertion that
+ * there is at most one call to either Then(...) or ChainTo(...).
+ */
+
+class MozPromiseRefcountable {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MozPromiseRefcountable)
+ protected:
+ virtual ~MozPromiseRefcountable() = default;
+};
+
+class MozPromiseBase : public MozPromiseRefcountable {
+ public:
+ virtual void AssertIsDead() = 0;
+};
+
+template <typename T>
+class MozPromiseHolder;
+template <typename T>
+class MozPromiseRequestHolder;
+template <typename ResolveValueT, typename RejectValueT, bool IsExclusive>
+class MozPromise : public MozPromiseBase {
+ static const uint32_t sMagic = 0xcecace11;
+
+ // Return a |T&&| to enable move when IsExclusive is true or
+ // a |const T&| to enforce copy otherwise.
+ template <typename T,
+ typename R = std::conditional_t<IsExclusive, T&&, const T&>>
+ static R MaybeMove(T& aX) {
+ return static_cast<R>(aX);
+ }
+
+ public:
+ typedef ResolveValueT ResolveValueType;
+ typedef RejectValueT RejectValueType;
+ class ResolveOrRejectValue {
+ public:
+ template <typename ResolveValueType_>
+ void SetResolve(ResolveValueType_&& aResolveValue) {
+ MOZ_ASSERT(IsNothing());
+ mValue = Storage(VariantIndex<ResolveIndex>{},
+ std::forward<ResolveValueType_>(aResolveValue));
+ }
+
+ template <typename RejectValueType_>
+ void SetReject(RejectValueType_&& aRejectValue) {
+ MOZ_ASSERT(IsNothing());
+ mValue = Storage(VariantIndex<RejectIndex>{},
+ std::forward<RejectValueType_>(aRejectValue));
+ }
+
+ template <typename ResolveValueType_>
+ static ResolveOrRejectValue MakeResolve(ResolveValueType_&& aResolveValue) {
+ ResolveOrRejectValue val;
+ val.SetResolve(std::forward<ResolveValueType_>(aResolveValue));
+ return val;
+ }
+
+ template <typename RejectValueType_>
+ static ResolveOrRejectValue MakeReject(RejectValueType_&& aRejectValue) {
+ ResolveOrRejectValue val;
+ val.SetReject(std::forward<RejectValueType_>(aRejectValue));
+ return val;
+ }
+
+ bool IsResolve() const { return mValue.template is<ResolveIndex>(); }
+ bool IsReject() const { return mValue.template is<RejectIndex>(); }
+ bool IsNothing() const { return mValue.template is<NothingIndex>(); }
+
+ const ResolveValueType& ResolveValue() const {
+ return mValue.template as<ResolveIndex>();
+ }
+ ResolveValueType& ResolveValue() {
+ return mValue.template as<ResolveIndex>();
+ }
+ const RejectValueType& RejectValue() const {
+ return mValue.template as<RejectIndex>();
+ }
+ RejectValueType& RejectValue() { return mValue.template as<RejectIndex>(); }
+
+ private:
+ enum { NothingIndex, ResolveIndex, RejectIndex };
+ using Storage = Variant<Nothing, ResolveValueType, RejectValueType>;
+ Storage mValue = Storage(VariantIndex<NothingIndex>{});
+ };
+
+ protected:
+ // MozPromise is the public type, and never constructed directly. Construct
+ // a MozPromise::Private, defined below.
+ MozPromise(const char* aCreationSite, bool aIsCompletionPromise)
+ : mCreationSite(aCreationSite),
+ mMutex("MozPromise Mutex"),
+ mHaveRequest(false),
+ mIsCompletionPromise(aIsCompletionPromise)
+# ifdef PROMISE_DEBUG
+ ,
+ mMagic4(&mMutex)
+# endif
+ {
+ PROMISE_LOG("%s creating MozPromise (%p)", mCreationSite, this);
+ }
+
+ public:
+ // MozPromise::Private allows us to separate the public interface (upon which
+ // consumers of the promise may invoke methods like Then()) from the private
+ // interface (upon which the creator of the promise may invoke Resolve() or
+ // Reject()). APIs should create and store a MozPromise::Private (usually
+ // via a MozPromiseHolder), and return a MozPromise to consumers.
+ //
+ // NB: We can include the definition of this class inline once B2G ICS is
+ // gone.
+ class Private;
+
+ template <typename ResolveValueType_>
+ [[nodiscard]] static RefPtr<MozPromise> CreateAndResolve(
+ ResolveValueType_&& aResolveValue, const char* aResolveSite) {
+ static_assert(std::is_convertible_v<ResolveValueType_, ResolveValueT>,
+ "Resolve() argument must be implicitly convertible to "
+ "MozPromise's ResolveValueT");
+ RefPtr<typename MozPromise::Private> p =
+ new MozPromise::Private(aResolveSite);
+ p->Resolve(std::forward<ResolveValueType_>(aResolveValue), aResolveSite);
+ return p;
+ }
+
+ template <typename RejectValueType_>
+ [[nodiscard]] static RefPtr<MozPromise> CreateAndReject(
+ RejectValueType_&& aRejectValue, const char* aRejectSite) {
+ static_assert(std::is_convertible_v<RejectValueType_, RejectValueT>,
+ "Reject() argument must be implicitly convertible to "
+ "MozPromise's RejectValueT");
+ RefPtr<typename MozPromise::Private> p =
+ new MozPromise::Private(aRejectSite);
+ p->Reject(std::forward<RejectValueType_>(aRejectValue), aRejectSite);
+ return p;
+ }
+
+ template <typename ResolveOrRejectValueType_>
+ [[nodiscard]] static RefPtr<MozPromise> CreateAndResolveOrReject(
+ ResolveOrRejectValueType_&& aValue, const char* aSite) {
+ RefPtr<typename MozPromise::Private> p = new MozPromise::Private(aSite);
+ p->ResolveOrReject(std::forward<ResolveOrRejectValueType_>(aValue), aSite);
+ return p;
+ }
+
+ typedef MozPromise<CopyableTArray<ResolveValueType>, RejectValueType,
+ IsExclusive>
+ AllPromiseType;
+
+ typedef MozPromise<CopyableTArray<ResolveOrRejectValue>, bool, IsExclusive>
+ AllSettledPromiseType;
+
+ private:
+ class AllPromiseHolder : public MozPromiseRefcountable {
+ public:
+ explicit AllPromiseHolder(size_t aDependentPromises)
+ : mPromise(new typename AllPromiseType::Private(__func__)),
+ mOutstandingPromises(aDependentPromises) {
+ MOZ_ASSERT(aDependentPromises > 0);
+ mResolveValues.SetLength(aDependentPromises);
+ }
+
+ void Resolve(size_t aIndex, ResolveValueType&& aResolveValue) {
+ if (!mPromise) {
+ // Already rejected.
+ return;
+ }
+
+ mResolveValues[aIndex].emplace(std::move(aResolveValue));
+ if (--mOutstandingPromises == 0) {
+ nsTArray<ResolveValueType> resolveValues;
+ resolveValues.SetCapacity(mResolveValues.Length());
+ for (auto&& resolveValue : mResolveValues) {
+ resolveValues.AppendElement(std::move(resolveValue.ref()));
+ }
+
+ mPromise->Resolve(std::move(resolveValues), __func__);
+ mPromise = nullptr;
+ mResolveValues.Clear();
+ }
+ }
+
+ void Reject(RejectValueType&& aRejectValue) {
+ if (!mPromise) {
+ // Already rejected.
+ return;
+ }
+
+ mPromise->Reject(std::move(aRejectValue), __func__);
+ mPromise = nullptr;
+ mResolveValues.Clear();
+ }
+
+ AllPromiseType* Promise() { return mPromise; }
+
+ private:
+ nsTArray<Maybe<ResolveValueType>> mResolveValues;
+ RefPtr<typename AllPromiseType::Private> mPromise;
+ size_t mOutstandingPromises;
+ };
+
+ // Trying to pass ResolveOrRejectValue by value fails static analysis checks,
+ // so we need to use either a const& or an rvalue reference, depending on
+ // whether IsExclusive is true or not.
+ typedef std::conditional_t<IsExclusive, ResolveOrRejectValue&&,
+ const ResolveOrRejectValue&>
+ ResolveOrRejectValueParam;
+
+ class AllSettledPromiseHolder : public MozPromiseRefcountable {
+ public:
+ explicit AllSettledPromiseHolder(size_t aDependentPromises)
+ : mPromise(new typename AllSettledPromiseType::Private(__func__)),
+ mOutstandingPromises(aDependentPromises) {
+ MOZ_ASSERT(aDependentPromises > 0);
+ mValues.SetLength(aDependentPromises);
+ }
+
+ void Settle(size_t aIndex, ResolveOrRejectValueParam aValue) {
+ if (!mPromise) {
+ // Already rejected.
+ return;
+ }
+
+ mValues[aIndex].emplace(MaybeMove(aValue));
+ if (--mOutstandingPromises == 0) {
+ nsTArray<ResolveOrRejectValue> values;
+ values.SetCapacity(mValues.Length());
+ for (auto&& value : mValues) {
+ values.AppendElement(std::move(value.ref()));
+ }
+
+ mPromise->Resolve(std::move(values), __func__);
+ mPromise = nullptr;
+ mValues.Clear();
+ }
+ }
+
+ AllSettledPromiseType* Promise() { return mPromise; }
+
+ private:
+ nsTArray<Maybe<ResolveOrRejectValue>> mValues;
+ RefPtr<typename AllSettledPromiseType::Private> mPromise;
+ size_t mOutstandingPromises;
+ };
+
+ public:
+ [[nodiscard]] static RefPtr<AllPromiseType> All(
+ nsISerialEventTarget* aProcessingTarget,
+ nsTArray<RefPtr<MozPromise>>& aPromises) {
+ if (aPromises.Length() == 0) {
+ return AllPromiseType::CreateAndResolve(
+ CopyableTArray<ResolveValueType>(), __func__);
+ }
+
+ RefPtr<AllPromiseHolder> holder = new AllPromiseHolder(aPromises.Length());
+ RefPtr<AllPromiseType> promise = holder->Promise();
+ for (size_t i = 0; i < aPromises.Length(); ++i) {
+ aPromises[i]->Then(
+ aProcessingTarget, __func__,
+ [holder, i](ResolveValueType aResolveValue) -> void {
+ holder->Resolve(i, std::move(aResolveValue));
+ },
+ [holder](RejectValueType aRejectValue) -> void {
+ holder->Reject(std::move(aRejectValue));
+ });
+ }
+ return promise;
+ }
+
+ [[nodiscard]] static RefPtr<AllSettledPromiseType> AllSettled(
+ nsISerialEventTarget* aProcessingTarget,
+ nsTArray<RefPtr<MozPromise>>& aPromises) {
+ if (aPromises.Length() == 0) {
+ return AllSettledPromiseType::CreateAndResolve(
+ CopyableTArray<ResolveOrRejectValue>(), __func__);
+ }
+
+ RefPtr<AllSettledPromiseHolder> holder =
+ new AllSettledPromiseHolder(aPromises.Length());
+ RefPtr<AllSettledPromiseType> promise = holder->Promise();
+ for (size_t i = 0; i < aPromises.Length(); ++i) {
+ aPromises[i]->Then(aProcessingTarget, __func__,
+ [holder, i](ResolveOrRejectValueParam aValue) -> void {
+ holder->Settle(i, MaybeMove(aValue));
+ });
+ }
+ return promise;
+ }
+
+ class Request : public MozPromiseRefcountable {
+ public:
+ virtual void Disconnect() = 0;
+
+ protected:
+ Request() : mComplete(false), mDisconnected(false) {}
+ virtual ~Request() = default;
+
+ bool mComplete;
+ bool mDisconnected;
+ };
+
+ protected:
+ /*
+ * A ThenValue tracks a single consumer waiting on the promise. When a
+ * consumer invokes promise->Then(...), a ThenValue is created. Once the
+ * Promise is resolved or rejected, a {Resolve,Reject}Runnable is dispatched,
+ * which invokes the resolve/reject method and then deletes the ThenValue.
+ */
+ class ThenValueBase : public Request {
+ friend class MozPromise;
+ static const uint32_t sMagic = 0xfadece11;
+
+ public:
+ class ResolveOrRejectRunnable : public CancelableRunnable {
+ public:
+ ResolveOrRejectRunnable(ThenValueBase* aThenValue, MozPromise* aPromise)
+ : CancelableRunnable(
+ "MozPromise::ThenValueBase::ResolveOrRejectRunnable"),
+ mThenValue(aThenValue),
+ mPromise(aPromise) {
+ MOZ_DIAGNOSTIC_ASSERT(!mPromise->IsPending());
+ }
+
+ ~ResolveOrRejectRunnable() {
+ if (mThenValue) {
+ mThenValue->AssertIsDead();
+ }
+ }
+
+ NS_IMETHOD Run() override {
+ PROMISE_LOG("ResolveOrRejectRunnable::Run() [this=%p]", this);
+ mThenValue->DoResolveOrReject(mPromise->Value());
+ mThenValue = nullptr;
+ mPromise = nullptr;
+ return NS_OK;
+ }
+
+ nsresult Cancel() override { return Run(); }
+
+ private:
+ RefPtr<ThenValueBase> mThenValue;
+ RefPtr<MozPromise> mPromise;
+ };
+
+ ThenValueBase(nsISerialEventTarget* aResponseTarget, const char* aCallSite)
+ : mResponseTarget(aResponseTarget), mCallSite(aCallSite) {
+ MOZ_ASSERT(aResponseTarget);
+ }
+
+# ifdef PROMISE_DEBUG
+ ~ThenValueBase() {
+ mMagic1 = 0;
+ mMagic2 = 0;
+ }
+# endif
+
+ void AssertIsDead() {
+ PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic);
+ // We want to assert that this ThenValues is dead - that is to say, that
+ // there are no consumers waiting for the result. In the case of a normal
+ // ThenValue, we check that it has been disconnected, which is the way
+ // that the consumer signals that it no longer wishes to hear about the
+ // result. If this ThenValue has a completion promise (which is mutually
+ // exclusive with being disconnectable), we recursively assert that every
+ // ThenValue associated with the completion promise is dead.
+ if (MozPromiseBase* p = CompletionPromise()) {
+ p->AssertIsDead();
+ } else {
+ MOZ_DIAGNOSTIC_ASSERT(Request::mDisconnected);
+ }
+ }
+
+ void Dispatch(MozPromise* aPromise) {
+ PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic);
+ aPromise->mMutex.AssertCurrentThreadOwns();
+ MOZ_ASSERT(!aPromise->IsPending());
+
+ nsCOMPtr<nsIRunnable> r = new ResolveOrRejectRunnable(this, aPromise);
+ PROMISE_LOG(
+ "%s Then() call made from %s [Runnable=%p, Promise=%p, ThenValue=%p] "
+ "%s dispatch",
+ aPromise->mValue.IsResolve() ? "Resolving" : "Rejecting", mCallSite,
+ r.get(), aPromise, this,
+ aPromise->mUseSynchronousTaskDispatch ? "synchronous"
+ : aPromise->mUseDirectTaskDispatch ? "directtask"
+ : "normal");
+
+ if (aPromise->mUseSynchronousTaskDispatch &&
+ mResponseTarget->IsOnCurrentThread()) {
+ PROMISE_LOG("ThenValue::Dispatch running task synchronously [this=%p]",
+ this);
+ r->Run();
+ return;
+ }
+
+ if (aPromise->mUseDirectTaskDispatch &&
+ mResponseTarget->IsOnCurrentThread()) {
+ PROMISE_LOG(
+ "ThenValue::Dispatch dispatch task via direct task queue [this=%p]",
+ this);
+ nsCOMPtr<nsIDirectTaskDispatcher> dispatcher =
+ do_QueryInterface(mResponseTarget);
+ if (dispatcher) {
+ dispatcher->DispatchDirectTask(r.forget());
+ return;
+ }
+ NS_WARNING(
+ nsPrintfCString(
+ "Direct Task dispatching not available for thread \"%s\"",
+ PR_GetThreadName(PR_GetCurrentThread()))
+ .get());
+ MOZ_DIAGNOSTIC_ASSERT(
+ false,
+ "mResponseTarget must implement nsIDirectTaskDispatcher for direct "
+ "task dispatching");
+ }
+
+ // Promise consumers are allowed to disconnect the Request object and
+ // then shut down the thread or task queue that the promise result would
+ // be dispatched on. So we unfortunately can't assert that promise
+ // dispatch succeeds. :-(
+ mResponseTarget->Dispatch(r.forget());
+ }
+
+ void Disconnect() override {
+ MOZ_DIAGNOSTIC_ASSERT(mResponseTarget->IsOnCurrentThread());
+ MOZ_DIAGNOSTIC_ASSERT(!Request::mComplete);
+ Request::mDisconnected = true;
+
+ // We could support rejecting the completion promise on disconnection, but
+ // then we'd need to have some sort of default reject value. The use cases
+ // of disconnection and completion promise chaining seem pretty
+ // orthogonal, so let's use assert against it.
+ MOZ_DIAGNOSTIC_ASSERT(!CompletionPromise());
+ }
+
+ protected:
+ virtual MozPromiseBase* CompletionPromise() const = 0;
+ virtual void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) = 0;
+
+ void DoResolveOrReject(ResolveOrRejectValue& aValue) {
+ PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic);
+ MOZ_DIAGNOSTIC_ASSERT(mResponseTarget->IsOnCurrentThread());
+ Request::mComplete = true;
+ if (Request::mDisconnected) {
+ PROMISE_LOG(
+ "ThenValue::DoResolveOrReject disconnected - bailing out [this=%p]",
+ this);
+ return;
+ }
+
+ // Invoke the resolve or reject method.
+ DoResolveOrRejectInternal(aValue);
+ }
+
+ nsCOMPtr<nsISerialEventTarget>
+ mResponseTarget; // May be released on any thread.
+# ifdef PROMISE_DEBUG
+ uint32_t mMagic1 = sMagic;
+# endif
+ const char* mCallSite;
+# ifdef PROMISE_DEBUG
+ uint32_t mMagic2 = sMagic;
+# endif
+ };
+
+ /*
+ * We create two overloads for invoking Resolve/Reject Methods so as to
+ * make the resolve/reject value argument "optional".
+ */
+ template <typename ThisType, typename MethodType, typename ValueType>
+ static std::enable_if_t<TakesArgument<MethodType>::value,
+ typename detail::MethodTrait<MethodType>::ReturnType>
+ InvokeMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue) {
+ return (aThisVal->*aMethod)(std::forward<ValueType>(aValue));
+ }
+
+ template <typename ThisType, typename MethodType, typename ValueType>
+ static std::enable_if_t<!TakesArgument<MethodType>::value,
+ typename detail::MethodTrait<MethodType>::ReturnType>
+ InvokeMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue) {
+ return (aThisVal->*aMethod)();
+ }
+
+ // Called when promise chaining is supported.
+ template <bool SupportChaining, typename ThisType, typename MethodType,
+ typename ValueType, typename CompletionPromiseType>
+ static std::enable_if_t<SupportChaining, void> InvokeCallbackMethod(
+ ThisType* aThisVal, MethodType aMethod, ValueType&& aValue,
+ CompletionPromiseType&& aCompletionPromise) {
+ auto p = InvokeMethod(aThisVal, aMethod, std::forward<ValueType>(aValue));
+ if (aCompletionPromise) {
+ p->ChainTo(aCompletionPromise.forget(), "<chained completion promise>");
+ }
+ }
+
+ // Called when promise chaining is not supported.
+ template <bool SupportChaining, typename ThisType, typename MethodType,
+ typename ValueType, typename CompletionPromiseType>
+ static std::enable_if_t<!SupportChaining, void> InvokeCallbackMethod(
+ ThisType* aThisVal, MethodType aMethod, ValueType&& aValue,
+ CompletionPromiseType&& aCompletionPromise) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ !aCompletionPromise,
+ "Can't do promise chaining for a non-promise-returning method.");
+ InvokeMethod(aThisVal, aMethod, std::forward<ValueType>(aValue));
+ }
+
+ template <typename>
+ class ThenCommand;
+
+ template <typename...>
+ class ThenValue;
+
+ template <typename ThisType, typename ResolveMethodType,
+ typename RejectMethodType>
+ class ThenValue<ThisType*, ResolveMethodType, RejectMethodType>
+ : public ThenValueBase {
+ friend class ThenCommand<ThenValue>;
+
+ using R1 = typename RemoveSmartPointer<
+ typename detail::MethodTrait<ResolveMethodType>::ReturnType>::Type;
+ using R2 = typename RemoveSmartPointer<
+ typename detail::MethodTrait<RejectMethodType>::ReturnType>::Type;
+ using SupportChaining =
+ std::integral_constant<bool, IsMozPromise<R1>::value &&
+ std::is_same_v<R1, R2>>;
+
+ // Fall back to MozPromise when promise chaining is not supported to make
+ // code compile.
+ using PromiseType =
+ std::conditional_t<SupportChaining::value, R1, MozPromise>;
+
+ public:
+ ThenValue(nsISerialEventTarget* aResponseTarget, ThisType* aThisVal,
+ ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod,
+ const char* aCallSite)
+ : ThenValueBase(aResponseTarget, aCallSite),
+ mThisVal(aThisVal),
+ mResolveMethod(aResolveMethod),
+ mRejectMethod(aRejectMethod) {}
+
+ void Disconnect() override {
+ ThenValueBase::Disconnect();
+
+ // If a Request has been disconnected, we don't guarantee that the
+ // resolve/reject runnable will be dispatched. Null out our refcounted
+ // this-value now so that it's released predictably on the dispatch
+ // thread.
+ mThisVal = nullptr;
+ }
+
+ protected:
+ MozPromiseBase* CompletionPromise() const override {
+ return mCompletionPromise;
+ }
+
+ void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override {
+ if (aValue.IsResolve()) {
+ InvokeCallbackMethod<SupportChaining::value>(
+ mThisVal.get(), mResolveMethod, MaybeMove(aValue.ResolveValue()),
+ std::move(mCompletionPromise));
+ } else {
+ InvokeCallbackMethod<SupportChaining::value>(
+ mThisVal.get(), mRejectMethod, MaybeMove(aValue.RejectValue()),
+ std::move(mCompletionPromise));
+ }
+
+ // Null out mThisVal after invoking the callback so that any references
+ // are released predictably on the dispatch thread. Otherwise, it would be
+ // released on whatever thread last drops its reference to the ThenValue,
+ // which may or may not be ok.
+ mThisVal = nullptr;
+ }
+
+ private:
+ RefPtr<ThisType>
+ mThisVal; // Only accessed and refcounted on dispatch thread.
+ ResolveMethodType mResolveMethod;
+ RejectMethodType mRejectMethod;
+ RefPtr<typename PromiseType::Private> mCompletionPromise;
+ };
+
+ template <typename ThisType, typename ResolveRejectMethodType>
+ class ThenValue<ThisType*, ResolveRejectMethodType> : public ThenValueBase {
+ friend class ThenCommand<ThenValue>;
+
+ using R1 = typename RemoveSmartPointer<typename detail::MethodTrait<
+ ResolveRejectMethodType>::ReturnType>::Type;
+ using SupportChaining =
+ std::integral_constant<bool, IsMozPromise<R1>::value>;
+
+ // Fall back to MozPromise when promise chaining is not supported to make
+ // code compile.
+ using PromiseType =
+ std::conditional_t<SupportChaining::value, R1, MozPromise>;
+
+ public:
+ ThenValue(nsISerialEventTarget* aResponseTarget, ThisType* aThisVal,
+ ResolveRejectMethodType aResolveRejectMethod,
+ const char* aCallSite)
+ : ThenValueBase(aResponseTarget, aCallSite),
+ mThisVal(aThisVal),
+ mResolveRejectMethod(aResolveRejectMethod) {}
+
+ void Disconnect() override {
+ ThenValueBase::Disconnect();
+
+ // If a Request has been disconnected, we don't guarantee that the
+ // resolve/reject runnable will be dispatched. Null out our refcounted
+ // this-value now so that it's released predictably on the dispatch
+ // thread.
+ mThisVal = nullptr;
+ }
+
+ protected:
+ MozPromiseBase* CompletionPromise() const override {
+ return mCompletionPromise;
+ }
+
+ void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override {
+ InvokeCallbackMethod<SupportChaining::value>(
+ mThisVal.get(), mResolveRejectMethod, MaybeMove(aValue),
+ std::move(mCompletionPromise));
+
+ // Null out mThisVal after invoking the callback so that any references
+ // are released predictably on the dispatch thread. Otherwise, it would be
+ // released on whatever thread last drops its reference to the ThenValue,
+ // which may or may not be ok.
+ mThisVal = nullptr;
+ }
+
+ private:
+ RefPtr<ThisType>
+ mThisVal; // Only accessed and refcounted on dispatch thread.
+ ResolveRejectMethodType mResolveRejectMethod;
+ RefPtr<typename PromiseType::Private> mCompletionPromise;
+ };
+
+ // NB: We could use std::function here instead of a template if it were
+ // supported. :-(
+ template <typename ResolveFunction, typename RejectFunction>
+ class ThenValue<ResolveFunction, RejectFunction> : public ThenValueBase {
+ friend class ThenCommand<ThenValue>;
+
+ using R1 = typename RemoveSmartPointer<
+ typename detail::MethodTrait<ResolveFunction>::ReturnType>::Type;
+ using R2 = typename RemoveSmartPointer<
+ typename detail::MethodTrait<RejectFunction>::ReturnType>::Type;
+ using SupportChaining =
+ std::integral_constant<bool, IsMozPromise<R1>::value &&
+ std::is_same_v<R1, R2>>;
+
+ // Fall back to MozPromise when promise chaining is not supported to make
+ // code compile.
+ using PromiseType =
+ std::conditional_t<SupportChaining::value, R1, MozPromise>;
+
+ public:
+ ThenValue(nsISerialEventTarget* aResponseTarget,
+ ResolveFunction&& aResolveFunction,
+ RejectFunction&& aRejectFunction, const char* aCallSite)
+ : ThenValueBase(aResponseTarget, aCallSite) {
+ mResolveFunction.emplace(std::move(aResolveFunction));
+ mRejectFunction.emplace(std::move(aRejectFunction));
+ }
+
+ void Disconnect() override {
+ ThenValueBase::Disconnect();
+
+ // If a Request has been disconnected, we don't guarantee that the
+ // resolve/reject runnable will be dispatched. Destroy our callbacks
+ // now so that any references in closures are released predictable on
+ // the dispatch thread.
+ mResolveFunction.reset();
+ mRejectFunction.reset();
+ }
+
+ protected:
+ MozPromiseBase* CompletionPromise() const override {
+ return mCompletionPromise;
+ }
+
+ void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override {
+ // Note: The usage of InvokeCallbackMethod here requires that
+ // ResolveFunction/RejectFunction are capture-lambdas (i.e. anonymous
+ // classes with ::operator()), since it allows us to share code more
+ // easily. We could fix this if need be, though it's quite easy to work
+ // around by just capturing something.
+ if (aValue.IsResolve()) {
+ InvokeCallbackMethod<SupportChaining::value>(
+ mResolveFunction.ptr(), &ResolveFunction::operator(),
+ MaybeMove(aValue.ResolveValue()), std::move(mCompletionPromise));
+ } else {
+ InvokeCallbackMethod<SupportChaining::value>(
+ mRejectFunction.ptr(), &RejectFunction::operator(),
+ MaybeMove(aValue.RejectValue()), std::move(mCompletionPromise));
+ }
+
+ // Destroy callbacks after invocation so that any references in closures
+ // are released predictably on the dispatch thread. Otherwise, they would
+ // be released on whatever thread last drops its reference to the
+ // ThenValue, which may or may not be ok.
+ mResolveFunction.reset();
+ mRejectFunction.reset();
+ }
+
+ private:
+ Maybe<ResolveFunction>
+ mResolveFunction; // Only accessed and deleted on dispatch thread.
+ Maybe<RejectFunction>
+ mRejectFunction; // Only accessed and deleted on dispatch thread.
+ RefPtr<typename PromiseType::Private> mCompletionPromise;
+ };
+
+ template <typename ResolveRejectFunction>
+ class ThenValue<ResolveRejectFunction> : public ThenValueBase {
+ friend class ThenCommand<ThenValue>;
+
+ using R1 = typename RemoveSmartPointer<
+ typename detail::MethodTrait<ResolveRejectFunction>::ReturnType>::Type;
+ using SupportChaining =
+ std::integral_constant<bool, IsMozPromise<R1>::value>;
+
+ // Fall back to MozPromise when promise chaining is not supported to make
+ // code compile.
+ using PromiseType =
+ std::conditional_t<SupportChaining::value, R1, MozPromise>;
+
+ public:
+ ThenValue(nsISerialEventTarget* aResponseTarget,
+ ResolveRejectFunction&& aResolveRejectFunction,
+ const char* aCallSite)
+ : ThenValueBase(aResponseTarget, aCallSite) {
+ mResolveRejectFunction.emplace(std::move(aResolveRejectFunction));
+ }
+
+ void Disconnect() override {
+ ThenValueBase::Disconnect();
+
+ // If a Request has been disconnected, we don't guarantee that the
+ // resolve/reject runnable will be dispatched. Destroy our callbacks
+ // now so that any references in closures are released predictable on
+ // the dispatch thread.
+ mResolveRejectFunction.reset();
+ }
+
+ protected:
+ MozPromiseBase* CompletionPromise() const override {
+ return mCompletionPromise;
+ }
+
+ void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override {
+ // Note: The usage of InvokeCallbackMethod here requires that
+ // ResolveRejectFunction is capture-lambdas (i.e. anonymous
+ // classes with ::operator()), since it allows us to share code more
+ // easily. We could fix this if need be, though it's quite easy to work
+ // around by just capturing something.
+ InvokeCallbackMethod<SupportChaining::value>(
+ mResolveRejectFunction.ptr(), &ResolveRejectFunction::operator(),
+ MaybeMove(aValue), std::move(mCompletionPromise));
+
+ // Destroy callbacks after invocation so that any references in closures
+ // are released predictably on the dispatch thread. Otherwise, they would
+ // be released on whatever thread last drops its reference to the
+ // ThenValue, which may or may not be ok.
+ mResolveRejectFunction.reset();
+ }
+
+ private:
+ Maybe<ResolveRejectFunction>
+ mResolveRejectFunction; // Only accessed and deleted on dispatch
+ // thread.
+ RefPtr<typename PromiseType::Private> mCompletionPromise;
+ };
+
+ public:
+ void ThenInternal(already_AddRefed<ThenValueBase> aThenValue,
+ const char* aCallSite) {
+ PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic &&
+ mMagic3 == sMagic && mMagic4 == &mMutex);
+ RefPtr<ThenValueBase> thenValue = aThenValue;
+ MutexAutoLock lock(mMutex);
+ MOZ_DIAGNOSTIC_ASSERT(
+ !IsExclusive || !mHaveRequest,
+ "Using an exclusive promise in a non-exclusive fashion");
+ mHaveRequest = true;
+ PROMISE_LOG("%s invoking Then() [this=%p, aThenValue=%p, isPending=%d]",
+ aCallSite, this, thenValue.get(), (int)IsPending());
+ if (!IsPending()) {
+ thenValue->Dispatch(this);
+ } else {
+ mThenValues.AppendElement(thenValue.forget());
+ }
+ }
+
+ protected:
+ /*
+ * A command object to store all information needed to make a request to
+ * the promise. This allows us to delay the request until further use is
+ * known (whether it is ->Then() again for more promise chaining or ->Track()
+ * to terminate chaining and issue the request).
+ *
+ * This allows a unified syntax for promise chaining and disconnection
+ * and feels more like its JS counterpart.
+ */
+ template <typename ThenValueType>
+ class ThenCommand {
+ // Allow Promise1::ThenCommand to access the private constructor,
+ // Promise2::ThenCommand(ThenCommand&&).
+ template <typename, typename, bool>
+ friend class MozPromise;
+
+ using PromiseType = typename ThenValueType::PromiseType;
+ using Private = typename PromiseType::Private;
+
+ ThenCommand(const char* aCallSite,
+ already_AddRefed<ThenValueType> aThenValue,
+ MozPromise* aReceiver)
+ : mCallSite(aCallSite), mThenValue(aThenValue), mReceiver(aReceiver) {}
+
+ ThenCommand(ThenCommand&& aOther) = default;
+
+ public:
+ ~ThenCommand() {
+ // Issue the request now if the return value of Then() is not used.
+ if (mThenValue) {
+ mReceiver->ThenInternal(mThenValue.forget(), mCallSite);
+ }
+ }
+
+ // Allow RefPtr<MozPromise> p = somePromise->Then();
+ // p->Then(thread1, ...);
+ // p->Then(thread2, ...);
+ operator RefPtr<PromiseType>() {
+ static_assert(
+ ThenValueType::SupportChaining::value,
+ "The resolve/reject callback needs to return a RefPtr<MozPromise> "
+ "in order to do promise chaining.");
+
+ // mCompletionPromise must be created before ThenInternal() to avoid race.
+ RefPtr<Private> p =
+ new Private("<completion promise>", true /* aIsCompletionPromise */);
+ mThenValue->mCompletionPromise = p;
+ // Note ThenInternal() might nullify mCompletionPromise before return.
+ // So we need to return p instead of mCompletionPromise.
+ mReceiver->ThenInternal(mThenValue.forget(), mCallSite);
+ return p;
+ }
+
+ template <typename... Ts>
+ auto Then(Ts&&... aArgs) -> decltype(
+ std::declval<PromiseType>().Then(std::forward<Ts>(aArgs)...)) {
+ return static_cast<RefPtr<PromiseType>>(*this)->Then(
+ std::forward<Ts>(aArgs)...);
+ }
+
+ void Track(MozPromiseRequestHolder<MozPromise>& aRequestHolder) {
+ aRequestHolder.Track(do_AddRef(mThenValue));
+ mReceiver->ThenInternal(mThenValue.forget(), mCallSite);
+ }
+
+ // Allow calling ->Then() again for more promise chaining or ->Track() to
+ // end chaining and track the request for future disconnection.
+ ThenCommand* operator->() { return this; }
+
+ private:
+ const char* mCallSite;
+ RefPtr<ThenValueType> mThenValue;
+ RefPtr<MozPromise> mReceiver;
+ };
+
+ public:
+ template <typename ThisType, typename... Methods,
+ typename ThenValueType = ThenValue<ThisType*, Methods...>,
+ typename ReturnType = ThenCommand<ThenValueType>>
+ ReturnType Then(nsISerialEventTarget* aResponseTarget, const char* aCallSite,
+ ThisType* aThisVal, Methods... aMethods) {
+ RefPtr<ThenValueType> thenValue =
+ new ThenValueType(aResponseTarget, aThisVal, aMethods..., aCallSite);
+ return ReturnType(aCallSite, thenValue.forget(), this);
+ }
+
+ template <typename... Functions,
+ typename ThenValueType = ThenValue<Functions...>,
+ typename ReturnType = ThenCommand<ThenValueType>>
+ ReturnType Then(nsISerialEventTarget* aResponseTarget, const char* aCallSite,
+ Functions&&... aFunctions) {
+ RefPtr<ThenValueType> thenValue =
+ new ThenValueType(aResponseTarget, std::move(aFunctions)..., aCallSite);
+ return ReturnType(aCallSite, thenValue.forget(), this);
+ }
+
+ void ChainTo(already_AddRefed<Private> aChainedPromise,
+ const char* aCallSite) {
+ MutexAutoLock lock(mMutex);
+ MOZ_DIAGNOSTIC_ASSERT(
+ !IsExclusive || !mHaveRequest,
+ "Using an exclusive promise in a non-exclusive fashion");
+ mHaveRequest = true;
+ RefPtr<Private> chainedPromise = aChainedPromise;
+ PROMISE_LOG(
+ "%s invoking Chain() [this=%p, chainedPromise=%p, isPending=%d]",
+ aCallSite, this, chainedPromise.get(), (int)IsPending());
+
+ // We want to use the same type of dispatching method with the chained
+ // promises.
+
+ // We need to ensure that the UseSynchronousTaskDispatch branch isn't taken
+ // at compilation time to ensure we're not triggering the static_assert in
+ // UseSynchronousTaskDispatch method. if constexpr (IsExclusive) ensures
+ // that.
+ if (mUseDirectTaskDispatch) {
+ chainedPromise->UseDirectTaskDispatch(aCallSite);
+ } else if constexpr (IsExclusive) {
+ if (mUseSynchronousTaskDispatch) {
+ chainedPromise->UseSynchronousTaskDispatch(aCallSite);
+ }
+ }
+
+ if (!IsPending()) {
+ ForwardTo(chainedPromise);
+ } else {
+ mChainedPromises.AppendElement(chainedPromise);
+ }
+ }
+
+# ifdef MOZ_WIDGET_ANDROID
+ // Creates a C++ MozPromise from its Java counterpart, GeckoResult.
+ [[nodiscard]] static RefPtr<MozPromise> FromGeckoResult(
+ java::GeckoResult::Param aGeckoResult) {
+ using jni::GeckoResultCallback;
+ RefPtr<Private> p = new Private("GeckoResult Glue", false);
+ auto resolve = GeckoResultCallback::CreateAndAttach<ResolveValueType>(
+ [p](ResolveValueType aArg) { p->Resolve(aArg, __func__); });
+ auto reject = GeckoResultCallback::CreateAndAttach<RejectValueType>(
+ [p](RejectValueType aArg) { p->Reject(aArg, __func__); });
+ aGeckoResult->NativeThen(resolve, reject);
+ return p;
+ }
+# endif
+
+ // Creates a C++ MozPromise from its JS counterpart, dom::Promise.
+ // FromDomPromise currently only supports primitive types (int8/16/32, float,
+ // double) And the reject value type must be a nsresult.
+ // To use, please include MozPromiseInlines.h
+ static RefPtr<MozPromise> FromDomPromise(dom::Promise* aDOMPromise);
+
+ // Note we expose the function AssertIsDead() instead of IsDead() since
+ // checking IsDead() is a data race in the situation where the request is not
+ // dead. Therefore we enforce the form |Assert(IsDead())| by exposing
+ // AssertIsDead() only.
+ void AssertIsDead() override {
+ PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic &&
+ mMagic3 == sMagic && mMagic4 == &mMutex);
+ MutexAutoLock lock(mMutex);
+ for (auto&& then : mThenValues) {
+ then->AssertIsDead();
+ }
+ for (auto&& chained : mChainedPromises) {
+ chained->AssertIsDead();
+ }
+ }
+
+ protected:
+ bool IsPending() const { return mValue.IsNothing(); }
+
+ ResolveOrRejectValue& Value() {
+ // This method should only be called once the value has stabilized. As
+ // such, we don't need to acquire the lock here.
+ MOZ_DIAGNOSTIC_ASSERT(!IsPending());
+ return mValue;
+ }
+
+ void DispatchAll() {
+ mMutex.AssertCurrentThreadOwns();
+ for (auto&& thenValue : mThenValues) {
+ thenValue->Dispatch(this);
+ }
+ mThenValues.Clear();
+
+ for (auto&& chainedPromise : mChainedPromises) {
+ ForwardTo(chainedPromise);
+ }
+ mChainedPromises.Clear();
+ }
+
+ void ForwardTo(Private* aOther) {
+ MOZ_ASSERT(!IsPending());
+ if (mValue.IsResolve()) {
+ aOther->Resolve(MaybeMove(mValue.ResolveValue()), "<chained promise>");
+ } else {
+ aOther->Reject(MaybeMove(mValue.RejectValue()), "<chained promise>");
+ }
+ }
+
+ virtual ~MozPromise() {
+ PROMISE_LOG("MozPromise::~MozPromise [this=%p]", this);
+ AssertIsDead();
+ // We can't guarantee a completion promise will always be revolved or
+ // rejected since ResolveOrRejectRunnable might not run when dispatch fails.
+ if (!mIsCompletionPromise) {
+ MOZ_ASSERT(!IsPending());
+ MOZ_ASSERT(mThenValues.IsEmpty());
+ MOZ_ASSERT(mChainedPromises.IsEmpty());
+ }
+# ifdef PROMISE_DEBUG
+ mMagic1 = 0;
+ mMagic2 = 0;
+ mMagic3 = 0;
+ mMagic4 = nullptr;
+# endif
+ };
+
+ const char* mCreationSite; // For logging
+ Mutex mMutex;
+ ResolveOrRejectValue mValue;
+ bool mUseSynchronousTaskDispatch = false;
+ bool mUseDirectTaskDispatch = false;
+# ifdef PROMISE_DEBUG
+ uint32_t mMagic1 = sMagic;
+# endif
+ // Try shows we never have more than 3 elements when IsExclusive is false.
+ // So '3' is a good value to avoid heap allocation in most cases.
+ AutoTArray<RefPtr<ThenValueBase>, IsExclusive ? 1 : 3> mThenValues;
+# ifdef PROMISE_DEBUG
+ uint32_t mMagic2 = sMagic;
+# endif
+ nsTArray<RefPtr<Private>> mChainedPromises;
+# ifdef PROMISE_DEBUG
+ uint32_t mMagic3 = sMagic;
+# endif
+ bool mHaveRequest;
+ const bool mIsCompletionPromise;
+# ifdef PROMISE_DEBUG
+ void* mMagic4;
+# endif
+};
+
+template <typename ResolveValueT, typename RejectValueT, bool IsExclusive>
+class MozPromise<ResolveValueT, RejectValueT, IsExclusive>::Private
+ : public MozPromise<ResolveValueT, RejectValueT, IsExclusive> {
+ public:
+ explicit Private(const char* aCreationSite, bool aIsCompletionPromise = false)
+ : MozPromise(aCreationSite, aIsCompletionPromise) {}
+
+ template <typename ResolveValueT_>
+ void Resolve(ResolveValueT_&& aResolveValue, const char* aResolveSite) {
+ PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic &&
+ mMagic3 == sMagic && mMagic4 == &mMutex);
+ MutexAutoLock lock(mMutex);
+ PROMISE_LOG("%s resolving MozPromise (%p created at %s)", aResolveSite,
+ this, mCreationSite);
+ if (!IsPending()) {
+ PROMISE_LOG(
+ "%s ignored already resolved or rejected MozPromise (%p created at "
+ "%s)",
+ aResolveSite, this, mCreationSite);
+ return;
+ }
+ mValue.SetResolve(std::forward<ResolveValueT_>(aResolveValue));
+ DispatchAll();
+ }
+
+ template <typename RejectValueT_>
+ void Reject(RejectValueT_&& aRejectValue, const char* aRejectSite) {
+ PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic &&
+ mMagic3 == sMagic && mMagic4 == &mMutex);
+ MutexAutoLock lock(mMutex);
+ PROMISE_LOG("%s rejecting MozPromise (%p created at %s)", aRejectSite, this,
+ mCreationSite);
+ if (!IsPending()) {
+ PROMISE_LOG(
+ "%s ignored already resolved or rejected MozPromise (%p created at "
+ "%s)",
+ aRejectSite, this, mCreationSite);
+ return;
+ }
+ mValue.SetReject(std::forward<RejectValueT_>(aRejectValue));
+ DispatchAll();
+ }
+
+ template <typename ResolveOrRejectValue_>
+ void ResolveOrReject(ResolveOrRejectValue_&& aValue, const char* aSite) {
+ PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic &&
+ mMagic3 == sMagic && mMagic4 == &mMutex);
+ MutexAutoLock lock(mMutex);
+ PROMISE_LOG("%s resolveOrRejecting MozPromise (%p created at %s)", aSite,
+ this, mCreationSite);
+ if (!IsPending()) {
+ PROMISE_LOG(
+ "%s ignored already resolved or rejected MozPromise (%p created at "
+ "%s)",
+ aSite, this, mCreationSite);
+ return;
+ }
+ mValue = std::forward<ResolveOrRejectValue_>(aValue);
+ DispatchAll();
+ }
+
+ // If the caller and target are both on the same thread, run the the resolve
+ // or reject callback synchronously. Otherwise, the task will be dispatched
+ // via the target Dispatch method.
+ void UseSynchronousTaskDispatch(const char* aSite) {
+ static_assert(
+ IsExclusive,
+ "Synchronous dispatch can only be used with exclusive promises");
+ PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic &&
+ mMagic3 == sMagic && mMagic4 == &mMutex);
+ MutexAutoLock lock(mMutex);
+ PROMISE_LOG("%s UseSynchronousTaskDispatch MozPromise (%p created at %s)",
+ aSite, this, mCreationSite);
+ MOZ_ASSERT(IsPending(),
+ "A Promise must not have been already resolved or rejected to "
+ "set dispatch state");
+ mUseSynchronousTaskDispatch = true;
+ }
+
+ // If the caller and target are both on the same thread, run the
+ // resolve/reject callback off the direct task queue instead. This avoids a
+ // full trip to the back of the event queue for each additional asynchronous
+ // step when using MozPromise, and is similar (but not identical to) the
+ // microtask semantics of JS promises.
+ void UseDirectTaskDispatch(const char* aSite) {
+ PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic &&
+ mMagic3 == sMagic && mMagic4 == &mMutex);
+ MutexAutoLock lock(mMutex);
+ PROMISE_LOG("%s UseDirectTaskDispatch MozPromise (%p created at %s)", aSite,
+ this, mCreationSite);
+ MOZ_ASSERT(IsPending(),
+ "A Promise must not have been already resolved or rejected to "
+ "set dispatch state");
+ MOZ_ASSERT(!mUseSynchronousTaskDispatch,
+ "Promise already set for synchronous dispatch");
+ mUseDirectTaskDispatch = true;
+ }
+};
+
+// A generic promise type that does the trick for simple use cases.
+typedef MozPromise<bool, nsresult, /* IsExclusive = */ true> GenericPromise;
+
+// A generic, non-exclusive promise type that does the trick for simple use
+// cases.
+typedef MozPromise<bool, nsresult, /* IsExclusive = */ false>
+ GenericNonExclusivePromise;
+
+/*
+ * Class to encapsulate a promise for a particular role. Use this as the member
+ * variable for a class whose method returns a promise.
+ */
+template <typename PromiseType, typename ImplType>
+class MozPromiseHolderBase {
+ public:
+ MozPromiseHolderBase() = default;
+
+ MozPromiseHolderBase(MozPromiseHolderBase&& aOther) = default;
+ MozPromiseHolderBase& operator=(MozPromiseHolderBase&& aOther) = default;
+
+ ~MozPromiseHolderBase() { MOZ_ASSERT(!mPromise); }
+
+ already_AddRefed<PromiseType> Ensure(const char* aMethodName) {
+ static_cast<ImplType*>(this)->Check();
+ if (!mPromise) {
+ mPromise = new (typename PromiseType::Private)(aMethodName);
+ }
+ RefPtr<PromiseType> p = mPromise.get();
+ return p.forget();
+ }
+
+ bool IsEmpty() const {
+ static_cast<const ImplType*>(this)->Check();
+ return !mPromise;
+ }
+
+ already_AddRefed<typename PromiseType::Private> Steal() {
+ static_cast<ImplType*>(this)->Check();
+ return mPromise.forget();
+ }
+
+ template <typename ResolveValueType_>
+ void Resolve(ResolveValueType_&& aResolveValue, const char* aMethodName) {
+ static_assert(std::is_convertible_v<ResolveValueType_,
+ typename PromiseType::ResolveValueType>,
+ "Resolve() argument must be implicitly convertible to "
+ "MozPromise's ResolveValueT");
+
+ static_cast<ImplType*>(this)->Check();
+ MOZ_ASSERT(mPromise);
+ mPromise->Resolve(std::forward<ResolveValueType_>(aResolveValue),
+ aMethodName);
+ mPromise = nullptr;
+ }
+
+ template <typename ResolveValueType_>
+ void ResolveIfExists(ResolveValueType_&& aResolveValue,
+ const char* aMethodName) {
+ if (!IsEmpty()) {
+ Resolve(std::forward<ResolveValueType_>(aResolveValue), aMethodName);
+ }
+ }
+
+ template <typename RejectValueType_>
+ void Reject(RejectValueType_&& aRejectValue, const char* aMethodName) {
+ static_assert(std::is_convertible_v<RejectValueType_,
+ typename PromiseType::RejectValueType>,
+ "Reject() argument must be implicitly convertible to "
+ "MozPromise's RejectValueT");
+
+ static_cast<ImplType*>(this)->Check();
+ MOZ_ASSERT(mPromise);
+ mPromise->Reject(std::forward<RejectValueType_>(aRejectValue), aMethodName);
+ mPromise = nullptr;
+ }
+
+ template <typename RejectValueType_>
+ void RejectIfExists(RejectValueType_&& aRejectValue,
+ const char* aMethodName) {
+ if (!IsEmpty()) {
+ Reject(std::forward<RejectValueType_>(aRejectValue), aMethodName);
+ }
+ }
+
+ template <typename ResolveOrRejectValueType_>
+ void ResolveOrReject(ResolveOrRejectValueType_&& aValue,
+ const char* aMethodName) {
+ static_cast<ImplType*>(this)->Check();
+ MOZ_ASSERT(mPromise);
+ mPromise->ResolveOrReject(std::forward<ResolveOrRejectValueType_>(aValue),
+ aMethodName);
+ mPromise = nullptr;
+ }
+
+ template <typename ResolveOrRejectValueType_>
+ void ResolveOrRejectIfExists(ResolveOrRejectValueType_&& aValue,
+ const char* aMethodName) {
+ if (!IsEmpty()) {
+ ResolveOrReject(std::forward<ResolveOrRejectValueType_>(aValue),
+ aMethodName);
+ }
+ }
+
+ void UseSynchronousTaskDispatch(const char* aSite) {
+ MOZ_ASSERT(mPromise);
+ mPromise->UseSynchronousTaskDispatch(aSite);
+ }
+
+ void UseDirectTaskDispatch(const char* aSite) {
+ MOZ_ASSERT(mPromise);
+ mPromise->UseDirectTaskDispatch(aSite);
+ }
+
+ private:
+ RefPtr<typename PromiseType::Private> mPromise;
+};
+
+template <typename PromiseType>
+class MozPromiseHolder
+ : public MozPromiseHolderBase<PromiseType, MozPromiseHolder<PromiseType>> {
+ public:
+ using MozPromiseHolderBase<
+ PromiseType, MozPromiseHolder<PromiseType>>::MozPromiseHolderBase;
+ static constexpr void Check(){};
+};
+
+template <typename PromiseType>
+class MozMonitoredPromiseHolder
+ : public MozPromiseHolderBase<PromiseType,
+ MozMonitoredPromiseHolder<PromiseType>> {
+ public:
+ // Provide a Monitor that should always be held when accessing this instance.
+ explicit MozMonitoredPromiseHolder(Monitor* const aMonitor)
+ : mMonitor(aMonitor) {
+ MOZ_ASSERT(aMonitor);
+ }
+
+ MozMonitoredPromiseHolder(MozMonitoredPromiseHolder&& aOther) = delete;
+ MozMonitoredPromiseHolder& operator=(MozMonitoredPromiseHolder&& aOther) =
+ delete;
+
+ void Check() const { mMonitor->AssertCurrentThreadOwns(); }
+
+ private:
+ Monitor* const mMonitor;
+};
+
+/*
+ * Class to encapsulate a MozPromise::Request reference. Use this as the member
+ * variable for a class waiting on a MozPromise.
+ */
+template <typename PromiseType>
+class MozPromiseRequestHolder {
+ public:
+ MozPromiseRequestHolder() = default;
+ ~MozPromiseRequestHolder() { MOZ_ASSERT(!mRequest); }
+
+ void Track(already_AddRefed<typename PromiseType::Request> aRequest) {
+ MOZ_DIAGNOSTIC_ASSERT(!Exists());
+ mRequest = aRequest;
+ }
+
+ void Complete() {
+ MOZ_DIAGNOSTIC_ASSERT(Exists());
+ mRequest = nullptr;
+ }
+
+ // Disconnects and forgets an outstanding promise. The resolve/reject methods
+ // will never be called.
+ void Disconnect() {
+ MOZ_ASSERT(Exists());
+ mRequest->Disconnect();
+ mRequest = nullptr;
+ }
+
+ void DisconnectIfExists() {
+ if (Exists()) {
+ Disconnect();
+ }
+ }
+
+ bool Exists() const { return !!mRequest; }
+
+ private:
+ RefPtr<typename PromiseType::Request> mRequest;
+};
+
+// Asynchronous Potentially-Cross-Thread Method Calls.
+//
+// This machinery allows callers to schedule a promise-returning function
+// (a method and object, or a function object like a lambda) to be invoked
+// asynchronously on a given thread, while at the same time receiving a
+// promise upon which to invoke Then() immediately. InvokeAsync dispatches a
+// task to invoke the function on the proper thread and also chain the
+// resulting promise to the one that the caller received, so that resolve/
+// reject values are forwarded through.
+
+namespace detail {
+
+// Non-templated base class to allow us to use MOZ_COUNT_{C,D}TOR, which cause
+// assertions when used on templated types.
+class MethodCallBase {
+ public:
+ MOZ_COUNTED_DEFAULT_CTOR(MethodCallBase)
+ MOZ_COUNTED_DTOR_VIRTUAL(MethodCallBase)
+};
+
+template <typename PromiseType, typename MethodType, typename ThisType,
+ typename... Storages>
+class MethodCall : public MethodCallBase {
+ public:
+ template <typename... Args>
+ MethodCall(MethodType aMethod, ThisType* aThisVal, Args&&... aArgs)
+ : mMethod(aMethod),
+ mThisVal(aThisVal),
+ mArgs(std::forward<Args>(aArgs)...) {
+ static_assert(sizeof...(Storages) == sizeof...(Args),
+ "Storages and Args should have equal sizes");
+ }
+
+ RefPtr<PromiseType> Invoke() { return mArgs.apply(mThisVal.get(), mMethod); }
+
+ private:
+ MethodType mMethod;
+ RefPtr<ThisType> mThisVal;
+ RunnableMethodArguments<Storages...> mArgs;
+};
+
+template <typename PromiseType, typename MethodType, typename ThisType,
+ typename... Storages>
+class ProxyRunnable : public CancelableRunnable {
+ public:
+ ProxyRunnable(
+ typename PromiseType::Private* aProxyPromise,
+ MethodCall<PromiseType, MethodType, ThisType, Storages...>* aMethodCall)
+ : CancelableRunnable("detail::ProxyRunnable"),
+ mProxyPromise(aProxyPromise),
+ mMethodCall(aMethodCall) {}
+
+ NS_IMETHOD Run() override {
+ RefPtr<PromiseType> p = mMethodCall->Invoke();
+ mMethodCall = nullptr;
+ p->ChainTo(mProxyPromise.forget(), "<Proxy Promise>");
+ return NS_OK;
+ }
+
+ nsresult Cancel() override { return Run(); }
+
+ private:
+ RefPtr<typename PromiseType::Private> mProxyPromise;
+ UniquePtr<MethodCall<PromiseType, MethodType, ThisType, Storages...>>
+ mMethodCall;
+};
+
+template <typename... Storages, typename PromiseType, typename ThisType,
+ typename... ArgTypes, typename... ActualArgTypes>
+static RefPtr<PromiseType> InvokeAsyncImpl(
+ nsISerialEventTarget* aTarget, ThisType* aThisVal, const char* aCallerName,
+ RefPtr<PromiseType> (ThisType::*aMethod)(ArgTypes...),
+ ActualArgTypes&&... aArgs) {
+ MOZ_ASSERT(aTarget);
+
+ typedef RefPtr<PromiseType> (ThisType::*MethodType)(ArgTypes...);
+ typedef detail::MethodCall<PromiseType, MethodType, ThisType, Storages...>
+ MethodCallType;
+ typedef detail::ProxyRunnable<PromiseType, MethodType, ThisType, Storages...>
+ ProxyRunnableType;
+
+ MethodCallType* methodCall = new MethodCallType(
+ aMethod, aThisVal, std::forward<ActualArgTypes>(aArgs)...);
+ RefPtr<typename PromiseType::Private> p =
+ new (typename PromiseType::Private)(aCallerName);
+ RefPtr<ProxyRunnableType> r = new ProxyRunnableType(p, methodCall);
+ aTarget->Dispatch(r.forget());
+ return p;
+}
+
+constexpr bool Any() { return false; }
+
+template <typename T1>
+constexpr bool Any(T1 a) {
+ return static_cast<bool>(a);
+}
+
+template <typename T1, typename... Ts>
+constexpr bool Any(T1 a, Ts... aOthers) {
+ return a || Any(aOthers...);
+}
+
+} // namespace detail
+
+// InvokeAsync with explicitly-specified storages.
+// See ParameterStorage in nsThreadUtils.h for help.
+template <typename... Storages, typename PromiseType, typename ThisType,
+ typename... ArgTypes, typename... ActualArgTypes,
+ std::enable_if_t<sizeof...(Storages) != 0, int> = 0>
+static RefPtr<PromiseType> InvokeAsync(
+ nsISerialEventTarget* aTarget, ThisType* aThisVal, const char* aCallerName,
+ RefPtr<PromiseType> (ThisType::*aMethod)(ArgTypes...),
+ ActualArgTypes&&... aArgs) {
+ static_assert(
+ sizeof...(Storages) == sizeof...(ArgTypes),
+ "Provided Storages and method's ArgTypes should have equal sizes");
+ static_assert(sizeof...(Storages) == sizeof...(ActualArgTypes),
+ "Provided Storages and ActualArgTypes should have equal sizes");
+ return detail::InvokeAsyncImpl<Storages...>(
+ aTarget, aThisVal, aCallerName, aMethod,
+ std::forward<ActualArgTypes>(aArgs)...);
+}
+
+// InvokeAsync with no explicitly-specified storages, will copy arguments and
+// then move them out of the runnable into the target method parameters.
+template <typename... Storages, typename PromiseType, typename ThisType,
+ typename... ArgTypes, typename... ActualArgTypes,
+ std::enable_if_t<sizeof...(Storages) == 0, int> = 0>
+static RefPtr<PromiseType> InvokeAsync(
+ nsISerialEventTarget* aTarget, ThisType* aThisVal, const char* aCallerName,
+ RefPtr<PromiseType> (ThisType::*aMethod)(ArgTypes...),
+ ActualArgTypes&&... aArgs) {
+ static_assert(
+ !detail::Any(
+ std::is_pointer_v<std::remove_reference_t<ActualArgTypes>>...),
+ "Cannot pass pointer types through InvokeAsync, Storages must be "
+ "provided");
+ static_assert(sizeof...(ArgTypes) == sizeof...(ActualArgTypes),
+ "Method's ArgTypes and ActualArgTypes should have equal sizes");
+ return detail::InvokeAsyncImpl<
+ StoreCopyPassByRRef<std::decay_t<ActualArgTypes>>...>(
+ aTarget, aThisVal, aCallerName, aMethod,
+ std::forward<ActualArgTypes>(aArgs)...);
+}
+
+namespace detail {
+
+template <typename Function, typename PromiseType>
+class ProxyFunctionRunnable : public CancelableRunnable {
+ using FunctionStorage = std::decay_t<Function>;
+
+ public:
+ template <typename F>
+ ProxyFunctionRunnable(typename PromiseType::Private* aProxyPromise,
+ F&& aFunction)
+ : CancelableRunnable("detail::ProxyFunctionRunnable"),
+ mProxyPromise(aProxyPromise),
+ mFunction(new FunctionStorage(std::forward<F>(aFunction))) {}
+
+ NS_IMETHOD Run() override {
+ RefPtr<PromiseType> p = (*mFunction)();
+ mFunction = nullptr;
+ p->ChainTo(mProxyPromise.forget(), "<Proxy Promise>");
+ return NS_OK;
+ }
+
+ nsresult Cancel() override { return Run(); }
+
+ private:
+ RefPtr<typename PromiseType::Private> mProxyPromise;
+ UniquePtr<FunctionStorage> mFunction;
+};
+
+// Note: The following struct and function are not for public consumption (yet?)
+// as we would prefer all calls to pass on-the-spot lambdas (or at least moved
+// function objects). They could be moved outside of detail if really needed.
+
+// We prefer getting function objects by non-lvalue-ref (to avoid copying them
+// and their captures). This struct is a tag that allows the use of objects
+// through lvalue-refs where necessary.
+struct AllowInvokeAsyncFunctionLVRef {};
+
+// Invoke a function object (e.g., lambda or std/mozilla::function)
+// asynchronously; note that the object will be copied if provided by
+// lvalue-ref. Return a promise that the function should eventually resolve or
+// reject.
+template <typename Function>
+static auto InvokeAsync(nsISerialEventTarget* aTarget, const char* aCallerName,
+ AllowInvokeAsyncFunctionLVRef, Function&& aFunction)
+ -> decltype(aFunction()) {
+ static_assert(
+ IsRefcountedSmartPointer<decltype(aFunction())>::value &&
+ IsMozPromise<
+ typename RemoveSmartPointer<decltype(aFunction())>::Type>::value,
+ "Function object must return RefPtr<MozPromise>");
+ MOZ_ASSERT(aTarget);
+ typedef typename RemoveSmartPointer<decltype(aFunction())>::Type PromiseType;
+ typedef detail::ProxyFunctionRunnable<Function, PromiseType>
+ ProxyRunnableType;
+
+ auto p = MakeRefPtr<typename PromiseType::Private>(aCallerName);
+ auto r = MakeRefPtr<ProxyRunnableType>(p, std::forward<Function>(aFunction));
+ aTarget->Dispatch(r.forget());
+ return p;
+}
+
+} // namespace detail
+
+// Invoke a function object (e.g., lambda) asynchronously.
+// Return a promise that the function should eventually resolve or reject.
+template <typename Function>
+static auto InvokeAsync(nsISerialEventTarget* aTarget, const char* aCallerName,
+ Function&& aFunction) -> decltype(aFunction()) {
+ static_assert(!std::is_lvalue_reference_v<Function>,
+ "Function object must not be passed by lvalue-ref (to avoid "
+ "unplanned copies); Consider move()ing the object.");
+ return detail::InvokeAsync(aTarget, aCallerName,
+ detail::AllowInvokeAsyncFunctionLVRef(),
+ std::forward<Function>(aFunction));
+}
+
+# undef PROMISE_LOG
+# undef PROMISE_ASSERT
+# undef PROMISE_DEBUG
+
+} // namespace mozilla
+
+#endif
diff --git a/xpcom/threads/MozPromiseInlines.h b/xpcom/threads/MozPromiseInlines.h
new file mode 100644
index 0000000000..28903917ba
--- /dev/null
+++ b/xpcom/threads/MozPromiseInlines.h
@@ -0,0 +1,47 @@
+/* -*- 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/. */
+
+#if !defined(MozPromiseInlines_h_)
+# define MozPromiseInlines_h_
+
+# include <type_traits>
+
+# include "mozilla/MozPromise.h"
+# include "mozilla/dom/PrimitiveConversions.h"
+# include "mozilla/dom/PromiseNativeHandler.h"
+
+namespace mozilla {
+
+// Creates a C++ MozPromise from its JS counterpart, dom::Promise.
+// FromDomPromise currently only supports primitive types (int8/16/32, float,
+// double) And the reject value type must be a nsresult.
+template <typename ResolveValueT, typename RejectValueT, bool IsExclusive>
+RefPtr<MozPromise<ResolveValueT, RejectValueT, IsExclusive>>
+MozPromise<ResolveValueT, RejectValueT, IsExclusive>::FromDomPromise(
+ dom::Promise* aDOMPromise) {
+ static_assert(std::is_same_v<RejectValueType, nsresult>,
+ "Reject type must be nsresult");
+ RefPtr<Private> p = new Private(__func__);
+ RefPtr<dom::DomPromiseListener> listener = new dom::DomPromiseListener(
+ aDOMPromise,
+ [p](JSContext* aCx, JS::Handle<JS::Value> aValue) {
+ ResolveValueT value;
+ bool ok = dom::ValueToPrimitive<ResolveValueT,
+ dom::ConversionBehavior::eDefault>(
+ aCx, aValue, "Resolution value", &value);
+ if (!ok) {
+ p->Reject(NS_ERROR_FAILURE, __func__);
+ return;
+ }
+ p->Resolve(value, __func__);
+ },
+ [p](nsresult aError) { p->Reject(aError, __func__); });
+ return p;
+}
+
+} // namespace mozilla
+
+#endif
diff --git a/xpcom/threads/Mutex.h b/xpcom/threads/Mutex.h
new file mode 100644
index 0000000000..e6d58806c9
--- /dev/null
+++ b/xpcom/threads/Mutex.h
@@ -0,0 +1,248 @@
+/* -*- 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_Mutex_h
+#define mozilla_Mutex_h
+
+#include "mozilla/BlockingResourceBase.h"
+#include "mozilla/PlatformMutex.h"
+#include "nsISupports.h"
+
+//
+// Provides:
+//
+// - Mutex, a non-recursive mutex
+// - MutexAutoLock, an RAII class for ensuring that Mutexes are properly
+// locked and unlocked
+// - MutexAutoUnlock, complementary sibling to MutexAutoLock
+//
+// - OffTheBooksMutex, a non-recursive mutex that doesn't do leak checking
+// - OffTheBooksMutexAuto{Lock,Unlock} - Like MutexAuto{Lock,Unlock}, but for
+// an OffTheBooksMutex.
+//
+// Using MutexAutoLock/MutexAutoUnlock etc. is MUCH preferred to making bare
+// calls to Lock and Unlock.
+//
+namespace mozilla {
+
+/**
+ * OffTheBooksMutex is identical to Mutex, except that OffTheBooksMutex doesn't
+ * include leak checking. Sometimes you want to intentionally "leak" a mutex
+ * until shutdown; in these cases, OffTheBooksMutex is for you.
+ */
+class OffTheBooksMutex : public detail::MutexImpl, BlockingResourceBase {
+ public:
+ /**
+ * @param aName A name which can reference this lock
+ * @returns If failure, nullptr
+ * If success, a valid Mutex* which must be destroyed
+ * by Mutex::DestroyMutex()
+ **/
+ explicit OffTheBooksMutex(const char* aName)
+ : BlockingResourceBase(aName, eMutex)
+#ifdef DEBUG
+ ,
+ mOwningThread(nullptr)
+#endif
+ {
+ }
+
+ ~OffTheBooksMutex() {
+#ifdef DEBUG
+ MOZ_ASSERT(!mOwningThread, "destroying a still-owned lock!");
+#endif
+ }
+
+#ifndef DEBUG
+ /**
+ * Lock this mutex.
+ **/
+ void Lock() { this->lock(); }
+
+ /**
+ * Try to lock this mutex, returning true if we were successful.
+ **/
+ [[nodiscard]] bool TryLock() { return this->tryLock(); }
+
+ /**
+ * Unlock this mutex.
+ **/
+ void Unlock() { this->unlock(); }
+
+ /**
+ * Assert that the current thread owns this mutex in debug builds.
+ *
+ * Does nothing in non-debug builds.
+ **/
+ void AssertCurrentThreadOwns() const {}
+
+ /**
+ * Assert that the current thread does not own this mutex.
+ *
+ * Note that this function is not implemented for debug builds *and*
+ * non-debug builds due to difficulties in dealing with memory ordering.
+ *
+ * It is therefore mostly useful as documentation.
+ **/
+ void AssertNotCurrentThreadOwns() const {}
+
+#else
+ void Lock();
+ [[nodiscard]] bool TryLock();
+ void Unlock();
+
+ void AssertCurrentThreadOwns() const;
+
+ void AssertNotCurrentThreadOwns() const {
+ // FIXME bug 476536
+ }
+
+#endif // ifndef DEBUG
+
+ private:
+ OffTheBooksMutex();
+ OffTheBooksMutex(const OffTheBooksMutex&);
+ OffTheBooksMutex& operator=(const OffTheBooksMutex&);
+
+ friend class OffTheBooksCondVar;
+
+#ifdef DEBUG
+ PRThread* mOwningThread;
+#endif
+};
+
+/**
+ * Mutex
+ * When possible, use MutexAutoLock/MutexAutoUnlock to lock/unlock this
+ * mutex within a scope, instead of calling Lock/Unlock directly.
+ */
+class Mutex : public OffTheBooksMutex {
+ public:
+ explicit Mutex(const char* aName) : OffTheBooksMutex(aName) {
+ MOZ_COUNT_CTOR(Mutex);
+ }
+
+ MOZ_COUNTED_DTOR(Mutex)
+
+ private:
+ Mutex();
+ Mutex(const Mutex&);
+ Mutex& operator=(const Mutex&);
+};
+
+namespace detail {
+template <typename T>
+class MOZ_RAII BaseAutoUnlock;
+
+/**
+ * MutexAutoLock
+ * Acquires the Mutex when it enters scope, and releases it when it leaves
+ * scope.
+ *
+ * MUCH PREFERRED to bare calls to Mutex.Lock and Unlock.
+ */
+template <typename T>
+class MOZ_RAII BaseAutoLock {
+ public:
+ /**
+ * Constructor
+ * The constructor aquires the given lock. The destructor
+ * releases the lock.
+ *
+ * @param aLock A valid mozilla::Mutex* returned by
+ * mozilla::Mutex::NewMutex.
+ **/
+ explicit BaseAutoLock(T aLock) : mLock(aLock) { mLock.Lock(); }
+
+ ~BaseAutoLock(void) { mLock.Unlock(); }
+
+ // Assert that aLock is the mutex passed to the constructor and that the
+ // current thread owns the mutex. In coding patterns such as:
+ //
+ // void LockedMethod(const BaseAutoLock<T>& aProofOfLock)
+ // {
+ // aProofOfLock.AssertOwns(mMutex);
+ // ...
+ // }
+ //
+ // Without this assertion, it could be that mMutex is not actually
+ // locked. It's possible to have code like:
+ //
+ // BaseAutoLock lock(someMutex);
+ // ...
+ // BaseAutoUnlock unlock(someMutex);
+ // ...
+ // LockedMethod(lock);
+ //
+ // and in such a case, simply asserting that the mutex pointers match is not
+ // sufficient; mutex ownership must be asserted as well.
+ //
+ // Note that if you are going to use the coding pattern presented above, you
+ // should use this method in preference to using AssertCurrentThreadOwns on
+ // the mutex you expected to be held, since this method provides stronger
+ // guarantees.
+ void AssertOwns(const T& aMutex) const {
+ MOZ_ASSERT(&aMutex == &mLock);
+ mLock.AssertCurrentThreadOwns();
+ }
+
+ private:
+ BaseAutoLock();
+ BaseAutoLock(BaseAutoLock&);
+ BaseAutoLock& operator=(BaseAutoLock&);
+ static void* operator new(size_t) noexcept(true);
+
+ friend class BaseAutoUnlock<T>;
+
+ T mLock;
+};
+
+template <typename MutexType>
+BaseAutoLock(MutexType&) -> BaseAutoLock<MutexType&>;
+} // namespace detail
+
+typedef detail::BaseAutoLock<Mutex&> MutexAutoLock;
+typedef detail::BaseAutoLock<OffTheBooksMutex&> OffTheBooksMutexAutoLock;
+
+namespace detail {
+/**
+ * BaseAutoUnlock
+ * Releases the Mutex when it enters scope, and re-acquires it when it leaves
+ * scope.
+ *
+ * MUCH PREFERRED to bare calls to Mutex.Unlock and Lock.
+ */
+template <typename T>
+class MOZ_RAII BaseAutoUnlock {
+ public:
+ explicit BaseAutoUnlock(T aLock) : mLock(aLock) { mLock.Unlock(); }
+
+ explicit BaseAutoUnlock(BaseAutoLock<T>& aAutoLock) : mLock(aAutoLock.mLock) {
+ NS_ASSERTION(mLock, "null lock");
+ mLock->Unlock();
+ }
+
+ ~BaseAutoUnlock() { mLock.Lock(); }
+
+ private:
+ BaseAutoUnlock();
+ BaseAutoUnlock(BaseAutoUnlock&);
+ BaseAutoUnlock& operator=(BaseAutoUnlock&);
+ static void* operator new(size_t) noexcept(true);
+
+ T mLock;
+};
+
+template <typename MutexType>
+BaseAutoUnlock(MutexType&) -> BaseAutoUnlock<MutexType&>;
+} // namespace detail
+
+typedef detail::BaseAutoUnlock<Mutex&> MutexAutoUnlock;
+typedef detail::BaseAutoUnlock<OffTheBooksMutex&> OffTheBooksMutexAutoUnlock;
+
+} // namespace mozilla
+
+#endif // ifndef mozilla_Mutex_h
diff --git a/xpcom/threads/PerformanceCounter.cpp b/xpcom/threads/PerformanceCounter.cpp
new file mode 100644
index 0000000000..65ee441809
--- /dev/null
+++ b/xpcom/threads/PerformanceCounter.cpp
@@ -0,0 +1,73 @@
+/* -*- 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/Logging.h"
+#include "mozilla/PerformanceCounter.h"
+
+using mozilla::DispatchCategory;
+using mozilla::DispatchCounter;
+using mozilla::PerformanceCounter;
+
+static mozilla::LazyLogModule sPerformanceCounter("PerformanceCounter");
+#ifdef LOG
+# undef LOG
+#endif
+#define LOG(args) MOZ_LOG(sPerformanceCounter, mozilla::LogLevel::Debug, args)
+
+// Global counter used by PerformanceCounter CTOR via NextCounterID().
+static mozilla::Atomic<uint64_t> gNextCounterID(0);
+
+static uint64_t NextCounterID() {
+ // This can return the same value on different processes but
+ // we're fine with this behavior because consumers can use a (pid, counter_id)
+ // tuple to make instances globally unique in a browser session.
+ return ++gNextCounterID;
+}
+
+// this instance is the extension for the worker
+const DispatchCategory DispatchCategory::Worker =
+ DispatchCategory((uint32_t)TaskCategory::Count);
+
+PerformanceCounter::PerformanceCounter(const nsACString& aName)
+ : mExecutionDuration(0),
+ mTotalDispatchCount(0),
+ mDispatchCounter(),
+ mName(aName),
+ mID(NextCounterID()) {
+ LOG(("PerformanceCounter created with ID %" PRIu64, mID));
+}
+
+void PerformanceCounter::IncrementDispatchCounter(DispatchCategory aCategory) {
+ mDispatchCounter[aCategory.GetValue()] += 1;
+ mTotalDispatchCount += 1;
+ LOG(("[%s][%" PRIu64 "] Total dispatch %" PRIu64, mName.get(), GetID(),
+ uint64_t(mTotalDispatchCount)));
+}
+
+void PerformanceCounter::IncrementExecutionDuration(uint32_t aMicroseconds) {
+ mExecutionDuration += aMicroseconds;
+ LOG(("[%s][%" PRIu64 "] Total duration %" PRIu64, mName.get(), GetID(),
+ uint64_t(mExecutionDuration)));
+}
+
+const DispatchCounter& PerformanceCounter::GetDispatchCounter() const {
+ return mDispatchCounter;
+}
+
+uint64_t PerformanceCounter::GetExecutionDuration() const {
+ return mExecutionDuration;
+}
+
+uint64_t PerformanceCounter::GetTotalDispatchCount() const {
+ return mTotalDispatchCount;
+}
+
+uint32_t PerformanceCounter::GetDispatchCount(
+ DispatchCategory aCategory) const {
+ return mDispatchCounter[aCategory.GetValue()];
+}
+
+uint64_t PerformanceCounter::GetID() const { return mID; }
diff --git a/xpcom/threads/PerformanceCounter.h b/xpcom/threads/PerformanceCounter.h
new file mode 100644
index 0000000000..4181320e10
--- /dev/null
+++ b/xpcom/threads/PerformanceCounter.h
@@ -0,0 +1,139 @@
+/* -*- 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_PerformanceCounter_h
+#define mozilla_PerformanceCounter_h
+
+#include "mozilla/Array.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/TaskCategory.h"
+#include "nsISupportsImpl.h"
+#include "nsString.h"
+
+namespace mozilla {
+
+/*
+ * The DispatchCategory class is used to fake the inheritance
+ * of the TaskCategory enum so we can extend it to hold
+ * one more value corresponding to the category
+ * we use when a worker dispatches a call.
+ *
+ */
+class DispatchCategory final {
+ public:
+ explicit DispatchCategory(uint32_t aValue) : mValue(aValue) {
+ // Since DispatchCategory is adding one single value to the
+ // TaskCategory enum, we can check here that the value is
+ // the next index e.g. TaskCategory::Count
+ MOZ_ASSERT(aValue == (uint32_t)TaskCategory::Count);
+ }
+
+ constexpr explicit DispatchCategory(TaskCategory aValue)
+ : mValue((uint32_t)aValue) {}
+
+ uint32_t GetValue() const { return mValue; }
+
+ static const DispatchCategory Worker;
+
+ private:
+ uint32_t mValue;
+};
+
+typedef Array<Atomic<uint32_t>, (uint32_t)TaskCategory::Count + 1>
+ DispatchCounter;
+
+// PerformanceCounter is a class that can be used to keep track of
+// runnable execution times and dispatch counts.
+//
+// - runnable execution time: time spent in a runnable when called
+// in nsThread::ProcessNextEvent (not counting recursive calls)
+// - dispatch counts: number of times a tracked runnable is dispatched
+// in nsThread. Useful to measure the activity of a tab or worker.
+//
+// The PerformanceCounter class is currently instantiated in DocGroup
+// and WorkerPrivate in order to count how many scheduler dispatches
+// are done through them, and how long the execution lasts.
+//
+// The execution time is calculated by the nsThread class (and its
+// inherited WorkerThread class) in its ProcessNextEvent method.
+//
+// For each processed runnable, nsThread will reach out the
+// PerformanceCounter attached to the runnable via its DocGroup
+// or WorkerPrivate and call IncrementExecutionDuration()
+//
+// Notice that the execution duration counting takes into account
+// recursivity. If an event triggers a recursive call to
+// nsThread::ProcessNextEVent, the counter will discard the time
+// spent in sub events.
+class PerformanceCounter final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PerformanceCounter)
+
+ explicit PerformanceCounter(const nsACString& aName);
+
+ /**
+ * This is called everytime a runnable is dispatched.
+ *
+ * aCategory can be used to distinguish counts per TaskCategory
+ *
+ * Note that an overflow will simply reset the counter.
+ */
+ void IncrementDispatchCounter(DispatchCategory aCategory);
+
+ /**
+ * This is called via nsThread::ProcessNextEvent to measure runnable
+ * execution duration.
+ *
+ * Note that an overflow will simply reset the counter.
+ */
+ void IncrementExecutionDuration(uint32_t aMicroseconds);
+
+ /**
+ * Returns a category/counter array of all dispatches.
+ */
+ const DispatchCounter& GetDispatchCounter() const;
+
+ /**
+ * Returns the total execution duration.
+ */
+ uint64_t GetExecutionDuration() const;
+
+ /**
+ * Returns the number of dispatches per TaskCategory.
+ */
+ uint32_t GetDispatchCount(DispatchCategory aCategory) const;
+
+ /**
+ * Returns the total number of dispatches.
+ */
+ uint64_t GetTotalDispatchCount() const;
+
+ /**
+ * Returns the unique id for the instance.
+ *
+ * Used to distinguish instances since the lifespan of
+ * a PerformanceCounter can be shorter than the
+ * host it's tracking. That leads to edge cases
+ * where a counter appears to have values that go
+ * backwards. Having this id let the consumers
+ * detect that they are dealing with a new counter
+ * when it happens.
+ */
+ uint64_t GetID() const;
+
+ private:
+ ~PerformanceCounter() = default;
+
+ Atomic<uint64_t> mExecutionDuration;
+ Atomic<uint64_t> mTotalDispatchCount;
+ DispatchCounter mDispatchCounter;
+ nsCString mName;
+ const uint64_t mID;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_PerformanceCounter_h
diff --git a/xpcom/threads/Queue.h b/xpcom/threads/Queue.h
new file mode 100644
index 0000000000..754224ca7e
--- /dev/null
+++ b/xpcom/threads/Queue.h
@@ -0,0 +1,242 @@
+/* -*- 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_Queue_h
+#define mozilla_Queue_h
+
+#include "mozilla/MemoryReporting.h"
+
+namespace mozilla {
+
+// define to turn on additional (DEBUG) asserts
+// #define EXTRA_ASSERTS 1
+
+// A queue implements a singly linked list of pages, each of which contains some
+// number of elements. Since the queue needs to store a "next" pointer, the
+// actual number of elements per page won't be quite as many as were requested.
+//
+// This class should only be used if it's valid to construct T elements from all
+// zeroes. The class also fails to call the destructor on items. However, it
+// will only destroy items after it has moved out their contents. The queue is
+// required to be empty when it is destroyed.
+//
+// Each page consists of N entries. We use the head buffer as a circular buffer
+// if it's the only buffer; if we have more than one buffer when the head is
+// empty we release it. This avoids occasional freeing and reallocating buffers
+// every N entries. We'll still allocate and free every N if the normal queue
+// depth is greated than N. A fancier solution would be to move an empty Head
+// buffer to be an empty tail buffer, freeing if we have multiple empty tails,
+// but that probably isn't worth it.
+//
+// Cases:
+// a) single buffer, circular
+// Push: if not full:
+// Add to tail, bump tail and reset to 0 if at end
+// full:
+// Add new page, insert there and set tail to 1
+// Pop:
+// take entry and bump head, reset to 0 if at end
+// b) multiple buffers:
+// Push: if not full:
+// Add to tail, bump tail
+// full:
+// Add new page, insert there and set tail to 1
+// Pop:
+// take entry and bump head, reset to 0 if at end
+// if buffer is empty, free head buffer and promote next to head
+//
+template <class T, size_t RequestedItemsPerPage = 256>
+class Queue {
+ public:
+ Queue() = default;
+
+ ~Queue() {
+ MOZ_ASSERT(IsEmpty());
+
+ if (mHead) {
+ free(mHead);
+ }
+ }
+
+ T& Push(T&& aElement) {
+#if defined(EXTRA_ASSERTS) && DEBUG
+ size_t original_length = Count();
+#endif
+ if (!mHead) {
+ mHead = NewPage();
+ MOZ_ASSERT(mHead);
+
+ mTail = mHead;
+ T& eltLocation = mTail->mEvents[0];
+ eltLocation = std::move(aElement);
+ mOffsetHead = 0;
+ mHeadLength = 1;
+#ifdef EXTRA_ASSERTS
+ MOZ_ASSERT(Count() == original_length + 1);
+#endif
+ return eltLocation;
+ }
+ if ((mHead == mTail && mHeadLength == ItemsPerPage) ||
+ (mHead != mTail && mTailLength == ItemsPerPage)) {
+ // either we have one (circular) buffer and it's full, or
+ // we have multiple buffers and the last buffer is full
+ Page* page = NewPage();
+ MOZ_ASSERT(page);
+
+ mTail->mNext = page;
+ mTail = page;
+ T& eltLocation = page->mEvents[0];
+ eltLocation = std::move(aElement);
+ mTailLength = 1;
+#ifdef EXTRA_ASSERTS
+ MOZ_ASSERT(Count() == original_length + 1);
+#endif
+ return eltLocation;
+ }
+ if (mHead == mTail) {
+ // we have space in the (single) head buffer
+ uint16_t offset = (mOffsetHead + mHeadLength++) % ItemsPerPage;
+ T& eltLocation = mTail->mEvents[offset];
+ eltLocation = std::move(aElement);
+#ifdef EXTRA_ASSERTS
+ MOZ_ASSERT(Count() == original_length + 1);
+#endif
+ return eltLocation;
+ }
+ // else we have space to insert into last buffer
+ T& eltLocation = mTail->mEvents[mTailLength++];
+ eltLocation = std::move(aElement);
+#ifdef EXTRA_ASSERTS
+ MOZ_ASSERT(Count() == original_length + 1);
+#endif
+ return eltLocation;
+ }
+
+ bool IsEmpty() const {
+ return !mHead || (mHead == mTail && mHeadLength == 0);
+ }
+
+ T Pop() {
+#if defined(EXTRA_ASSERTS) && DEBUG
+ size_t original_length = Count();
+#endif
+ MOZ_ASSERT(!IsEmpty());
+
+ T result = std::move(mHead->mEvents[mOffsetHead]);
+ mOffsetHead = (mOffsetHead + 1) % ItemsPerPage;
+ mHeadLength -= 1;
+
+ // Check if mHead points to empty (circular) Page and we have more
+ // pages
+ if (mHead != mTail && mHeadLength == 0) {
+ Page* dead = mHead;
+ mHead = mHead->mNext;
+ free(dead);
+ mOffsetHead = 0;
+ // if there are still >1 pages, the new head is full.
+ if (mHead != mTail) {
+ mHeadLength = ItemsPerPage;
+ } else {
+ mHeadLength = mTailLength;
+ mTailLength = 0;
+ }
+ }
+
+#ifdef EXTRA_ASSERTS
+ MOZ_ASSERT(Count() == original_length - 1);
+#endif
+ return result;
+ }
+
+ T& FirstElement() {
+ MOZ_ASSERT(!IsEmpty());
+ return mHead->mEvents[mOffsetHead];
+ }
+
+ const T& FirstElement() const {
+ MOZ_ASSERT(!IsEmpty());
+ return mHead->mEvents[mOffsetHead];
+ }
+
+ T& LastElement() {
+ MOZ_ASSERT(!IsEmpty());
+ uint16_t offset =
+ mHead == mTail ? mOffsetHead + mHeadLength - 1 : mTailLength - 1;
+ return mTail->mEvents[offset];
+ }
+
+ const T& LastElement() const {
+ MOZ_ASSERT(!IsEmpty());
+ uint16_t offset =
+ mHead == mTail ? mOffsetHead + mHeadLength - 1 : mTailLength - 1;
+ return mTail->mEvents[offset];
+ }
+
+ size_t Count() const {
+ // It is obvious count is 0 when the queue is empty.
+ if (!mHead) {
+ return 0;
+ }
+
+ // Compute full (intermediate) pages; Doesn't count first or last page
+ int count = 0;
+ // 1 buffer will have mHead == mTail; 2 will have mHead->mNext == mTail
+ for (Page* page = mHead; page != mTail && page->mNext != mTail;
+ page = page->mNext) {
+ count += ItemsPerPage;
+ }
+ // add first and last page
+ count += mHeadLength + mTailLength;
+ MOZ_ASSERT(count >= 0);
+
+ return count;
+ }
+
+ size_t ShallowSizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t n = 0;
+ if (mHead) {
+ for (Page* page = mHead; page != mTail; page = page->mNext) {
+ n += aMallocSizeOf(page);
+ }
+ }
+ return n;
+ }
+
+ size_t ShallowSizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ private:
+ static_assert(
+ (RequestedItemsPerPage & (RequestedItemsPerPage - 1)) == 0,
+ "RequestedItemsPerPage should be a power of two to avoid heap slop.");
+
+ // Since a Page must also contain a "next" pointer, we use one of the items to
+ // store this pointer. If sizeof(T) > sizeof(Page*), then some space will be
+ // wasted. So be it.
+ static const size_t ItemsPerPage = RequestedItemsPerPage - 1;
+
+ // Page objects are linked together to form a simple deque.
+ struct Page {
+ struct Page* mNext;
+ T mEvents[ItemsPerPage];
+ };
+
+ static Page* NewPage() {
+ return static_cast<Page*>(moz_xcalloc(1, sizeof(Page)));
+ }
+
+ Page* mHead = nullptr;
+ Page* mTail = nullptr;
+
+ uint16_t mOffsetHead = 0; // Read position in head page
+ uint16_t mHeadLength = 0; // Number of items in the head page
+ uint16_t mTailLength = 0; // Number of items in the tail page
+};
+
+} // namespace mozilla
+
+#endif // mozilla_Queue_h
diff --git a/xpcom/threads/RWLock.cpp b/xpcom/threads/RWLock.cpp
new file mode 100644
index 0000000000..ffdd78ceb3
--- /dev/null
+++ b/xpcom/threads/RWLock.cpp
@@ -0,0 +1,87 @@
+/* -*- 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/RWLock.h"
+
+#ifdef XP_WIN
+# include <windows.h>
+
+static_assert(sizeof(SRWLOCK) <= sizeof(void*), "SRWLOCK is too big!");
+
+# define NativeHandle(m) (reinterpret_cast<SRWLOCK*>(&m))
+#else
+# define NativeHandle(m) (&m)
+#endif
+
+namespace mozilla {
+
+RWLock::RWLock(const char* aName)
+ : BlockingResourceBase(aName, eMutex)
+#ifdef DEBUG
+ ,
+ mOwningThread(nullptr)
+#endif
+{
+#ifdef XP_WIN
+ InitializeSRWLock(NativeHandle(mRWLock));
+#else
+ MOZ_RELEASE_ASSERT(pthread_rwlock_init(NativeHandle(mRWLock), nullptr) == 0,
+ "pthread_rwlock_init failed");
+#endif
+}
+
+#ifdef DEBUG
+bool RWLock::LockedForWritingByCurrentThread() {
+ return mOwningThread == PR_GetCurrentThread();
+}
+#endif
+
+#ifndef XP_WIN
+RWLock::~RWLock() {
+ MOZ_RELEASE_ASSERT(pthread_rwlock_destroy(NativeHandle(mRWLock)) == 0,
+ "pthread_rwlock_destroy failed");
+}
+#endif
+
+void RWLock::ReadLockInternal() {
+#ifdef XP_WIN
+ AcquireSRWLockShared(NativeHandle(mRWLock));
+#else
+ MOZ_RELEASE_ASSERT(pthread_rwlock_rdlock(NativeHandle(mRWLock)) == 0,
+ "pthread_rwlock_rdlock failed");
+#endif
+}
+
+void RWLock::ReadUnlockInternal() {
+#ifdef XP_WIN
+ ReleaseSRWLockShared(NativeHandle(mRWLock));
+#else
+ MOZ_RELEASE_ASSERT(pthread_rwlock_unlock(NativeHandle(mRWLock)) == 0,
+ "pthread_rwlock_unlock failed");
+#endif
+}
+
+void RWLock::WriteLockInternal() {
+#ifdef XP_WIN
+ AcquireSRWLockExclusive(NativeHandle(mRWLock));
+#else
+ MOZ_RELEASE_ASSERT(pthread_rwlock_wrlock(NativeHandle(mRWLock)) == 0,
+ "pthread_rwlock_wrlock failed");
+#endif
+}
+
+void RWLock::WriteUnlockInternal() {
+#ifdef XP_WIN
+ ReleaseSRWLockExclusive(NativeHandle(mRWLock));
+#else
+ MOZ_RELEASE_ASSERT(pthread_rwlock_unlock(NativeHandle(mRWLock)) == 0,
+ "pthread_rwlock_unlock failed");
+#endif
+}
+
+} // namespace mozilla
+
+#undef NativeHandle
diff --git a/xpcom/threads/RWLock.h b/xpcom/threads/RWLock.h
new file mode 100644
index 0000000000..13d711ae8b
--- /dev/null
+++ b/xpcom/threads/RWLock.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/. */
+
+// An interface for read-write locks.
+
+#ifndef mozilla_RWLock_h
+#define mozilla_RWLock_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/BlockingResourceBase.h"
+
+#ifndef XP_WIN
+# include <pthread.h>
+#endif
+
+namespace mozilla {
+
+// A RWLock is similar to a Mutex, but whereas a Mutex permits only a single
+// reader thread or a single writer thread to access a piece of data, a
+// RWLock distinguishes between readers and writers: you may have multiple
+// reader threads concurrently accessing a piece of data or a single writer
+// thread. This difference should guide your usage of RWLock: if you are not
+// reading the data from multiple threads simultaneously or you are writing
+// to the data roughly as often as read from it, then Mutex will suit your
+// purposes just fine.
+//
+// You should be using the AutoReadLock and AutoWriteLock classes, below,
+// for RAII read locking and write locking, respectively. If you really must
+// take a read lock manually, call the ReadLock method; to relinquish that
+// read lock, call the ReadUnlock method. Similarly, WriteLock and WriteUnlock
+// perform the same operations, but for write locks.
+//
+// It is unspecified what happens when a given thread attempts to acquire the
+// same lock in multiple ways; some underlying implementations of RWLock do
+// support acquiring a read lock multiple times on a given thread, but you
+// should not rely on this behavior.
+//
+// It is unspecified whether RWLock gives priority to waiting readers or
+// a waiting writer when unlocking.
+class RWLock : public BlockingResourceBase {
+ public:
+ explicit RWLock(const char* aName);
+
+ // Windows rwlocks don't need any special handling to be destroyed, but
+ // POSIX ones do.
+#ifdef XP_WIN
+ ~RWLock() = default;
+#else
+ ~RWLock();
+#endif
+
+#ifdef DEBUG
+ bool LockedForWritingByCurrentThread();
+ void ReadLock();
+ void ReadUnlock();
+ void WriteLock();
+ void WriteUnlock();
+#else
+ void ReadLock() { ReadLockInternal(); }
+ void ReadUnlock() { ReadUnlockInternal(); }
+ void WriteLock() { WriteLockInternal(); }
+ void WriteUnlock() { WriteUnlockInternal(); }
+#endif
+
+ private:
+ void ReadLockInternal();
+ void ReadUnlockInternal();
+ void WriteLockInternal();
+ void WriteUnlockInternal();
+
+ RWLock() = delete;
+ RWLock(const RWLock&) = delete;
+ RWLock& operator=(const RWLock&) = delete;
+
+#ifndef XP_WIN
+ pthread_rwlock_t mRWLock;
+#else
+ // SRWLock is pointer-sized. We declare it in such a fashion here to
+ // avoid pulling in windows.h wherever this header is used.
+ void* mRWLock;
+#endif
+
+#ifdef DEBUG
+ // We record the owning thread for write locks only.
+ PRThread* mOwningThread;
+#endif
+};
+
+template <typename T>
+class MOZ_RAII BaseAutoReadLock {
+ public:
+ explicit BaseAutoReadLock(T& aLock) : mLock(&aLock) {
+ MOZ_ASSERT(mLock, "null lock");
+ mLock->ReadLock();
+ }
+
+ ~BaseAutoReadLock() { mLock->ReadUnlock(); }
+
+ private:
+ BaseAutoReadLock() = delete;
+ BaseAutoReadLock(const BaseAutoReadLock&) = delete;
+ BaseAutoReadLock& operator=(const BaseAutoReadLock&) = delete;
+
+ T* mLock;
+};
+
+template <typename T>
+class MOZ_RAII BaseAutoWriteLock final {
+ public:
+ explicit BaseAutoWriteLock(T& aLock) : mLock(&aLock) {
+ MOZ_ASSERT(mLock, "null lock");
+ mLock->WriteLock();
+ }
+
+ ~BaseAutoWriteLock() { mLock->WriteUnlock(); }
+
+ private:
+ BaseAutoWriteLock() = delete;
+ BaseAutoWriteLock(const BaseAutoWriteLock&) = delete;
+ BaseAutoWriteLock& operator=(const BaseAutoWriteLock&) = delete;
+
+ T* mLock;
+};
+
+// Read lock and unlock a RWLock with RAII semantics. Much preferred to bare
+// calls to ReadLock() and ReadUnlock().
+typedef BaseAutoReadLock<RWLock> AutoReadLock;
+
+// Write lock and unlock a RWLock with RAII semantics. Much preferred to bare
+// calls to WriteLock() and WriteUnlock().
+typedef BaseAutoWriteLock<RWLock> AutoWriteLock;
+
+// XXX: normally we would define StaticRWLock as
+// MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS, but the contexts in which it
+// is used (e.g. member variables in a third-party library) are non-trivial
+// to modify to properly declare everything at static scope. As those
+// third-party libraries are the only clients, put it behind the detail
+// namespace to discourage other (possibly erroneous) uses from popping up.
+
+namespace detail {
+
+class StaticRWLock {
+ public:
+ // In debug builds, check that mLock is initialized for us as we expect by
+ // the compiler. In non-debug builds, don't declare a constructor so that
+ // the compiler can see that the constructor is trivial.
+#ifdef DEBUG
+ StaticRWLock() { MOZ_ASSERT(!mLock); }
+#endif
+
+ void ReadLock() { Lock()->ReadLock(); }
+ void ReadUnlock() { Lock()->ReadUnlock(); }
+ void WriteLock() { Lock()->WriteLock(); }
+ void WriteUnlock() { Lock()->WriteUnlock(); }
+
+ private:
+ RWLock* Lock() {
+ if (mLock) {
+ return mLock;
+ }
+
+ RWLock* lock = new RWLock("StaticRWLock");
+ if (!mLock.compareExchange(nullptr, lock)) {
+ delete lock;
+ }
+
+ return mLock;
+ }
+
+ Atomic<RWLock*> mLock;
+
+ // Disallow copy constructor, but only in debug mode. We only define
+ // a default constructor in debug mode (see above); if we declared
+ // this constructor always, the compiler wouldn't generate a trivial
+ // default constructor for us in non-debug mode.
+#ifdef DEBUG
+ StaticRWLock(const StaticRWLock& aOther);
+#endif
+
+ // Disallow these operators.
+ StaticRWLock& operator=(StaticRWLock* aRhs);
+ static void* operator new(size_t) noexcept(true);
+ static void operator delete(void*);
+};
+
+typedef BaseAutoReadLock<StaticRWLock> StaticAutoReadLock;
+typedef BaseAutoWriteLock<StaticRWLock> StaticAutoWriteLock;
+
+} // namespace detail
+
+} // namespace mozilla
+
+#endif // mozilla_RWLock_h
diff --git a/xpcom/threads/RecursiveMutex.cpp b/xpcom/threads/RecursiveMutex.cpp
new file mode 100644
index 0000000000..7c45052c07
--- /dev/null
+++ b/xpcom/threads/RecursiveMutex.cpp
@@ -0,0 +1,85 @@
+/* -*- 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/RecursiveMutex.h"
+
+#ifdef XP_WIN
+# include <windows.h>
+
+# define NativeHandle(m) (reinterpret_cast<CRITICAL_SECTION*>(&m))
+#endif
+
+namespace mozilla {
+
+RecursiveMutex::RecursiveMutex(const char* aName)
+ : BlockingResourceBase(aName, eRecursiveMutex)
+#ifdef DEBUG
+ ,
+ mOwningThread(nullptr),
+ mEntryCount(0)
+#endif
+{
+#ifdef XP_WIN
+ // This number was adapted from NSPR.
+ static const DWORD sLockSpinCount = 100;
+
+# if defined(RELEASE_OR_BETA)
+ // Vista and later automatically allocate and subsequently leak a debug info
+ // object for each critical section that we allocate unless we tell the
+ // system not to do that.
+ DWORD flags = CRITICAL_SECTION_NO_DEBUG_INFO;
+# else
+ DWORD flags = 0;
+# endif
+ BOOL r =
+ InitializeCriticalSectionEx(NativeHandle(mMutex), sLockSpinCount, flags);
+ MOZ_RELEASE_ASSERT(r);
+#else
+ pthread_mutexattr_t attr;
+
+ MOZ_RELEASE_ASSERT(pthread_mutexattr_init(&attr) == 0,
+ "pthread_mutexattr_init failed");
+
+ MOZ_RELEASE_ASSERT(
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) == 0,
+ "pthread_mutexattr_settype failed");
+
+ MOZ_RELEASE_ASSERT(pthread_mutex_init(&mMutex, &attr) == 0,
+ "pthread_mutex_init failed");
+
+ MOZ_RELEASE_ASSERT(pthread_mutexattr_destroy(&attr) == 0,
+ "pthread_mutexattr_destroy failed");
+#endif
+}
+
+RecursiveMutex::~RecursiveMutex() {
+#ifdef XP_WIN
+ DeleteCriticalSection(NativeHandle(mMutex));
+#else
+ MOZ_RELEASE_ASSERT(pthread_mutex_destroy(&mMutex) == 0,
+ "pthread_mutex_destroy failed");
+#endif
+}
+
+void RecursiveMutex::LockInternal() {
+#ifdef XP_WIN
+ EnterCriticalSection(NativeHandle(mMutex));
+#else
+ MOZ_RELEASE_ASSERT(pthread_mutex_lock(&mMutex) == 0,
+ "pthread_mutex_lock failed");
+#endif
+}
+
+void RecursiveMutex::UnlockInternal() {
+#ifdef XP_WIN
+ LeaveCriticalSection(NativeHandle(mMutex));
+#else
+ MOZ_RELEASE_ASSERT(pthread_mutex_unlock(&mMutex) == 0,
+ "pthread_mutex_unlock failed");
+#endif
+}
+
+} // namespace mozilla
diff --git a/xpcom/threads/RecursiveMutex.h b/xpcom/threads/RecursiveMutex.h
new file mode 100644
index 0000000000..4dd256a039
--- /dev/null
+++ b/xpcom/threads/RecursiveMutex.h
@@ -0,0 +1,112 @@
+/* -*- 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/. */
+
+// A lock that can be acquired multiple times on the same thread.
+
+#ifndef mozilla_RecursiveMutex_h
+#define mozilla_RecursiveMutex_h
+
+#include "mozilla/BlockingResourceBase.h"
+
+#ifndef XP_WIN
+# include <pthread.h>
+#endif
+
+namespace mozilla {
+
+class RecursiveMutex : public BlockingResourceBase {
+ public:
+ explicit RecursiveMutex(const char* aName);
+ ~RecursiveMutex();
+
+#ifdef DEBUG
+ void Lock();
+ void Unlock();
+#else
+ void Lock() { LockInternal(); }
+ void Unlock() { UnlockInternal(); }
+#endif
+
+#ifdef DEBUG
+ /**
+ * AssertCurrentThreadIn
+ **/
+ void AssertCurrentThreadIn();
+ /**
+ * AssertNotCurrentThreadIn
+ **/
+ void AssertNotCurrentThreadIn() {
+ // Not currently implemented. See bug 476536 for discussion.
+ }
+#else
+ void AssertCurrentThreadIn() {}
+ void AssertNotCurrentThreadIn() {}
+#endif
+
+ private:
+ RecursiveMutex() = delete;
+ RecursiveMutex(const RecursiveMutex&) = delete;
+ RecursiveMutex& operator=(const RecursiveMutex&) = delete;
+
+ void LockInternal();
+ void UnlockInternal();
+
+#ifdef DEBUG
+ PRThread* mOwningThread;
+ size_t mEntryCount;
+#endif
+
+#if !defined(XP_WIN)
+ pthread_mutex_t mMutex;
+#else
+ // We eschew including windows.h and using CRITICAL_SECTION here so that files
+ // including us don't also pull in windows.h. Just use a type that's big
+ // enough for CRITICAL_SECTION, and we'll fix it up later.
+ void* mMutex[6];
+#endif
+};
+
+class MOZ_RAII RecursiveMutexAutoLock {
+ public:
+ explicit RecursiveMutexAutoLock(RecursiveMutex& aRecursiveMutex)
+ : mRecursiveMutex(&aRecursiveMutex) {
+ NS_ASSERTION(mRecursiveMutex, "null mutex");
+ mRecursiveMutex->Lock();
+ }
+
+ ~RecursiveMutexAutoLock(void) { mRecursiveMutex->Unlock(); }
+
+ private:
+ RecursiveMutexAutoLock() = delete;
+ RecursiveMutexAutoLock(const RecursiveMutexAutoLock&) = delete;
+ RecursiveMutexAutoLock& operator=(const RecursiveMutexAutoLock&) = delete;
+ static void* operator new(size_t) noexcept(true);
+
+ mozilla::RecursiveMutex* mRecursiveMutex;
+};
+
+class MOZ_RAII RecursiveMutexAutoUnlock {
+ public:
+ explicit RecursiveMutexAutoUnlock(RecursiveMutex& aRecursiveMutex)
+ : mRecursiveMutex(&aRecursiveMutex) {
+ NS_ASSERTION(mRecursiveMutex, "null mutex");
+ mRecursiveMutex->Unlock();
+ }
+
+ ~RecursiveMutexAutoUnlock(void) { mRecursiveMutex->Lock(); }
+
+ private:
+ RecursiveMutexAutoUnlock() = delete;
+ RecursiveMutexAutoUnlock(const RecursiveMutexAutoUnlock&) = delete;
+ RecursiveMutexAutoUnlock& operator=(const RecursiveMutexAutoUnlock&) = delete;
+ static void* operator new(size_t) noexcept(true);
+
+ mozilla::RecursiveMutex* mRecursiveMutex;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_RecursiveMutex_h
diff --git a/xpcom/threads/ReentrantMonitor.h b/xpcom/threads/ReentrantMonitor.h
new file mode 100644
index 0000000000..d1d1145d1b
--- /dev/null
+++ b/xpcom/threads/ReentrantMonitor.h
@@ -0,0 +1,242 @@
+/* -*- 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_ReentrantMonitor_h
+#define mozilla_ReentrantMonitor_h
+
+#include "prmon.h"
+
+#if defined(MOZILLA_INTERNAL_API) && !defined(DEBUG)
+# include "GeckoProfiler.h"
+#endif // defined( MOZILLA_INTERNAL_API) && !defined(DEBUG)
+
+#include "mozilla/BlockingResourceBase.h"
+#include "nsISupports.h"
+
+//
+// Provides:
+//
+// - ReentrantMonitor, a Java-like monitor
+// - ReentrantMonitorAutoEnter, an RAII class for ensuring that
+// ReentrantMonitors are properly entered and exited
+//
+// Using ReentrantMonitorAutoEnter is MUCH preferred to making bare calls to
+// ReentrantMonitor.Enter and Exit.
+//
+namespace mozilla {
+
+/**
+ * ReentrantMonitor
+ * Java-like monitor.
+ * When possible, use ReentrantMonitorAutoEnter to hold this monitor within a
+ * scope, instead of calling Enter/Exit directly.
+ **/
+class ReentrantMonitor : BlockingResourceBase {
+ public:
+ /**
+ * ReentrantMonitor
+ * @param aName A name which can reference this monitor
+ */
+ explicit ReentrantMonitor(const char* aName)
+ : BlockingResourceBase(aName, eReentrantMonitor)
+#ifdef DEBUG
+ ,
+ mEntryCount(0)
+#endif
+ {
+ MOZ_COUNT_CTOR(ReentrantMonitor);
+ mReentrantMonitor = PR_NewMonitor();
+ if (!mReentrantMonitor) {
+ MOZ_CRASH("Can't allocate mozilla::ReentrantMonitor");
+ }
+ }
+
+ /**
+ * ~ReentrantMonitor
+ **/
+ ~ReentrantMonitor() {
+ NS_ASSERTION(mReentrantMonitor,
+ "improperly constructed ReentrantMonitor or double free");
+ PR_DestroyMonitor(mReentrantMonitor);
+ mReentrantMonitor = 0;
+ MOZ_COUNT_DTOR(ReentrantMonitor);
+ }
+
+#ifndef DEBUG
+ /**
+ * Enter
+ * @see prmon.h
+ **/
+ void Enter() { PR_EnterMonitor(mReentrantMonitor); }
+
+ /**
+ * Exit
+ * @see prmon.h
+ **/
+ void Exit() { PR_ExitMonitor(mReentrantMonitor); }
+
+ /**
+ * Wait
+ * @see prmon.h
+ **/
+ nsresult Wait(PRIntervalTime aInterval = PR_INTERVAL_NO_TIMEOUT) {
+# ifdef MOZILLA_INTERNAL_API
+ AUTO_PROFILER_THREAD_SLEEP;
+# endif // MOZILLA_INTERNAL_API
+ return PR_Wait(mReentrantMonitor, aInterval) == PR_SUCCESS
+ ? NS_OK
+ : NS_ERROR_FAILURE;
+ }
+
+#else // ifndef DEBUG
+ void Enter();
+ void Exit();
+ nsresult Wait(PRIntervalTime aInterval = PR_INTERVAL_NO_TIMEOUT);
+
+#endif // ifndef DEBUG
+
+ /**
+ * Notify
+ * @see prmon.h
+ **/
+ nsresult Notify() {
+ return PR_Notify(mReentrantMonitor) == PR_SUCCESS ? NS_OK
+ : NS_ERROR_FAILURE;
+ }
+
+ /**
+ * NotifyAll
+ * @see prmon.h
+ **/
+ nsresult NotifyAll() {
+ return PR_NotifyAll(mReentrantMonitor) == PR_SUCCESS ? NS_OK
+ : NS_ERROR_FAILURE;
+ }
+
+#ifdef DEBUG
+ /**
+ * AssertCurrentThreadIn
+ * @see prmon.h
+ **/
+ void AssertCurrentThreadIn() {
+ PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mReentrantMonitor);
+ }
+
+ /**
+ * AssertNotCurrentThreadIn
+ * @see prmon.h
+ **/
+ void AssertNotCurrentThreadIn() {
+ // FIXME bug 476536
+ }
+
+#else
+ void AssertCurrentThreadIn() {}
+ void AssertNotCurrentThreadIn() {}
+
+#endif // ifdef DEBUG
+
+ private:
+ ReentrantMonitor();
+ ReentrantMonitor(const ReentrantMonitor&);
+ ReentrantMonitor& operator=(const ReentrantMonitor&);
+
+ PRMonitor* mReentrantMonitor;
+#ifdef DEBUG
+ int32_t mEntryCount;
+#endif
+};
+
+/**
+ * ReentrantMonitorAutoEnter
+ * Enters the ReentrantMonitor when it enters scope, and exits it when
+ * it leaves scope.
+ *
+ * MUCH PREFERRED to bare calls to ReentrantMonitor.Enter and Exit.
+ */
+class MOZ_STACK_CLASS ReentrantMonitorAutoEnter {
+ public:
+ /**
+ * Constructor
+ * The constructor aquires the given lock. The destructor
+ * releases the lock.
+ *
+ * @param aReentrantMonitor A valid mozilla::ReentrantMonitor*.
+ **/
+ explicit ReentrantMonitorAutoEnter(
+ mozilla::ReentrantMonitor& aReentrantMonitor)
+ : mReentrantMonitor(&aReentrantMonitor) {
+ NS_ASSERTION(mReentrantMonitor, "null monitor");
+ mReentrantMonitor->Enter();
+ }
+
+ ~ReentrantMonitorAutoEnter(void) { mReentrantMonitor->Exit(); }
+
+ nsresult Wait(PRIntervalTime aInterval = PR_INTERVAL_NO_TIMEOUT) {
+ return mReentrantMonitor->Wait(aInterval);
+ }
+
+ nsresult Notify() { return mReentrantMonitor->Notify(); }
+ nsresult NotifyAll() { return mReentrantMonitor->NotifyAll(); }
+
+ private:
+ ReentrantMonitorAutoEnter();
+ ReentrantMonitorAutoEnter(const ReentrantMonitorAutoEnter&);
+ ReentrantMonitorAutoEnter& operator=(const ReentrantMonitorAutoEnter&);
+ static void* operator new(size_t) noexcept(true);
+
+ friend class ReentrantMonitorAutoExit;
+
+ mozilla::ReentrantMonitor* mReentrantMonitor;
+};
+
+/**
+ * ReentrantMonitorAutoExit
+ * Exit the ReentrantMonitor when it enters scope, and enters it when it leaves
+ * scope.
+ *
+ * MUCH PREFERRED to bare calls to ReentrantMonitor.Exit and Enter.
+ */
+class MOZ_STACK_CLASS ReentrantMonitorAutoExit {
+ public:
+ /**
+ * Constructor
+ * The constructor releases the given lock. The destructor
+ * acquires the lock. The lock must be held before constructing
+ * this object!
+ *
+ * @param aReentrantMonitor A valid mozilla::ReentrantMonitor*. It
+ * must be already locked.
+ **/
+ explicit ReentrantMonitorAutoExit(ReentrantMonitor& aReentrantMonitor)
+ : mReentrantMonitor(&aReentrantMonitor) {
+ NS_ASSERTION(mReentrantMonitor, "null monitor");
+ mReentrantMonitor->AssertCurrentThreadIn();
+ mReentrantMonitor->Exit();
+ }
+
+ explicit ReentrantMonitorAutoExit(
+ ReentrantMonitorAutoEnter& aReentrantMonitorAutoEnter)
+ : mReentrantMonitor(aReentrantMonitorAutoEnter.mReentrantMonitor) {
+ NS_ASSERTION(mReentrantMonitor, "null monitor");
+ mReentrantMonitor->AssertCurrentThreadIn();
+ mReentrantMonitor->Exit();
+ }
+
+ ~ReentrantMonitorAutoExit(void) { mReentrantMonitor->Enter(); }
+
+ private:
+ ReentrantMonitorAutoExit();
+ ReentrantMonitorAutoExit(const ReentrantMonitorAutoExit&);
+ ReentrantMonitorAutoExit& operator=(const ReentrantMonitorAutoExit&);
+ static void* operator new(size_t) noexcept(true);
+
+ ReentrantMonitor* mReentrantMonitor;
+};
+
+} // namespace mozilla
+
+#endif // ifndef mozilla_ReentrantMonitor_h
diff --git a/xpcom/threads/SchedulerGroup.cpp b/xpcom/threads/SchedulerGroup.cpp
new file mode 100644
index 0000000000..e17066340c
--- /dev/null
+++ b/xpcom/threads/SchedulerGroup.cpp
@@ -0,0 +1,157 @@
+/* -*- 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/SchedulerGroup.h"
+
+#include <utility>
+
+#include "jsfriendapi.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "nsINamed.h"
+#include "nsQueryObject.h"
+#include "nsThreadUtils.h"
+
+using namespace mozilla;
+
+namespace {
+
+static Atomic<uint64_t> gEarliestUnprocessedVsync(0);
+
+} // namespace
+
+/* static */
+nsresult SchedulerGroup::UnlabeledDispatch(
+ TaskCategory aCategory, already_AddRefed<nsIRunnable>&& aRunnable) {
+ if (NS_IsMainThread()) {
+ return NS_DispatchToCurrentThread(std::move(aRunnable));
+ } else {
+ return NS_DispatchToMainThread(std::move(aRunnable));
+ }
+}
+
+/* static */
+void SchedulerGroup::MarkVsyncReceived() {
+ if (gEarliestUnprocessedVsync) {
+ // If we've seen a vsync already, but haven't handled it, keep the
+ // older one.
+ return;
+ }
+
+ MOZ_ASSERT(!NS_IsMainThread());
+ bool inconsistent = false;
+ TimeStamp creation = TimeStamp::ProcessCreation(&inconsistent);
+ if (inconsistent) {
+ return;
+ }
+
+ gEarliestUnprocessedVsync = (TimeStamp::Now() - creation).ToMicroseconds();
+}
+
+/* static */
+void SchedulerGroup::MarkVsyncRan() { gEarliestUnprocessedVsync = 0; }
+
+SchedulerGroup::SchedulerGroup() : mIsRunning(false) {}
+
+/* static */
+nsresult SchedulerGroup::DispatchWithDocGroup(
+ TaskCategory aCategory, already_AddRefed<nsIRunnable>&& aRunnable,
+ dom::DocGroup* aDocGroup) {
+ return LabeledDispatch(aCategory, std::move(aRunnable), aDocGroup);
+}
+
+/* static */
+nsresult SchedulerGroup::Dispatch(TaskCategory aCategory,
+ already_AddRefed<nsIRunnable>&& aRunnable) {
+ return LabeledDispatch(aCategory, std::move(aRunnable), nullptr);
+}
+
+/* static */
+nsresult SchedulerGroup::LabeledDispatch(
+ TaskCategory aCategory, already_AddRefed<nsIRunnable>&& aRunnable,
+ dom::DocGroup* aDocGroup) {
+ nsCOMPtr<nsIRunnable> runnable(aRunnable);
+ if (XRE_IsContentProcess()) {
+ RefPtr<Runnable> internalRunnable =
+ new Runnable(runnable.forget(), aDocGroup);
+ return InternalUnlabeledDispatch(aCategory, internalRunnable.forget());
+ }
+ return UnlabeledDispatch(aCategory, runnable.forget());
+}
+
+/*static*/
+nsresult SchedulerGroup::InternalUnlabeledDispatch(
+ TaskCategory aCategory, already_AddRefed<Runnable>&& aRunnable) {
+ if (NS_IsMainThread()) {
+ // NS_DispatchToCurrentThread will not leak the passed in runnable
+ // when it fails, so we don't need to do anything special.
+ return NS_DispatchToCurrentThread(std::move(aRunnable));
+ }
+
+ RefPtr<Runnable> runnable(aRunnable);
+ nsresult rv = NS_DispatchToMainThread(do_AddRef(runnable));
+ if (NS_FAILED(rv)) {
+ // Dispatch failed. This is a situation where we would have used
+ // NS_DispatchToMainThread rather than calling into the SchedulerGroup
+ // machinery, and the caller would be expecting to leak the nsIRunnable
+ // originally passed in. But because we've had to wrap things up
+ // internally, we were going to leak the nsIRunnable *and* our Runnable
+ // wrapper. But there's no reason that we have to leak our Runnable
+ // wrapper; we can just leak the wrapped nsIRunnable, and let the caller
+ // take care of unleaking it if they need to.
+ Unused << runnable->mRunnable.forget().take();
+ nsrefcnt refcnt = runnable.get()->Release();
+ MOZ_RELEASE_ASSERT(refcnt == 1, "still holding an unexpected reference!");
+ }
+
+ return rv;
+}
+
+SchedulerGroup::Runnable::Runnable(already_AddRefed<nsIRunnable>&& aRunnable,
+ dom::DocGroup* aDocGroup)
+ : mozilla::Runnable("SchedulerGroup::Runnable"),
+ mRunnable(std::move(aRunnable)),
+ mDocGroup(aDocGroup) {}
+
+dom::DocGroup* SchedulerGroup::Runnable::DocGroup() const { return mDocGroup; }
+
+#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+NS_IMETHODIMP
+SchedulerGroup::Runnable::GetName(nsACString& aName) {
+ // Try to get a name from the underlying runnable.
+ nsCOMPtr<nsINamed> named = do_QueryInterface(mRunnable);
+ if (named) {
+ named->GetName(aName);
+ }
+ if (aName.IsEmpty()) {
+ aName.AssignLiteral("anonymous");
+ }
+
+ return NS_OK;
+}
+#endif
+
+NS_IMETHODIMP
+SchedulerGroup::Runnable::Run() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ // The runnable's destructor can have side effects, so try to execute it in
+ // the scope of the SchedulerGroup.
+ nsCOMPtr<nsIRunnable> runnable(std::move(mRunnable));
+ return runnable->Run();
+}
+
+NS_IMETHODIMP
+SchedulerGroup::Runnable::GetPriority(uint32_t* aPriority) {
+ *aPriority = nsIRunnablePriority::PRIORITY_NORMAL;
+ nsCOMPtr<nsIRunnablePriority> runnablePrio = do_QueryInterface(mRunnable);
+ return runnablePrio ? runnablePrio->GetPriority(aPriority) : NS_OK;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(SchedulerGroup::Runnable, mozilla::Runnable,
+ nsIRunnablePriority, SchedulerGroup::Runnable)
diff --git a/xpcom/threads/SchedulerGroup.h b/xpcom/threads/SchedulerGroup.h
new file mode 100644
index 0000000000..7221ce3bb7
--- /dev/null
+++ b/xpcom/threads/SchedulerGroup.h
@@ -0,0 +1,118 @@
+/* -*- 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_SchedulerGroup_h
+#define mozilla_SchedulerGroup_h
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/TaskCategory.h"
+#include "nsCOMPtr.h"
+#include "nsID.h"
+#include "nsIRunnable.h"
+#include "nsISupports.h"
+#include "nsStringFwd.h"
+#include "nsThreadUtils.h"
+#include "nscore.h"
+
+class nsIEventTarget;
+class nsIRunnable;
+class nsISerialEventTarget;
+
+namespace mozilla {
+class AbstractThread;
+namespace dom {
+class DocGroup;
+} // namespace dom
+
+#define NS_SCHEDULERGROUPRUNNABLE_IID \
+ { \
+ 0xd31b7420, 0x872b, 0x4cfb, { \
+ 0xa9, 0xc6, 0xae, 0x4c, 0x0f, 0x06, 0x36, 0x74 \
+ } \
+ }
+
+// The "main thread" in Gecko will soon be a set of cooperatively scheduled
+// "fibers". Global state in Gecko will be partitioned into a series of "groups"
+// (with roughly one group per tab). Runnables will be annotated with the set of
+// groups that they touch. Two runnables may run concurrently on different
+// fibers as long as they touch different groups.
+//
+// A SchedulerGroup is an abstract class to represent a "group". Essentially the
+// only functionality offered by a SchedulerGroup is the ability to dispatch
+// runnables to the group. DocGroup, and SystemGroup are the concrete
+// implementations of SchedulerGroup.
+class SchedulerGroup {
+ public:
+ SchedulerGroup();
+
+ NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
+
+ class Runnable final : public mozilla::Runnable, public nsIRunnablePriority {
+ public:
+ Runnable(already_AddRefed<nsIRunnable>&& aRunnable,
+ dom::DocGroup* aDocGroup);
+
+ dom::DocGroup* DocGroup() const;
+
+#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+ NS_IMETHOD GetName(nsACString& aName) override;
+#endif
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSIRUNNABLEPRIORITY
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_SCHEDULERGROUPRUNNABLE_IID);
+
+ private:
+ friend class SchedulerGroup;
+
+ ~Runnable() = default;
+
+ nsCOMPtr<nsIRunnable> mRunnable;
+ RefPtr<dom::DocGroup> mDocGroup;
+ };
+ friend class Runnable;
+
+ static nsresult Dispatch(TaskCategory aCategory,
+ already_AddRefed<nsIRunnable>&& aRunnable);
+
+ static nsresult UnlabeledDispatch(TaskCategory aCategory,
+ already_AddRefed<nsIRunnable>&& aRunnable);
+
+ static void MarkVsyncReceived();
+
+ static void MarkVsyncRan();
+
+ static nsresult DispatchWithDocGroup(
+ TaskCategory aCategory, already_AddRefed<nsIRunnable>&& aRunnable,
+ dom::DocGroup* aDocGroup);
+
+ protected:
+ static nsresult InternalUnlabeledDispatch(
+ TaskCategory aCategory, already_AddRefed<Runnable>&& aRunnable);
+
+ static nsresult LabeledDispatch(TaskCategory aCategory,
+ already_AddRefed<nsIRunnable>&& aRunnable,
+ dom::DocGroup* aDocGroup);
+
+ // Shuts down this dispatcher. If aXPCOMShutdown is true, invalidates this
+ // dispatcher.
+ void Shutdown(bool aXPCOMShutdown);
+
+ bool mIsRunning;
+
+ // Number of events that are currently enqueued for this SchedulerGroup
+ // (across all queues).
+ size_t mEventCount = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(SchedulerGroup::Runnable,
+ NS_SCHEDULERGROUPRUNNABLE_IID);
+
+} // namespace mozilla
+
+#endif // mozilla_SchedulerGroup_h
diff --git a/xpcom/threads/SharedThreadPool.cpp b/xpcom/threads/SharedThreadPool.cpp
new file mode 100644
index 0000000000..fede9a2f39
--- /dev/null
+++ b/xpcom/threads/SharedThreadPool.cpp
@@ -0,0 +1,232 @@
+/* -*- 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/SharedThreadPool.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "mozilla/Services.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/StaticPtr.h"
+#include "nsDataHashtable.h"
+#include "nsXPCOMCIDInternal.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIThreadManager.h"
+#include "nsThreadPool.h"
+#ifdef XP_WIN
+# include "ThreadPoolCOMListener.h"
+#endif
+
+namespace mozilla {
+
+// Created and destroyed on the main thread.
+static StaticAutoPtr<ReentrantMonitor> sMonitor;
+
+// Hashtable, maps thread pool name to SharedThreadPool instance.
+// Modified only on the main thread.
+static StaticAutoPtr<nsDataHashtable<nsCStringHashKey, SharedThreadPool*>>
+ sPools;
+
+static already_AddRefed<nsIThreadPool> CreateThreadPool(const nsCString& aName);
+
+class SharedThreadPoolShutdownObserver : public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ protected:
+ virtual ~SharedThreadPoolShutdownObserver() = default;
+};
+
+NS_IMPL_ISUPPORTS(SharedThreadPoolShutdownObserver, nsIObserver, nsISupports)
+
+NS_IMETHODIMP
+SharedThreadPoolShutdownObserver::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData) {
+ MOZ_RELEASE_ASSERT(!strcmp(aTopic, "xpcom-shutdown-threads"));
+#ifdef EARLY_BETA_OR_EARLIER
+ {
+ ReentrantMonitorAutoEnter mon(*sMonitor);
+ if (!sPools->Iter().Done()) {
+ nsAutoCString str;
+ for (auto i = sPools->Iter(); !i.Done(); i.Next()) {
+ str.AppendPrintf("\"%s\" ", nsAutoCString(i.Key()).get());
+ }
+ printf_stderr(
+ "SharedThreadPool in xpcom-shutdown-threads. Waiting for "
+ "pools %s\n",
+ str.get());
+ }
+ }
+#endif
+ SharedThreadPool::SpinUntilEmpty();
+ sMonitor = nullptr;
+ sPools = nullptr;
+ return NS_OK;
+}
+
+void SharedThreadPool::InitStatics() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!sMonitor && !sPools);
+ sMonitor = new ReentrantMonitor("SharedThreadPool");
+ sPools = new nsDataHashtable<nsCStringHashKey, SharedThreadPool*>();
+ nsCOMPtr<nsIObserverService> obsService =
+ mozilla::services::GetObserverService();
+ nsCOMPtr<nsIObserver> obs = new SharedThreadPoolShutdownObserver();
+ obsService->AddObserver(obs, "xpcom-shutdown-threads", false);
+}
+
+/* static */
+bool SharedThreadPool::IsEmpty() {
+ ReentrantMonitorAutoEnter mon(*sMonitor);
+ return !sPools->Count();
+}
+
+/* static */
+void SharedThreadPool::SpinUntilEmpty() {
+ MOZ_ASSERT(NS_IsMainThread());
+ SpinEventLoopUntil([]() -> bool {
+ sMonitor->AssertNotCurrentThreadIn();
+ return IsEmpty();
+ });
+}
+
+already_AddRefed<SharedThreadPool> SharedThreadPool::Get(
+ const nsCString& aName, uint32_t aThreadLimit) {
+ MOZ_ASSERT(sMonitor && sPools);
+ ReentrantMonitorAutoEnter mon(*sMonitor);
+ RefPtr<SharedThreadPool> pool;
+ nsresult rv;
+
+ if (auto entry = sPools->LookupForAdd(aName)) {
+ pool = entry.Data();
+ if (NS_FAILED(pool->EnsureThreadLimitIsAtLeast(aThreadLimit))) {
+ NS_WARNING("Failed to set limits on thread pool");
+ }
+ } else {
+ nsCOMPtr<nsIThreadPool> threadPool(CreateThreadPool(aName));
+ if (NS_WARN_IF(!threadPool)) {
+ sPools->Remove(aName); // XXX entry.Remove()
+ return nullptr;
+ }
+ pool = new SharedThreadPool(aName, threadPool);
+
+ // Set the thread and idle limits. Note that we don't rely on the
+ // EnsureThreadLimitIsAtLeast() call below, as the default thread limit
+ // is 4, and if aThreadLimit is less than 4 we'll end up with a pool
+ // with 4 threads rather than what we expected; so we'll have unexpected
+ // behaviour.
+ rv = pool->SetThreadLimit(aThreadLimit);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ sPools->Remove(aName); // XXX entry.Remove()
+ return nullptr;
+ }
+
+ rv = pool->SetIdleThreadLimit(aThreadLimit);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ sPools->Remove(aName); // XXX entry.Remove()
+ return nullptr;
+ }
+
+ entry.OrInsert([pool]() { return pool.get(); });
+ }
+
+ return pool.forget();
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType) SharedThreadPool::AddRef(void) {
+ MOZ_ASSERT(sMonitor);
+ ReentrantMonitorAutoEnter mon(*sMonitor);
+ MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt");
+ nsrefcnt count = ++mRefCnt;
+ NS_LOG_ADDREF(this, count, "SharedThreadPool", sizeof(*this));
+ return count;
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType) SharedThreadPool::Release(void) {
+ MOZ_ASSERT(sMonitor);
+ ReentrantMonitorAutoEnter mon(*sMonitor);
+ nsrefcnt count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "SharedThreadPool");
+ if (count) {
+ return count;
+ }
+
+ // Remove SharedThreadPool from table of pools.
+ sPools->Remove(mName);
+ MOZ_ASSERT(!sPools->Get(mName));
+
+ // Dispatch an event to the main thread to call Shutdown() on
+ // the nsIThreadPool. The Runnable here will add a refcount to the pool,
+ // and when the Runnable releases the nsIThreadPool it will be deleted.
+ NS_DispatchToMainThread(NewRunnableMethod("nsIThreadPool::Shutdown", mPool,
+ &nsIThreadPool::Shutdown));
+
+ // Stabilize refcount, so that if something in the dtor QIs, it won't explode.
+ mRefCnt = 1;
+ delete this;
+ return 0;
+}
+
+NS_IMPL_QUERY_INTERFACE(SharedThreadPool, nsIThreadPool, nsIEventTarget)
+
+SharedThreadPool::SharedThreadPool(const nsCString& aName, nsIThreadPool* aPool)
+ : mName(aName), mPool(aPool), mRefCnt(0) {
+ mEventTarget = aPool;
+}
+
+SharedThreadPool::~SharedThreadPool() = default;
+
+nsresult SharedThreadPool::EnsureThreadLimitIsAtLeast(uint32_t aLimit) {
+ // We limit the number of threads that we use. Note that we
+ // set the thread limit to the same as the idle limit so that we're not
+ // constantly creating and destroying threads (see Bug 881954). When the
+ // thread pool threads shutdown they dispatch an event to the main thread
+ // to call nsIThread::Shutdown(), and if we're very busy that can take a
+ // while to run, and we end up with dozens of extra threads. Note that
+ // threads that are idle for 60 seconds are shutdown naturally.
+ uint32_t existingLimit = 0;
+ nsresult rv;
+
+ rv = mPool->GetThreadLimit(&existingLimit);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (aLimit > existingLimit) {
+ rv = mPool->SetThreadLimit(aLimit);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = mPool->GetIdleThreadLimit(&existingLimit);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (aLimit > existingLimit) {
+ rv = mPool->SetIdleThreadLimit(aLimit);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+static already_AddRefed<nsIThreadPool> CreateThreadPool(
+ const nsCString& aName) {
+ nsCOMPtr<nsIThreadPool> pool = new nsThreadPool();
+
+ nsresult rv = pool->SetName(aName);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ rv = pool->SetThreadStackSize(nsIThreadManager::kThreadPoolStackSize);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+#ifdef XP_WIN
+ // Ensure MSCOM is initialized on the thread pools threads.
+ nsCOMPtr<nsIThreadPoolListener> listener = new MSCOMInitThreadPoolListener();
+ rv = pool->SetListener(listener);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+#endif
+
+ return pool.forget();
+}
+
+} // namespace mozilla
diff --git a/xpcom/threads/SharedThreadPool.h b/xpcom/threads/SharedThreadPool.h
new file mode 100644
index 0000000000..936cac5cd8
--- /dev/null
+++ b/xpcom/threads/SharedThreadPool.h
@@ -0,0 +1,128 @@
+/* -*- 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 SharedThreadPool_h_
+#define SharedThreadPool_h_
+
+#include <utility>
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/RefCountType.h"
+#include "nsCOMPtr.h"
+#include "nsID.h"
+#include "nsIEventTarget.h"
+#include "nsIThreadPool.h"
+#include "nsString.h"
+#include "nscore.h"
+
+class nsIRunnable;
+
+namespace mozilla {
+
+// Wrapper that makes an nsIThreadPool a singleton, and provides a
+// consistent threadsafe interface to get instances. Callers simply get a
+// SharedThreadPool by the name of its nsIThreadPool. All get requests of
+// the same name get the same SharedThreadPool. Users must store a reference
+// to the pool, and when the last reference to a SharedThreadPool is dropped
+// the pool is shutdown and deleted. Users aren't required to manually
+// shutdown the pool, and can release references on any thread. This can make
+// it significantly easier to use thread pools, because the caller doesn't need
+// to worry about joining and tearing it down.
+//
+// On Windows all threads in the pool have MSCOM initialized with
+// COINIT_MULTITHREADED. Note that not all users of MSCOM use this mode see [1],
+// and mixing MSCOM objects between the two is terrible for performance, and can
+// cause some functions to fail. So be careful when using Win32 APIs on a
+// SharedThreadPool, and avoid sharing objects if at all possible.
+//
+// [1]
+// https://searchfox.org/mozilla-central/search?q=coinitialize&redirect=false
+class SharedThreadPool : public nsIThreadPool {
+ public:
+ // Gets (possibly creating) the shared thread pool singleton instance with
+ // thread pool named aName.
+ static already_AddRefed<SharedThreadPool> Get(const nsCString& aName,
+ uint32_t aThreadLimit = 4);
+
+ // We implement custom threadsafe AddRef/Release pair, that destroys the
+ // the shared pool singleton when the refcount drops to 0. The addref/release
+ // are implemented using locking, so it's not recommended that you use them
+ // in a tight loop.
+ NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
+ NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override;
+ NS_IMETHOD_(MozExternalRefCountType) Release(void) override;
+
+ // Forward behaviour to wrapped thread pool implementation.
+ NS_FORWARD_SAFE_NSITHREADPOOL(mPool);
+
+ // Call this when dispatching from an event on the same
+ // threadpool that is about to complete. We should not create a new thread
+ // in that case since a thread is about to become idle.
+ nsresult DispatchFromEndOfTaskInThisPool(nsIRunnable* event) {
+ return Dispatch(event, NS_DISPATCH_AT_END);
+ }
+
+ NS_IMETHOD DispatchFromScript(nsIRunnable* event, uint32_t flags) override {
+ return Dispatch(event, flags);
+ }
+
+ NS_IMETHOD Dispatch(already_AddRefed<nsIRunnable> event,
+ uint32_t flags = NS_DISPATCH_NORMAL) override {
+ return !mEventTarget ? NS_ERROR_NULL_POINTER
+ : mEventTarget->Dispatch(std::move(event), flags);
+ }
+
+ NS_IMETHOD DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ using nsIEventTarget::Dispatch;
+
+ NS_IMETHOD IsOnCurrentThread(bool* _retval) override {
+ return !mEventTarget ? NS_ERROR_NULL_POINTER
+ : mEventTarget->IsOnCurrentThread(_retval);
+ }
+
+ NS_IMETHOD_(bool) IsOnCurrentThreadInfallible() override {
+ return mEventTarget && mEventTarget->IsOnCurrentThread();
+ }
+
+ // Creates necessary statics. Called once at startup.
+ static void InitStatics();
+
+ // Spins the event loop until all thread pools are shutdown.
+ // *Must* be called on the main thread.
+ static void SpinUntilEmpty();
+
+ private:
+ // Returns whether there are no pools in existence at the moment.
+ static bool IsEmpty();
+
+ // Creates a singleton SharedThreadPool wrapper around aPool.
+ // aName is the name of the aPool, and is used to lookup the
+ // SharedThreadPool in the hash table of all created pools.
+ SharedThreadPool(const nsCString& aName, nsIThreadPool* aPool);
+ virtual ~SharedThreadPool();
+
+ nsresult EnsureThreadLimitIsAtLeast(uint32_t aThreadLimit);
+
+ // Name of mPool.
+ const nsCString mName;
+
+ // Thread pool being wrapped.
+ nsCOMPtr<nsIThreadPool> mPool;
+
+ // Refcount. We implement custom ref counting so that the thread pool is
+ // shutdown in a threadsafe manner and singletonness is preserved.
+ nsrefcnt mRefCnt;
+
+ // mPool QI'd to nsIEventTarget. We cache this, so that we can use
+ // NS_FORWARD_SAFE_NSIEVENTTARGET above.
+ nsCOMPtr<nsIEventTarget> mEventTarget;
+};
+
+} // namespace mozilla
+
+#endif // SharedThreadPool_h_
diff --git a/xpcom/threads/SpinEventLoopUntil.h b/xpcom/threads/SpinEventLoopUntil.h
new file mode 100644
index 0000000000..398c7084a2
--- /dev/null
+++ b/xpcom/threads/SpinEventLoopUntil.h
@@ -0,0 +1,108 @@
+/* -*- 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 xpcom_threads_SpinEventLoopUntil_h__
+#define xpcom_threads_SpinEventLoopUntil_h__
+
+#include "MainThreadUtils.h"
+#include "mozilla/Maybe.h"
+#include "nsThreadUtils.h"
+#include "xpcpublic.h"
+
+class nsIThread;
+
+// A wrapper for nested event loops.
+//
+// This function is intended to make code more obvious (do you remember
+// what NS_ProcessNextEvent(nullptr, true) means?) and slightly more
+// efficient, as people often pass nullptr or NS_GetCurrentThread to
+// NS_ProcessNextEvent, which results in needless querying of the current
+// thread every time through the loop.
+//
+// You should use this function in preference to NS_ProcessNextEvent inside
+// a loop unless one of the following is true:
+//
+// * You need to pass `false` to NS_ProcessNextEvent; or
+// * You need to do unusual things around the call to NS_ProcessNextEvent,
+// such as unlocking mutexes that you are holding.
+//
+// If you *do* need to call NS_ProcessNextEvent manually, please do call
+// NS_GetCurrentThread() outside of your loop and pass the returned pointer
+// into NS_ProcessNextEvent for a tiny efficiency win.
+namespace mozilla {
+
+// You should normally not need to deal with this template parameter. If
+// you enjoy esoteric event loop details, read on.
+//
+// If you specify that NS_ProcessNextEvent wait for an event, it is possible
+// for NS_ProcessNextEvent to return false, i.e. to indicate that an event
+// was not processed. This can only happen when the thread has been shut
+// down by another thread, but is still attempting to process events outside
+// of a nested event loop.
+//
+// This behavior is admittedly strange. The scenario it deals with is the
+// following:
+//
+// * The current thread has been shut down by some owner thread.
+// * The current thread is spinning an event loop waiting for some condition
+// to become true.
+// * Said condition is actually being fulfilled by another thread, so there
+// are timing issues in play.
+//
+// Thus, there is a small window where the current thread's event loop
+// spinning can check the condition, find it false, and call
+// NS_ProcessNextEvent to wait for another event. But we don't actually
+// want it to wait indefinitely, because there might not be any other events
+// in the event loop, and the current thread can't accept dispatched events
+// because it's being shut down. Thus, actually blocking would hang the
+// thread, which is bad. The solution, then, is to detect such a scenario
+// and not actually block inside NS_ProcessNextEvent.
+//
+// But this is a problem, because we want to return the status of
+// NS_ProcessNextEvent to the caller of SpinEventLoopUntil if possible. In
+// the above scenario, however, we'd stop spinning prematurely and cause
+// all sorts of havoc. We therefore have this template parameter to
+// control whether errors are ignored or passed out to the caller of
+// SpinEventLoopUntil. The latter is the default; if you find yourself
+// wanting to use the former, you should think long and hard before doing
+// so, and write a comment like this defending your choice.
+
+enum class ProcessFailureBehavior {
+ IgnoreAndContinue,
+ ReportToCaller,
+};
+
+template <
+ ProcessFailureBehavior Behavior = ProcessFailureBehavior::ReportToCaller,
+ typename Pred>
+bool SpinEventLoopUntil(Pred&& aPredicate, nsIThread* aThread = nullptr) {
+ nsIThread* thread = aThread ? aThread : NS_GetCurrentThread();
+
+ // From a latency perspective, spinning the event loop is like leaving script
+ // and returning to the event loop. Tell the watchdog we stopped running
+ // script (until we return).
+ mozilla::Maybe<xpc::AutoScriptActivity> asa;
+ if (NS_IsMainThread()) {
+ asa.emplace(false);
+ }
+
+ while (!aPredicate()) {
+ bool didSomething = NS_ProcessNextEvent(thread, true);
+
+ if (Behavior == ProcessFailureBehavior::IgnoreAndContinue) {
+ // Don't care what happened, continue on.
+ continue;
+ } else if (!didSomething) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // namespace mozilla
+
+#endif // xpcom_threads_SpinEventLoopUntil_h__
diff --git a/xpcom/threads/StateMirroring.h b/xpcom/threads/StateMirroring.h
new file mode 100644
index 0000000000..c233116962
--- /dev/null
+++ b/xpcom/threads/StateMirroring.h
@@ -0,0 +1,393 @@
+/* -*- 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/. */
+
+#if !defined(StateMirroring_h_)
+# define StateMirroring_h_
+
+# include <cstddef>
+# include "mozilla/AbstractThread.h"
+# include "mozilla/AlreadyAddRefed.h"
+# include "mozilla/Assertions.h"
+# include "mozilla/Logging.h"
+# include "mozilla/Maybe.h"
+# include "mozilla/RefPtr.h"
+# include "mozilla/StateWatching.h"
+# include "nsCOMPtr.h"
+# include "nsIRunnable.h"
+# include "nsISupports.h"
+# include "nsTArray.h"
+# include "nsThreadUtils.h"
+
+/*
+ * The state-mirroring machinery allows pieces of interesting state to be
+ * observed on multiple thread without locking. The basic strategy is to track
+ * changes in a canonical value and post updates to other threads that hold
+ * mirrors for that value.
+ *
+ * One problem with the naive implementation of such a system is that some
+ * pieces of state need to be updated atomically, and certain other operations
+ * need to wait for these atomic updates to complete before executing. The
+ * state-mirroring machinery solves this problem by requiring that its owner
+ * thread uses tail dispatch, and posting state update events (which should
+ * always be run first by TaskDispatcher implementations) to that tail
+ * dispatcher. This ensures that state changes are always atomic from the
+ * perspective of observing threads.
+ *
+ * Given that state-mirroring is an automatic background process, we try to
+ * avoid burdening the caller with worrying too much about teardown. To that
+ * end, we don't assert dispatch success for any of the notifications, and
+ * assume that any canonical or mirror owned by a thread for whom dispatch fails
+ * will soon be disconnected by its holder anyway.
+ *
+ * Given that semantics may change and comments tend to go out of date, we
+ * deliberately don't provide usage examples here. Grep around to find them.
+ */
+
+namespace mozilla {
+
+// Mirror<T> and Canonical<T> inherit WatchTarget, so we piggy-back on the
+// logging that WatchTarget already does. Given that, it makes sense to share
+// the same log module.
+# define MIRROR_LOG(x, ...) \
+ MOZ_ASSERT(gStateWatchingLog); \
+ MOZ_LOG(gStateWatchingLog, LogLevel::Debug, (x, ##__VA_ARGS__))
+
+template <typename T>
+class AbstractMirror;
+
+/*
+ * AbstractCanonical is a superclass from which all Canonical values must
+ * inherit. It serves as the interface of operations which may be performed (via
+ * asynchronous dispatch) by other threads, in particular by the corresponding
+ * Mirror value.
+ */
+template <typename T>
+class AbstractCanonical {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractCanonical)
+ AbstractCanonical(AbstractThread* aThread) : mOwnerThread(aThread) {}
+ virtual void AddMirror(AbstractMirror<T>* aMirror) = 0;
+ virtual void RemoveMirror(AbstractMirror<T>* aMirror) = 0;
+
+ AbstractThread* OwnerThread() const { return mOwnerThread; }
+
+ protected:
+ virtual ~AbstractCanonical() {}
+ RefPtr<AbstractThread> mOwnerThread;
+};
+
+/*
+ * AbstractMirror is a superclass from which all Mirror values must
+ * inherit. It serves as the interface of operations which may be performed (via
+ * asynchronous dispatch) by other threads, in particular by the corresponding
+ * Canonical value.
+ */
+template <typename T>
+class AbstractMirror {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractMirror)
+ AbstractMirror(AbstractThread* aThread) : mOwnerThread(aThread) {}
+ virtual void UpdateValue(const T& aNewValue) = 0;
+ virtual void NotifyDisconnected() = 0;
+
+ AbstractThread* OwnerThread() const { return mOwnerThread; }
+
+ protected:
+ virtual ~AbstractMirror() {}
+ RefPtr<AbstractThread> mOwnerThread;
+};
+
+/*
+ * Canonical<T> is a wrapper class that allows a given value to be mirrored by
+ * other threads. It maintains a list of active mirrors, and queues updates for
+ * them when the internal value changes. When changing the value, the caller
+ * needs to pass a TaskDispatcher object, which fires the updates at the
+ * appropriate time. Canonical<T> is also a WatchTarget, and may be set up to
+ * trigger other routines (on the same thread) when the canonical value changes.
+ *
+ * Canonical<T> is intended to be used as a member variable, so it doesn't
+ * actually inherit AbstractCanonical<T> (a refcounted type). Rather, it
+ * contains an inner class called |Impl| that implements most of the interesting
+ * logic.
+ */
+template <typename T>
+class Canonical {
+ public:
+ Canonical(AbstractThread* aThread, const T& aInitialValue,
+ const char* aName) {
+ mImpl = new Impl(aThread, aInitialValue, aName);
+ }
+
+ ~Canonical() {}
+
+ private:
+ class Impl : public AbstractCanonical<T>, public WatchTarget {
+ public:
+ using AbstractCanonical<T>::OwnerThread;
+
+ Impl(AbstractThread* aThread, const T& aInitialValue, const char* aName)
+ : AbstractCanonical<T>(aThread),
+ WatchTarget(aName),
+ mValue(aInitialValue) {
+ MIRROR_LOG("%s [%p] initialized", mName, this);
+ MOZ_ASSERT(aThread->SupportsTailDispatch(),
+ "Can't get coherency without tail dispatch");
+ }
+
+ void AddMirror(AbstractMirror<T>* aMirror) override {
+ MIRROR_LOG("%s [%p] adding mirror %p", mName, this, aMirror);
+ MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
+ MOZ_ASSERT(!mMirrors.Contains(aMirror));
+ mMirrors.AppendElement(aMirror);
+ aMirror->OwnerThread()->DispatchStateChange(MakeNotifier(aMirror));
+ }
+
+ void RemoveMirror(AbstractMirror<T>* aMirror) override {
+ MIRROR_LOG("%s [%p] removing mirror %p", mName, this, aMirror);
+ MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
+ MOZ_ASSERT(mMirrors.Contains(aMirror));
+ mMirrors.RemoveElement(aMirror);
+ }
+
+ void DisconnectAll() {
+ MIRROR_LOG("%s [%p] Disconnecting all mirrors", mName, this);
+ for (size_t i = 0; i < mMirrors.Length(); ++i) {
+ mMirrors[i]->OwnerThread()->Dispatch(
+ NewRunnableMethod("AbstractMirror::NotifyDisconnected", mMirrors[i],
+ &AbstractMirror<T>::NotifyDisconnected));
+ }
+ mMirrors.Clear();
+ }
+
+ operator const T&() {
+ MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
+ return mValue;
+ }
+
+ void Set(const T& aNewValue) {
+ MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
+
+ if (aNewValue == mValue) {
+ return;
+ }
+
+ // Notify same-thread watchers. The state watching machinery will make
+ // sure that notifications run at the right time.
+ NotifyWatchers();
+
+ // Check if we've already got a pending update. If so we won't schedule
+ // another one.
+ bool alreadyNotifying = mInitialValue.isSome();
+
+ // Stash the initial value if needed, then update to the new value.
+ if (mInitialValue.isNothing()) {
+ mInitialValue.emplace(mValue);
+ }
+ mValue = aNewValue;
+
+ // We wait until things have stablized before sending state updates so
+ // that we can avoid sending multiple updates, and possibly avoid sending
+ // any updates at all if the value ends up where it started.
+ if (!alreadyNotifying) {
+ AbstractThread::DispatchDirectTask(NewRunnableMethod(
+ "Canonical::Impl::DoNotify", this, &Impl::DoNotify));
+ }
+ }
+
+ Impl& operator=(const T& aNewValue) {
+ Set(aNewValue);
+ return *this;
+ }
+ Impl& operator=(const Impl& aOther) {
+ Set(aOther);
+ return *this;
+ }
+ Impl(const Impl& aOther) = delete;
+
+ protected:
+ ~Impl() { MOZ_DIAGNOSTIC_ASSERT(mMirrors.IsEmpty()); }
+
+ private:
+ void DoNotify() {
+ MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
+ MOZ_ASSERT(mInitialValue.isSome());
+ bool same = mInitialValue.ref() == mValue;
+ mInitialValue.reset();
+
+ if (same) {
+ MIRROR_LOG("%s [%p] unchanged - not sending update", mName, this);
+ return;
+ }
+
+ for (size_t i = 0; i < mMirrors.Length(); ++i) {
+ mMirrors[i]->OwnerThread()->DispatchStateChange(
+ MakeNotifier(mMirrors[i]));
+ }
+ }
+
+ already_AddRefed<nsIRunnable> MakeNotifier(AbstractMirror<T>* aMirror) {
+ return NewRunnableMethod<T>("AbstractMirror::UpdateValue", aMirror,
+ &AbstractMirror<T>::UpdateValue, mValue);
+ ;
+ }
+
+ T mValue;
+ Maybe<T> mInitialValue;
+ nsTArray<RefPtr<AbstractMirror<T>>> mMirrors;
+ };
+
+ public:
+ // NB: Because mirror-initiated disconnection can race with canonical-
+ // initiated disconnection, a canonical should never be reinitialized.
+ // Forward control operations to the Impl.
+ void DisconnectAll() { return mImpl->DisconnectAll(); }
+
+ // Access to the Impl.
+ operator Impl&() { return *mImpl; }
+ Impl* operator&() { return mImpl; }
+
+ // Access to the T.
+ const T& Ref() const { return *mImpl; }
+ operator const T&() const { return Ref(); }
+ void Set(const T& aNewValue) { mImpl->Set(aNewValue); }
+ Canonical& operator=(const T& aNewValue) {
+ Set(aNewValue);
+ return *this;
+ }
+ Canonical& operator=(const Canonical& aOther) {
+ Set(aOther);
+ return *this;
+ }
+ Canonical(const Canonical& aOther) = delete;
+
+ private:
+ RefPtr<Impl> mImpl;
+};
+
+/*
+ * Mirror<T> is a wrapper class that allows a given value to mirror that of a
+ * Canonical<T> owned by another thread. It registers itself with a
+ * Canonical<T>, and is periodically updated with new values. Mirror<T> is also
+ * a WatchTarget, and may be set up to trigger other routines (on the same
+ * thread) when the mirrored value changes.
+ *
+ * Mirror<T> is intended to be used as a member variable, so it doesn't actually
+ * inherit AbstractMirror<T> (a refcounted type). Rather, it contains an inner
+ * class called |Impl| that implements most of the interesting logic.
+ */
+template <typename T>
+class Mirror {
+ public:
+ Mirror(AbstractThread* aThread, const T& aInitialValue, const char* aName) {
+ mImpl = new Impl(aThread, aInitialValue, aName);
+ }
+
+ ~Mirror() {
+ // As a member of complex objects, a Mirror<T> may be destroyed on a
+ // different thread than its owner, or late in shutdown during CC. Given
+ // that, we require manual disconnection so that callers can put things in
+ // the right place.
+ MOZ_DIAGNOSTIC_ASSERT(!mImpl->IsConnected());
+ }
+
+ private:
+ class Impl : public AbstractMirror<T>, public WatchTarget {
+ public:
+ using AbstractMirror<T>::OwnerThread;
+
+ Impl(AbstractThread* aThread, const T& aInitialValue, const char* aName)
+ : AbstractMirror<T>(aThread),
+ WatchTarget(aName),
+ mValue(aInitialValue) {
+ MIRROR_LOG("%s [%p] initialized", mName, this);
+ MOZ_ASSERT(aThread->SupportsTailDispatch(),
+ "Can't get coherency without tail dispatch");
+ }
+
+ operator const T&() {
+ MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
+ return mValue;
+ }
+
+ virtual void UpdateValue(const T& aNewValue) override {
+ MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
+ if (mValue != aNewValue) {
+ mValue = aNewValue;
+ WatchTarget::NotifyWatchers();
+ }
+ }
+
+ virtual void NotifyDisconnected() override {
+ MIRROR_LOG("%s [%p] Notifed of disconnection from %p", mName, this,
+ mCanonical.get());
+ MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
+ mCanonical = nullptr;
+ }
+
+ bool IsConnected() const { return !!mCanonical; }
+
+ void Connect(AbstractCanonical<T>* aCanonical) {
+ MIRROR_LOG("%s [%p] Connecting to %p", mName, this, aCanonical);
+ MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
+ MOZ_ASSERT(!IsConnected());
+ MOZ_ASSERT(OwnerThread()->RequiresTailDispatch(aCanonical->OwnerThread()),
+ "Can't get coherency without tail dispatch");
+
+ nsCOMPtr<nsIRunnable> r =
+ NewRunnableMethod<StoreRefPtrPassByPtr<AbstractMirror<T>>>(
+ "AbstractCanonical::AddMirror", aCanonical,
+ &AbstractCanonical<T>::AddMirror, this);
+ aCanonical->OwnerThread()->Dispatch(r.forget());
+ mCanonical = aCanonical;
+ }
+
+ public:
+ void DisconnectIfConnected() {
+ MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
+ if (!IsConnected()) {
+ return;
+ }
+
+ MIRROR_LOG("%s [%p] Disconnecting from %p", mName, this,
+ mCanonical.get());
+ nsCOMPtr<nsIRunnable> r =
+ NewRunnableMethod<StoreRefPtrPassByPtr<AbstractMirror<T>>>(
+ "AbstractCanonical::RemoveMirror", mCanonical,
+ &AbstractCanonical<T>::RemoveMirror, this);
+ mCanonical->OwnerThread()->Dispatch(r.forget());
+ mCanonical = nullptr;
+ }
+
+ protected:
+ ~Impl() { MOZ_DIAGNOSTIC_ASSERT(!IsConnected()); }
+
+ private:
+ T mValue;
+ RefPtr<AbstractCanonical<T>> mCanonical;
+ };
+
+ public:
+ // Forward control operations to the Impl<T>.
+ void Connect(AbstractCanonical<T>* aCanonical) { mImpl->Connect(aCanonical); }
+ void DisconnectIfConnected() { mImpl->DisconnectIfConnected(); }
+
+ // Access to the Impl<T>.
+ operator Impl&() { return *mImpl; }
+ Impl* operator&() { return mImpl; }
+
+ // Access to the T.
+ const T& Ref() const { return *mImpl; }
+ operator const T&() const { return Ref(); }
+
+ private:
+ RefPtr<Impl> mImpl;
+};
+
+# undef MIRROR_LOG
+
+} // namespace mozilla
+
+#endif
diff --git a/xpcom/threads/StateWatching.h b/xpcom/threads/StateWatching.h
new file mode 100644
index 0000000000..3da0c63bfe
--- /dev/null
+++ b/xpcom/threads/StateWatching.h
@@ -0,0 +1,302 @@
+/* -*- 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/. */
+
+#if !defined(StateWatching_h_)
+# define StateWatching_h_
+
+# include <cstddef>
+# include <new>
+# include <utility>
+# include "mozilla/AbstractThread.h"
+# include "mozilla/Assertions.h"
+# include "mozilla/Logging.h"
+# include "mozilla/RefPtr.h"
+# include "nsISupports.h"
+# include "nsTArray.h"
+# include "nsThreadUtils.h"
+
+/*
+ * The state-watching machinery automates the process of responding to changes
+ * in various pieces of state.
+ *
+ * A standard programming pattern is as follows:
+ *
+ * mFoo = ...;
+ * NotifyStuffChanged();
+ * ...
+ * mBar = ...;
+ * NotifyStuffChanged();
+ *
+ * This pattern is error-prone and difficult to audit because it requires the
+ * programmer to manually trigger the update routine. This can be especially
+ * problematic when the update routine depends on numerous pieces of state, and
+ * when that state is modified across a variety of helper methods. In these
+ * cases the responsibility for invoking the routine is often unclear, causing
+ * developers to scatter calls to it like pixie dust. This can result in
+ * duplicate invocations (which is wasteful) and missing invocations in corner-
+ * cases (which is a source of bugs).
+ *
+ * This file provides a set of primitives that automatically handle updates and
+ * allow the programmers to explicitly construct a graph of state dependencies.
+ * When used correctly, it eliminates the guess-work and wasted cycles described
+ * above.
+ *
+ * There are two basic pieces:
+ * (1) Objects that can be watched for updates. These inherit WatchTarget.
+ * (2) Objects that receive objects and trigger processing. These inherit
+ * AbstractWatcher. In the current machinery, these exist only internally
+ * within the WatchManager, though that could change.
+ *
+ * Note that none of this machinery is thread-safe - it must all happen on the
+ * same owning thread. To solve multi-threaded use-cases, use state mirroring
+ * and watch the mirrored value.
+ *
+ * Given that semantics may change and comments tend to go out of date, we
+ * deliberately don't provide usage examples here. Grep around to find them.
+ */
+
+namespace mozilla {
+
+extern LazyLogModule gStateWatchingLog;
+
+# define WATCH_LOG(x, ...) \
+ MOZ_LOG(gStateWatchingLog, LogLevel::Debug, (x, ##__VA_ARGS__))
+
+/*
+ * AbstractWatcher is a superclass from which all watchers must inherit.
+ */
+class AbstractWatcher {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractWatcher)
+ AbstractWatcher() : mDestroyed(false) {}
+ bool IsDestroyed() { return mDestroyed; }
+ virtual void Notify() = 0;
+
+ protected:
+ virtual ~AbstractWatcher() { MOZ_ASSERT(mDestroyed); }
+ bool mDestroyed;
+};
+
+/*
+ * WatchTarget is a superclass from which all watchable things must inherit.
+ * Unlike AbstractWatcher, it is a fully-implemented Mix-in, and the subclass
+ * needs only to invoke NotifyWatchers when something changes.
+ *
+ * The functionality that this class provides is not threadsafe, and should only
+ * be used on the thread that owns that WatchTarget.
+ */
+class WatchTarget {
+ public:
+ explicit WatchTarget(const char* aName) : mName(aName) {}
+
+ void AddWatcher(AbstractWatcher* aWatcher) {
+ MOZ_ASSERT(!mWatchers.Contains(aWatcher));
+ mWatchers.AppendElement(aWatcher);
+ }
+
+ void RemoveWatcher(AbstractWatcher* aWatcher) {
+ MOZ_ASSERT(mWatchers.Contains(aWatcher));
+ mWatchers.RemoveElement(aWatcher);
+ }
+
+ protected:
+ void NotifyWatchers() {
+ WATCH_LOG("%s[%p] notifying watchers\n", mName, this);
+ PruneWatchers();
+ for (size_t i = 0; i < mWatchers.Length(); ++i) {
+ mWatchers[i]->Notify();
+ }
+ }
+
+ private:
+ // We don't have Watchers explicitly unregister themselves when they die,
+ // because then they'd need back-references to all the WatchTargets they're
+ // subscribed to, and WatchTargets aren't reference-counted. So instead we
+ // just prune dead ones at appropriate times, which works just fine.
+ void PruneWatchers() {
+ mWatchers.RemoveElementsBy(
+ [](const auto& watcher) { return watcher->IsDestroyed(); });
+ }
+
+ nsTArray<RefPtr<AbstractWatcher>> mWatchers;
+
+ protected:
+ const char* mName;
+};
+
+/*
+ * Watchable is a wrapper class that turns any primitive into a WatchTarget.
+ */
+template <typename T>
+class Watchable : public WatchTarget {
+ public:
+ Watchable(const T& aInitialValue, const char* aName)
+ : WatchTarget(aName), mValue(aInitialValue) {}
+
+ const T& Ref() const { return mValue; }
+ operator const T&() const { return Ref(); }
+ Watchable& operator=(const T& aNewValue) {
+ if (aNewValue != mValue) {
+ mValue = aNewValue;
+ NotifyWatchers();
+ }
+
+ return *this;
+ }
+
+ private:
+ Watchable(const Watchable& aOther) = delete;
+ Watchable& operator=(const Watchable& aOther) = delete;
+
+ T mValue;
+};
+
+// Manager class for state-watching. Declare one of these in any class for which
+// you want to invoke method callbacks.
+//
+// Internally, WatchManager maintains one AbstractWatcher per callback method.
+// Consumers invoke Watch/Unwatch on a particular (WatchTarget, Callback) tuple.
+// This causes an AbstractWatcher for |Callback| to be instantiated if it
+// doesn't already exist, and registers it with |WatchTarget|.
+//
+// Using Direct Tasks on the TailDispatcher, WatchManager ensures that we fire
+// watch callbacks no more than once per task, once all other operations for
+// that task have been completed.
+//
+// WatchManager<OwnerType> is intended to be declared as a member of |OwnerType|
+// objects. Given that, it and its owned objects can't hold permanent strong
+// refs to the owner, since that would keep the owner alive indefinitely.
+// Instead, it _only_ holds strong refs while waiting for Direct Tasks to fire.
+// This ensures that everything is kept alive just long enough.
+template <typename OwnerType>
+class WatchManager {
+ public:
+ typedef void (OwnerType::*CallbackMethod)();
+ explicit WatchManager(OwnerType* aOwner, AbstractThread* aOwnerThread)
+ : mOwner(aOwner), mOwnerThread(aOwnerThread) {}
+
+ ~WatchManager() {
+ if (!IsShutdown()) {
+ Shutdown();
+ }
+ }
+
+ bool IsShutdown() const { return !mOwner; }
+
+ // Shutdown needs to happen on mOwnerThread. If the WatchManager will be
+ // destroyed on a different thread, Shutdown() must be called manually.
+ void Shutdown() {
+ MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+ for (auto& watcher : mWatchers) {
+ watcher->Destroy();
+ }
+ mWatchers.Clear();
+ mOwner = nullptr;
+ }
+
+ void Watch(WatchTarget& aTarget, CallbackMethod aMethod) {
+ MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+ aTarget.AddWatcher(&EnsureWatcher(aMethod));
+ }
+
+ void Unwatch(WatchTarget& aTarget, CallbackMethod aMethod) {
+ MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+ PerCallbackWatcher* watcher = GetWatcher(aMethod);
+ MOZ_ASSERT(watcher);
+ aTarget.RemoveWatcher(watcher);
+ }
+
+ void ManualNotify(CallbackMethod aMethod) {
+ MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+ PerCallbackWatcher* watcher = GetWatcher(aMethod);
+ MOZ_ASSERT(watcher);
+ watcher->Notify();
+ }
+
+ private:
+ class PerCallbackWatcher : public AbstractWatcher {
+ public:
+ PerCallbackWatcher(OwnerType* aOwner, AbstractThread* aOwnerThread,
+ CallbackMethod aMethod)
+ : mOwner(aOwner),
+ mOwnerThread(aOwnerThread),
+ mCallbackMethod(aMethod) {}
+
+ void Destroy() {
+ MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+ mDestroyed = true;
+ mOwner = nullptr;
+ }
+
+ void Notify() override {
+ MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+ MOZ_DIAGNOSTIC_ASSERT(mOwner,
+ "mOwner is only null after destruction, "
+ "at which point we shouldn't be notified");
+ if (mNotificationPending) {
+ // We've already got a notification job in the pipe.
+ return;
+ }
+ mNotificationPending = true;
+
+ // Queue up our notification jobs to run in a stable state.
+ AbstractThread::DispatchDirectTask(
+ NS_NewRunnableFunction("WatchManager::PerCallbackWatcher::Notify",
+ [self = RefPtr<PerCallbackWatcher>(this),
+ owner = RefPtr<OwnerType>(mOwner)]() {
+ if (!self->mDestroyed) {
+ ((*owner).*(self->mCallbackMethod))();
+ }
+ self->mNotificationPending = false;
+ }));
+ }
+
+ bool CallbackMethodIs(CallbackMethod aMethod) const {
+ return mCallbackMethod == aMethod;
+ }
+
+ private:
+ ~PerCallbackWatcher() = default;
+
+ OwnerType* mOwner; // Never null.
+ bool mNotificationPending = false;
+ RefPtr<AbstractThread> mOwnerThread;
+ CallbackMethod mCallbackMethod;
+ };
+
+ PerCallbackWatcher* GetWatcher(CallbackMethod aMethod) {
+ MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+ for (auto& watcher : mWatchers) {
+ if (watcher->CallbackMethodIs(aMethod)) {
+ return watcher;
+ }
+ }
+ return nullptr;
+ }
+
+ PerCallbackWatcher& EnsureWatcher(CallbackMethod aMethod) {
+ MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+ PerCallbackWatcher* watcher = GetWatcher(aMethod);
+ if (watcher) {
+ return *watcher;
+ }
+ watcher = mWatchers
+ .AppendElement(MakeAndAddRef<PerCallbackWatcher>(
+ mOwner, mOwnerThread, aMethod))
+ ->get();
+ return *watcher;
+ }
+
+ nsTArray<RefPtr<PerCallbackWatcher>> mWatchers;
+ OwnerType* mOwner;
+ RefPtr<AbstractThread> mOwnerThread;
+};
+
+# undef WATCH_LOG
+
+} // namespace mozilla
+
+#endif
diff --git a/xpcom/threads/SyncRunnable.h b/xpcom/threads/SyncRunnable.h
new file mode 100644
index 0000000000..49235e764a
--- /dev/null
+++ b/xpcom/threads/SyncRunnable.h
@@ -0,0 +1,133 @@
+/* -*- 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 <utility>
+
+#include "mozilla/AbstractThread.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/dom/JSExecutionManager.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+
+/**
+ * This class will wrap a nsIRunnable and dispatch it to the main thread
+ * synchronously. This is different from nsIEventTarget.DISPATCH_SYNC:
+ * this class does not spin the event loop waiting for the event to be
+ * dispatched. This means that you don't risk reentrance from pending
+ * messages, but you must be sure that the target thread does not ever block
+ * on this thread, or else you will deadlock.
+ *
+ * Typical usage:
+ * RefPtr<SyncRunnable> sr = new SyncRunnable(new myrunnable...());
+ * sr->DispatchToThread(t);
+ *
+ * We also provide a convenience wrapper:
+ * SyncRunnable::DispatchToThread(new myrunnable...());
+ *
+ */
+class SyncRunnable : public Runnable {
+ public:
+ explicit SyncRunnable(nsIRunnable* aRunnable)
+ : Runnable("SyncRunnable"),
+ mRunnable(aRunnable),
+ mMonitor("SyncRunnable"),
+ mDone(false) {}
+
+ explicit SyncRunnable(already_AddRefed<nsIRunnable> aRunnable)
+ : Runnable("SyncRunnable"),
+ mRunnable(std::move(aRunnable)),
+ mMonitor("SyncRunnable"),
+ mDone(false) {}
+
+ nsresult DispatchToThread(nsIEventTarget* aThread,
+ bool aForceDispatch = false) {
+ nsresult rv;
+ bool on;
+
+ if (!aForceDispatch) {
+ rv = aThread->IsOnCurrentThread(&on);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (NS_SUCCEEDED(rv) && on) {
+ mRunnable->Run();
+ return NS_OK;
+ }
+ }
+
+ rv = aThread->Dispatch(this, NS_DISPATCH_NORMAL);
+ if (NS_SUCCEEDED(rv)) {
+ mozilla::MonitorAutoLock lock(mMonitor);
+ // This could be synchronously dispatching to a thread currently waiting
+ // for JS execution clearance. Yield JS execution.
+ dom::AutoYieldJSThreadExecution yield;
+
+ while (!mDone) {
+ lock.Wait();
+ }
+ }
+ return rv;
+ }
+
+ nsresult DispatchToThread(AbstractThread* aThread,
+ bool aForceDispatch = false) {
+ if (!aForceDispatch && aThread->IsCurrentThreadIn()) {
+ mRunnable->Run();
+ return NS_OK;
+ }
+
+ // Check we don't have tail dispatching here. Otherwise we will deadlock
+ // ourself when spinning the loop below.
+ MOZ_ASSERT(!aThread->RequiresTailDispatchFromCurrentThread());
+
+ nsresult rv = aThread->Dispatch(RefPtr<nsIRunnable>(this).forget());
+ if (NS_SUCCEEDED(rv)) {
+ mozilla::MonitorAutoLock lock(mMonitor);
+ while (!mDone) {
+ lock.Wait();
+ }
+ }
+ return rv;
+ }
+
+ static nsresult DispatchToThread(nsIEventTarget* aThread,
+ nsIRunnable* aRunnable,
+ bool aForceDispatch = false) {
+ RefPtr<SyncRunnable> s(new SyncRunnable(aRunnable));
+ return s->DispatchToThread(aThread, aForceDispatch);
+ }
+
+ static nsresult DispatchToThread(AbstractThread* aThread,
+ nsIRunnable* aRunnable,
+ bool aForceDispatch = false) {
+ RefPtr<SyncRunnable> s(new SyncRunnable(aRunnable));
+ return s->DispatchToThread(aThread, aForceDispatch);
+ }
+
+ protected:
+ NS_IMETHOD Run() override {
+ mRunnable->Run();
+
+ 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
+
+#endif // mozilla_SyncRunnable_h
diff --git a/xpcom/threads/SynchronizedEventQueue.cpp b/xpcom/threads/SynchronizedEventQueue.cpp
new file mode 100644
index 0000000000..59161b7f9d
--- /dev/null
+++ b/xpcom/threads/SynchronizedEventQueue.cpp
@@ -0,0 +1,26 @@
+/* -*- 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 "SynchronizedEventQueue.h"
+#include "nsIThreadInternal.h"
+
+using namespace mozilla;
+
+void SynchronizedEventQueue::AddObserver(nsIThreadObserver* aObserver) {
+ MOZ_ASSERT(aObserver);
+ MOZ_ASSERT(!mEventObservers.Contains(aObserver));
+ mEventObservers.AppendElement(aObserver);
+}
+
+void SynchronizedEventQueue::RemoveObserver(nsIThreadObserver* aObserver) {
+ MOZ_ASSERT(aObserver);
+ MOZ_ALWAYS_TRUE(mEventObservers.RemoveElement(aObserver));
+}
+
+const nsTObserverArray<nsCOMPtr<nsIThreadObserver>>&
+SynchronizedEventQueue::EventObservers() {
+ return mEventObservers;
+}
diff --git a/xpcom/threads/SynchronizedEventQueue.h b/xpcom/threads/SynchronizedEventQueue.h
new file mode 100644
index 0000000000..81a48fa51b
--- /dev/null
+++ b/xpcom/threads/SynchronizedEventQueue.h
@@ -0,0 +1,119 @@
+/* -*- 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_SynchronizedEventQueue_h
+#define mozilla_SynchronizedEventQueue_h
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/EventQueue.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Mutex.h"
+#include "nsIThreadInternal.h"
+#include "nsCOMPtr.h"
+#include "nsTObserverArray.h"
+
+class nsIEventTarget;
+class nsISerialEventTarget;
+class nsIThreadObserver;
+
+namespace mozilla {
+
+// A SynchronizedEventQueue is an abstract class for event queues that can be
+// used across threads. A SynchronizedEventQueue implementation will typically
+// use locks and condition variables to guarantee consistency. The methods of
+// SynchronizedEventQueue are split between ThreadTargetSink (which contains
+// methods for posting events) and SynchronizedEventQueue (which contains
+// methods for getting events). This split allows event targets (specifically
+// ThreadEventTarget) to use a narrow interface, since they only need to post
+// events.
+//
+// ThreadEventQueue is the canonical implementation of
+// SynchronizedEventQueue. When Quantum DOM is implemented, we will use a
+// different synchronized queue on the main thread, SchedulerEventQueue, which
+// will handle the cooperative threading model.
+
+class ThreadTargetSink {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ThreadTargetSink)
+
+ virtual bool PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
+ EventQueuePriority aPriority) = 0;
+
+ // After this method is called, no more events can be posted.
+ virtual void Disconnect(const MutexAutoLock& aProofOfLock) = 0;
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ virtual size_t SizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const = 0;
+
+ protected:
+ virtual ~ThreadTargetSink() = default;
+};
+
+class SynchronizedEventQueue : public ThreadTargetSink {
+ public:
+ virtual already_AddRefed<nsIRunnable> GetEvent(
+ bool aMayWait, mozilla::TimeDuration* aLastEventDelay = nullptr) = 0;
+ virtual bool HasPendingEvent() = 0;
+
+ // This method atomically checks if there are pending events and, if there are
+ // none, forbids future events from being posted. It returns true if there
+ // were no pending events.
+ virtual bool ShutdownIfNoPendingEvents() = 0;
+
+ // These methods provide access to an nsIThreadObserver, whose methods are
+ // called when posting and processing events. SetObserver should only be
+ // called on the thread that processes events. GetObserver can be called from
+ // any thread. GetObserverOnThread must be used from the thread that processes
+ // events; it does not acquire a lock.
+ virtual already_AddRefed<nsIThreadObserver> GetObserver() = 0;
+ virtual already_AddRefed<nsIThreadObserver> GetObserverOnThread() = 0;
+ virtual void SetObserver(nsIThreadObserver* aObserver) = 0;
+
+ void AddObserver(nsIThreadObserver* aObserver);
+ void RemoveObserver(nsIThreadObserver* aObserver);
+ const nsTObserverArray<nsCOMPtr<nsIThreadObserver>>& EventObservers();
+
+ size_t SizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const override {
+ return mEventObservers.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ /**
+ * 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.
+ */
+ virtual already_AddRefed<nsISerialEventTarget> PushEventQueue() = 0;
+
+ /**
+ * 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.
+ */
+ virtual void PopEventQueue(nsIEventTarget* aTarget) = 0;
+
+ protected:
+ virtual ~SynchronizedEventQueue() = default;
+
+ private:
+ nsTObserverArray<nsCOMPtr<nsIThreadObserver>> mEventObservers;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_SynchronizedEventQueue_h
diff --git a/xpcom/threads/TaskCategory.h b/xpcom/threads/TaskCategory.h
new file mode 100644
index 0000000000..8f189c8737
--- /dev/null
+++ b/xpcom/threads/TaskCategory.h
@@ -0,0 +1,47 @@
+/* -*- 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_TaskCategory_h
+#define mozilla_TaskCategory_h
+
+namespace mozilla {
+
+// The different kinds of tasks we can dispatch to a SystemGroup, TabGroup, or
+// DocGroup.
+enum class TaskCategory {
+ // User input (clicks, keypresses, etc.)
+ UI,
+
+ // Data from the network
+ Network,
+
+ // setTimeout, setInterval
+ Timer,
+
+ // Runnables posted from a worker to the main thread
+ Worker,
+
+ // requestIdleCallback
+ IdleCallback,
+
+ // Vsync notifications
+ RefreshDriver,
+
+ // GC/CC-related tasks
+ GarbageCollection,
+
+ // Most DOM events (postMessage, media, plugins)
+ Other,
+
+ // Runnables related to Performance Counting
+ Performance,
+
+ Count
+};
+
+} // namespace mozilla
+
+#endif // mozilla_TaskCategory_h
diff --git a/xpcom/threads/TaskController.cpp b/xpcom/threads/TaskController.cpp
new file mode 100644
index 0000000000..a30353bc20
--- /dev/null
+++ b/xpcom/threads/TaskController.cpp
@@ -0,0 +1,936 @@
+/* -*- 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 "TaskController.h"
+#include "nsIIdleRunnable.h"
+#include "nsIRunnable.h"
+#include "nsThreadUtils.h"
+#include <algorithm>
+#include <initializer_list>
+#include "GeckoProfiler.h"
+#include "mozilla/EventQueue.h"
+#include "mozilla/BackgroundHangMonitor.h"
+#include "mozilla/InputTaskManager.h"
+#include "mozilla/IOInterposer.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Unused.h"
+#include "nsIThreadInternal.h"
+#include "nsQueryObject.h"
+#include "nsThread.h"
+#include "prenv.h"
+#include "prsystem.h"
+
+#ifdef XP_WIN
+typedef HRESULT(WINAPI* SetThreadDescriptionPtr)(HANDLE hThread,
+ PCWSTR lpThreadDescription);
+#endif
+
+namespace mozilla {
+
+std::unique_ptr<TaskController> TaskController::sSingleton;
+thread_local size_t mThreadPoolIndex = -1;
+std::atomic<uint64_t> Task::sCurrentTaskSeqNo = 0;
+
+const int32_t kMaximumPoolThreadCount = 8;
+
+static int32_t GetPoolThreadCount() {
+ if (PR_GetEnv("MOZ_TASKCONTROLLER_THREADCOUNT")) {
+ return strtol(PR_GetEnv("MOZ_TASKCONTROLLER_THREADCOUNT"), nullptr, 0);
+ }
+
+ int32_t numCores = std::max<int32_t>(1, PR_GetNumberOfProcessors());
+
+ if (numCores == 1) {
+ return 1;
+ }
+ if (numCores == 2) {
+ return 2;
+ }
+ return std::min<int32_t>(kMaximumPoolThreadCount, numCores - 1);
+}
+
+bool TaskManager::
+ UpdateCachesForCurrentIterationAndReportPriorityModifierChanged(
+ const MutexAutoLock& aProofOfLock, IterationType aIterationType) {
+ mCurrentSuspended = IsSuspended(aProofOfLock);
+
+ if (aIterationType == IterationType::EVENT_LOOP_TURN) {
+ int32_t oldModifier = mCurrentPriorityModifier;
+ mCurrentPriorityModifier =
+ GetPriorityModifierForEventLoopTurn(aProofOfLock);
+
+ if (mCurrentPriorityModifier != oldModifier) {
+ return true;
+ }
+ }
+ return false;
+}
+
+Task* Task::GetHighestPriorityDependency() {
+ Task* currentTask = this;
+
+ while (!currentTask->mDependencies.empty()) {
+ auto iter = currentTask->mDependencies.begin();
+
+ while (iter != currentTask->mDependencies.end()) {
+ if ((*iter)->mCompleted) {
+ auto oldIter = iter;
+ iter++;
+ // Completed tasks are removed here to prevent needlessly keeping them
+ // alive or iterating over them in the future.
+ currentTask->mDependencies.erase(oldIter);
+ continue;
+ }
+
+ currentTask = iter->get();
+ break;
+ }
+ }
+
+ return currentTask == this ? nullptr : currentTask;
+}
+
+TaskController* TaskController::Get() {
+ MOZ_ASSERT(sSingleton.get());
+ return sSingleton.get();
+}
+
+bool TaskController::Initialize() {
+ MOZ_ASSERT(!sSingleton);
+ sSingleton = std::make_unique<TaskController>();
+ return sSingleton->InitializeInternal();
+}
+
+void ThreadFuncPoolThread(void* aIndex) {
+ mThreadPoolIndex = *reinterpret_cast<int32_t*>(aIndex);
+ delete reinterpret_cast<int32_t*>(aIndex);
+ TaskController::Get()->RunPoolThread();
+}
+
+#ifdef XP_WIN
+static SetThreadDescriptionPtr sSetThreadDescriptionFunc = nullptr;
+#endif
+
+bool TaskController::InitializeInternal() {
+ InputTaskManager::Init();
+ mMTProcessingRunnable = NS_NewRunnableFunction(
+ "TaskController::ExecutePendingMTTasks()",
+ []() { TaskController::Get()->ProcessPendingMTTask(); });
+ mMTBlockingProcessingRunnable = NS_NewRunnableFunction(
+ "TaskController::ExecutePendingMTTasks()",
+ []() { TaskController::Get()->ProcessPendingMTTask(true); });
+
+#ifdef XP_WIN
+ sSetThreadDescriptionFunc =
+ reinterpret_cast<SetThreadDescriptionPtr>(::GetProcAddress(
+ ::GetModuleHandle(L"Kernel32.dll"), "SetThreadDescription"));
+#endif
+
+ return true;
+}
+
+// Ample stack size allocated for applications like ImageLib's AV1 decoder.
+const PRUint32 sStackSize = 512u * 1024u;
+
+void TaskController::InitializeThreadPool() {
+ mPoolInitializationMutex.AssertCurrentThreadOwns();
+ MOZ_ASSERT(!mThreadPoolInitialized);
+ mThreadPoolInitialized = true;
+
+ int32_t poolSize = GetPoolThreadCount();
+ for (int32_t i = 0; i < poolSize; i++) {
+ int32_t* index = new int32_t(i);
+ mPoolThreads.push_back(
+ {PR_CreateThread(PR_USER_THREAD, ThreadFuncPoolThread, index,
+ PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
+ PR_JOINABLE_THREAD, 512u * 1024u),
+ nullptr});
+ }
+}
+
+void TaskController::SetPerformanceCounterState(
+ PerformanceCounterState* aPerformanceCounterState) {
+ mPerformanceCounterState = aPerformanceCounterState;
+}
+
+/* static */
+void TaskController::Shutdown() {
+ InputTaskManager::Cleanup();
+ if (sSingleton) {
+ sSingleton->ShutdownThreadPoolInternal();
+ sSingleton->ShutdownInternal();
+ }
+ MOZ_ASSERT(!sSingleton);
+}
+
+void TaskController::ShutdownThreadPoolInternal() {
+ {
+ // Prevent racecondition on mShuttingDown and wait.
+ MutexAutoLock lock(mGraphMutex);
+
+ mShuttingDown = true;
+ mThreadPoolCV.NotifyAll();
+ }
+ for (PoolThread& thread : mPoolThreads) {
+ PR_JoinThread(thread.mThread);
+ }
+}
+
+void TaskController::ShutdownInternal() { sSingleton = nullptr; }
+
+void TaskController::RunPoolThread() {
+ IOInterposer::RegisterCurrentThread();
+
+ // This is used to hold on to a task to make sure it is released outside the
+ // lock. This is required since it's perfectly feasible for task destructors
+ // to post events themselves.
+ RefPtr<Task> lastTask;
+
+#ifdef XP_WIN
+ nsAutoString threadWName;
+ threadWName.AppendLiteral(u"TaskController Thread #");
+ threadWName.AppendInt(static_cast<int64_t>(mThreadPoolIndex));
+
+ if (sSetThreadDescriptionFunc) {
+ sSetThreadDescriptionFunc(
+ ::GetCurrentThread(),
+ reinterpret_cast<const WCHAR*>(threadWName.BeginReading()));
+ }
+#endif
+ nsAutoCString threadName;
+ threadName.AppendLiteral("TaskController Thread #");
+ threadName.AppendInt(static_cast<int64_t>(mThreadPoolIndex));
+ PROFILER_REGISTER_THREAD(threadName.BeginReading());
+
+ MutexAutoLock lock(mGraphMutex);
+ while (true) {
+ bool ranTask = false;
+
+ if (!mThreadableTasks.empty()) {
+ for (auto iter = mThreadableTasks.begin(); iter != mThreadableTasks.end();
+ ++iter) {
+ // Search for the highest priority dependency of the highest priority
+ // task.
+
+ // We work with rawptrs to avoid needless refcounting. All our tasks
+ // are always kept alive by the graph. If one is removed from the graph
+ // it is kept alive by mPoolThreads[mThreadPoolIndex].mCurrentTask.
+ Task* task = iter->get();
+
+ MOZ_ASSERT(!task->mTaskManager);
+
+ mPoolThreads[mThreadPoolIndex].mEffectiveTaskPriority =
+ task->GetPriority();
+
+ Task* nextTask;
+ while ((nextTask = task->GetHighestPriorityDependency())) {
+ task = nextTask;
+ }
+
+ if (task->IsMainThreadOnly() || task->mInProgress) {
+ continue;
+ }
+
+ mPoolThreads[mThreadPoolIndex].mCurrentTask = task;
+ mThreadableTasks.erase(task->mIterator);
+ task->mIterator = mThreadableTasks.end();
+ task->mInProgress = true;
+
+ bool taskCompleted = false;
+ {
+ MutexAutoUnlock unlock(mGraphMutex);
+ lastTask = nullptr;
+ taskCompleted = task->Run();
+ ranTask = true;
+ }
+
+ task->mInProgress = false;
+
+ if (!taskCompleted) {
+ // Presumably this task was interrupted, leave its dependencies
+ // unresolved and reinsert into the queue.
+ auto insertion = mThreadableTasks.insert(
+ mPoolThreads[mThreadPoolIndex].mCurrentTask);
+ MOZ_ASSERT(insertion.second);
+ task->mIterator = insertion.first;
+ } else {
+ task->mCompleted = true;
+#ifdef DEBUG
+ task->mIsInGraph = false;
+#endif
+ task->mDependencies.clear();
+ // This may have unblocked a main thread task. We could do this only
+ // if there was a main thread task before this one in the dependency
+ // chain.
+ mMayHaveMainThreadTask = true;
+ // Since this could have multiple dependencies thare are restricted
+ // to the main thread. Let's make sure that's awake.
+ EnsureMainThreadTasksScheduled();
+
+ MaybeInterruptTask(GetHighestPriorityMTTask());
+ }
+
+ // Store last task for release next time we release the lock or enter
+ // wait state.
+ lastTask = mPoolThreads[mThreadPoolIndex].mCurrentTask.forget();
+ break;
+ }
+ }
+
+ // Ensure the last task is released before we enter the wait state.
+ if (lastTask) {
+ MutexAutoUnlock unlock(mGraphMutex);
+ lastTask = nullptr;
+
+ // Run another loop iteration, while we were unlocked there was an
+ // opportunity for another task to be posted or shutdown to be initiated.
+ continue;
+ }
+
+ if (!ranTask) {
+ if (mShuttingDown) {
+ IOInterposer::UnregisterCurrentThread();
+ MOZ_ASSERT(mThreadableTasks.empty());
+ return;
+ }
+
+ AUTO_PROFILER_LABEL("TaskController::RunPoolThread", IDLE);
+ mThreadPoolCV.Wait();
+ }
+ }
+}
+
+void TaskController::AddTask(already_AddRefed<Task>&& aTask) {
+ RefPtr<Task> task(aTask);
+
+ if (!task->IsMainThreadOnly()) {
+ MutexAutoLock lock(mPoolInitializationMutex);
+ if (!mThreadPoolInitialized) {
+ InitializeThreadPool();
+ mThreadPoolInitialized = true;
+ }
+ }
+
+ MutexAutoLock lock(mGraphMutex);
+
+ if (TaskManager* manager = task->GetManager()) {
+ if (manager->mTaskCount == 0) {
+ mTaskManagers.insert(manager);
+ }
+ manager->DidQueueTask();
+
+ // Set this here since if this manager's priority modifier doesn't change
+ // we will not reprioritize when iterating over the queue.
+ task->mPriorityModifier = manager->mCurrentPriorityModifier;
+ }
+
+#ifdef MOZ_GECKO_PROFILER
+ task->mInsertionTime = TimeStamp::Now();
+#endif
+
+#ifdef DEBUG
+ task->mIsInGraph = true;
+
+ for (const RefPtr<Task>& otherTask : task->mDependencies) {
+ MOZ_ASSERT(!otherTask->mTaskManager ||
+ otherTask->mTaskManager == task->mTaskManager);
+ }
+#endif
+
+ LogTask::LogDispatch(task);
+
+ std::pair<std::set<RefPtr<Task>, Task::PriorityCompare>::iterator, bool>
+ insertion;
+ if (task->IsMainThreadOnly()) {
+ insertion = mMainThreadTasks.insert(std::move(task));
+ } else {
+ insertion = mThreadableTasks.insert(std::move(task));
+ }
+ (*insertion.first)->mIterator = insertion.first;
+ MOZ_ASSERT(insertion.second);
+
+ MaybeInterruptTask(*insertion.first);
+}
+
+void TaskController::WaitForTaskOrMessage() {
+ MutexAutoLock lock(mGraphMutex);
+ while (!mMayHaveMainThreadTask) {
+ AUTO_PROFILER_LABEL("TaskController::WaitForTaskOrMessage", IDLE);
+ mMainThreadCV.Wait();
+ }
+}
+
+void TaskController::ExecuteNextTaskOnlyMainThread() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MutexAutoLock lock(mGraphMutex);
+ ExecuteNextTaskOnlyMainThreadInternal(lock);
+}
+
+void TaskController::ProcessPendingMTTask(bool aMayWait) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MutexAutoLock lock(mGraphMutex);
+
+ for (;;) {
+ // We only ever process one event here. However we may sometimes
+ // not actually process a real event because of suspended tasks.
+ // This loop allows us to wait until we've processed something
+ // in that scenario.
+
+ mMTTaskRunnableProcessedTask = ExecuteNextTaskOnlyMainThreadInternal(lock);
+
+ if (mMTTaskRunnableProcessedTask || !aMayWait) {
+ break;
+ }
+
+ BackgroundHangMonitor().NotifyWait();
+
+ {
+ // ProcessNextEvent will also have attempted to wait, however we may have
+ // given it a Runnable when all the tasks in our task graph were suspended
+ // but we weren't able to cheaply determine that.
+ AUTO_PROFILER_LABEL("TaskController::ProcessPendingMTTask", IDLE);
+ mMainThreadCV.Wait();
+ }
+
+ BackgroundHangMonitor().NotifyActivity();
+ }
+
+ if (mMayHaveMainThreadTask) {
+ EnsureMainThreadTasksScheduled();
+ }
+}
+
+void TaskController::ReprioritizeTask(Task* aTask, uint32_t aPriority) {
+ MutexAutoLock lock(mGraphMutex);
+ std::set<RefPtr<Task>, Task::PriorityCompare>* queue = &mMainThreadTasks;
+ if (!aTask->IsMainThreadOnly()) {
+ queue = &mThreadableTasks;
+ }
+
+ MOZ_ASSERT(aTask->mIterator != queue->end());
+ queue->erase(aTask->mIterator);
+
+ aTask->mPriority = aPriority;
+
+ auto insertion = queue->insert(aTask);
+ MOZ_ASSERT(insertion.second);
+ aTask->mIterator = insertion.first;
+
+ MaybeInterruptTask(aTask);
+}
+
+// Code supporting runnable compatibility.
+// Task that wraps a runnable.
+class RunnableTask : public Task {
+ public:
+ RunnableTask(already_AddRefed<nsIRunnable>&& aRunnable, int32_t aPriority,
+ bool aMainThread = true)
+ : Task(aMainThread, aPriority), mRunnable(aRunnable) {}
+
+ virtual bool Run() override {
+#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+ MOZ_ASSERT(NS_IsMainThread());
+ // If we're on the main thread, we want to record our current
+ // runnable's name in a static so that BHR can record it.
+ Array<char, nsThread::kRunnableNameBufSize> restoreRunnableName;
+ restoreRunnableName[0] = '\0';
+ auto clear = MakeScopeExit([&] {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsThread::sMainThreadRunnableName = restoreRunnableName;
+ });
+ nsAutoCString name;
+ nsThread::GetLabeledRunnableName(mRunnable, name,
+ EventQueuePriority(GetPriority()));
+
+ restoreRunnableName = nsThread::sMainThreadRunnableName;
+
+ // Copy the name into sMainThreadRunnableName's buffer, and append a
+ // terminating null.
+ uint32_t length = std::min((uint32_t)nsThread::kRunnableNameBufSize - 1,
+ (uint32_t)name.Length());
+ memcpy(nsThread::sMainThreadRunnableName.begin(), name.BeginReading(),
+ length);
+ nsThread::sMainThreadRunnableName[length] = '\0';
+#endif
+
+ mRunnable->Run();
+ mRunnable = nullptr;
+ return true;
+ }
+
+ void SetIdleDeadline(TimeStamp aDeadline) override {
+ nsCOMPtr<nsIIdleRunnable> idleRunnable = do_QueryInterface(mRunnable);
+ if (idleRunnable) {
+ idleRunnable->SetDeadline(aDeadline);
+ }
+ }
+
+ PerformanceCounter* GetPerformanceCounter() const override {
+ return nsThread::GetPerformanceCounterBase(mRunnable);
+ }
+
+ virtual bool GetName(nsACString& aName) override {
+#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+ nsThread::GetLabeledRunnableName(mRunnable, aName,
+ EventQueuePriority(GetPriority()));
+ return true;
+#else
+ return false;
+#endif
+ }
+
+ private:
+ RefPtr<nsIRunnable> mRunnable;
+};
+
+void TaskController::DispatchRunnable(already_AddRefed<nsIRunnable>&& aRunnable,
+ uint32_t aPriority,
+ TaskManager* aManager) {
+ RefPtr<RunnableTask> task = new RunnableTask(std::move(aRunnable), aPriority);
+
+ task->SetManager(aManager);
+ TaskController::Get()->AddTask(task.forget());
+}
+
+nsIRunnable* TaskController::GetRunnableForMTTask(bool aReallyWait) {
+ MutexAutoLock lock(mGraphMutex);
+
+ while (mMainThreadTasks.empty()) {
+ if (!aReallyWait) {
+ return nullptr;
+ }
+
+ AUTO_PROFILER_LABEL("TaskController::GetRunnableForMTTask::Wait", IDLE);
+ mMainThreadCV.Wait();
+ }
+
+ return aReallyWait ? mMTBlockingProcessingRunnable : mMTProcessingRunnable;
+}
+
+bool TaskController::HasMainThreadPendingTasks() {
+ auto resetIdleState = MakeScopeExit([&idleManager = mIdleTaskManager] {
+ if (idleManager) {
+ idleManager->State().ClearCachedIdleDeadline();
+ }
+ });
+
+ for (bool considerIdle : {false, true}) {
+ if (considerIdle && !mIdleTaskManager) {
+ continue;
+ }
+
+ MutexAutoLock lock(mGraphMutex);
+
+ if (considerIdle) {
+ mIdleTaskManager->State().ForgetPendingTaskGuarantee();
+ // Temporarily unlock so we can peek our idle deadline.
+ // XXX We could do this _before_ we take the lock if the API would let us.
+ // We do want to do this before looking at mMainThreadTasks, in case
+ // someone adds one while we're unlocked.
+ {
+ MutexAutoUnlock unlock(mGraphMutex);
+ mIdleTaskManager->State().CachePeekedIdleDeadline(unlock);
+ }
+ }
+
+ // Return early if there's no tasks at all.
+ if (mMainThreadTasks.empty()) {
+ return false;
+ }
+
+ // We can cheaply count how many tasks are suspended.
+ uint64_t totalSuspended = 0;
+ for (TaskManager* manager : mTaskManagers) {
+ DebugOnly<bool> modifierChanged =
+ manager
+ ->UpdateCachesForCurrentIterationAndReportPriorityModifierChanged(
+ lock, TaskManager::IterationType::NOT_EVENT_LOOP_TURN);
+ MOZ_ASSERT(!modifierChanged);
+
+ // The idle manager should be suspended unless we're doing the idle pass.
+ MOZ_ASSERT(manager != mIdleTaskManager || manager->mCurrentSuspended ||
+ considerIdle,
+ "Why are idle tasks not suspended here?");
+
+ if (manager->mCurrentSuspended) {
+ // XXX - If managers manage off-main-thread tasks this breaks! This
+ // scenario is explicitly not supported.
+ //
+ // This is only incremented inside the lock -or- decremented on the main
+ // thread so this is safe.
+ totalSuspended += manager->mTaskCount;
+ }
+ }
+
+ // Thi would break down if we have a non-suspended task depending on a
+ // suspended task. This is why for the moment we do not allow tasks
+ // to be dependent on tasks managed by another taskmanager.
+ if (mMainThreadTasks.size() > totalSuspended) {
+ // If mIdleTaskManager->mTaskCount is 0, we never updated the suspended
+ // state of mIdleTaskManager above, hence shouldn't even check it here.
+ // But in that case idle tasks are not contributing to our suspended task
+ // count anyway.
+ if (mIdleTaskManager && mIdleTaskManager->mTaskCount &&
+ !mIdleTaskManager->mCurrentSuspended) {
+ MOZ_ASSERT(considerIdle, "Why is mIdleTaskManager not suspended?");
+ // Check whether the idle tasks were really needed to make our "we have
+ // an unsuspended task" decision. If they were, we need to force-enable
+ // idle tasks until we run our next task.
+ if (mMainThreadTasks.size() - mIdleTaskManager->mTaskCount <=
+ totalSuspended) {
+ mIdleTaskManager->State().EnforcePendingTaskGuarantee();
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+bool TaskController::ExecuteNextTaskOnlyMainThreadInternal(
+ const MutexAutoLock& aProofOfLock) {
+ // Block to make it easier to jump to our cleanup.
+ bool taskRan = false;
+ do {
+ taskRan = DoExecuteNextTaskOnlyMainThreadInternal(aProofOfLock);
+ if (taskRan) {
+ break;
+ }
+
+ if (!mIdleTaskManager) {
+ break;
+ }
+
+ if (mIdleTaskManager->mTaskCount) {
+ // We have idle tasks that we may not have gotten above because
+ // our idle state is not up to date. We need to update the idle state
+ // and try again. We need to temporarily release the lock while we do
+ // that.
+ MutexAutoUnlock unlock(mGraphMutex);
+ mIdleTaskManager->State().UpdateCachedIdleDeadline(unlock);
+ } else {
+ MutexAutoUnlock unlock(mGraphMutex);
+ mIdleTaskManager->State().RanOutOfTasks(unlock);
+ }
+
+ // When we unlocked, someone may have queued a new task on us. So try to
+ // see whether we can run things again.
+ taskRan = DoExecuteNextTaskOnlyMainThreadInternal(aProofOfLock);
+ } while (false);
+
+ if (mIdleTaskManager) {
+ // The pending task guarantee is not needed anymore, since we just tried
+ // running a task
+ mIdleTaskManager->State().ForgetPendingTaskGuarantee();
+
+ if (mMainThreadTasks.empty()) {
+ // XXX the IdlePeriodState API demands we have a MutexAutoUnlock for it.
+ // Otherwise we could perhaps just do this after we exit the locked block,
+ // by pushing the lock down into this method. Though it's not clear that
+ // we could check mMainThreadTasks.size() once we unlock, and whether we
+ // could maybe substitute mMayHaveMainThreadTask for that check.
+ MutexAutoUnlock unlock(mGraphMutex);
+ mIdleTaskManager->State().RanOutOfTasks(unlock);
+ }
+ }
+
+ return taskRan;
+}
+
+bool TaskController::DoExecuteNextTaskOnlyMainThreadInternal(
+ const MutexAutoLock& aProofOfLock) {
+ nsCOMPtr<nsIThread> mainIThread;
+ NS_GetMainThread(getter_AddRefs(mainIThread));
+ nsThread* mainThread = static_cast<nsThread*>(mainIThread.get());
+ mainThread->SetRunningEventDelay(TimeDuration(), TimeStamp());
+
+ uint32_t totalSuspended = 0;
+ for (TaskManager* manager : mTaskManagers) {
+ bool modifierChanged =
+ manager
+ ->UpdateCachesForCurrentIterationAndReportPriorityModifierChanged(
+ aProofOfLock, TaskManager::IterationType::EVENT_LOOP_TURN);
+ if (modifierChanged) {
+ ProcessUpdatedPriorityModifier(manager);
+ }
+ if (manager->mCurrentSuspended) {
+ totalSuspended += manager->mTaskCount;
+ }
+ }
+
+ MOZ_ASSERT(mMainThreadTasks.size() >= totalSuspended);
+
+ // This would break down if we have a non-suspended task depending on a
+ // suspended task. This is why for the moment we do not allow tasks
+ // to be dependent on tasks managed by another taskmanager.
+ if (mMainThreadTasks.size() > totalSuspended) {
+ for (auto iter = mMainThreadTasks.begin(); iter != mMainThreadTasks.end();
+ iter++) {
+ Task* task = iter->get();
+
+ if (task->mTaskManager && task->mTaskManager->mCurrentSuspended) {
+ // Even though we may want to run some dependencies of this task, we
+ // will run them at their own priority level and not the priority
+ // level of their dependents.
+ continue;
+ }
+
+ task = GetFinalDependency(task);
+
+ if (!task->IsMainThreadOnly() || task->mInProgress ||
+ (task->mTaskManager && task->mTaskManager->mCurrentSuspended)) {
+ continue;
+ }
+
+ mCurrentTasksMT.push(task);
+ mMainThreadTasks.erase(task->mIterator);
+ task->mIterator = mMainThreadTasks.end();
+ task->mInProgress = true;
+ TaskManager* manager = task->GetManager();
+ bool result = false;
+
+ {
+ MutexAutoUnlock unlock(mGraphMutex);
+ if (manager) {
+ manager->WillRunTask();
+ if (manager != mIdleTaskManager) {
+ // Notify the idle period state that we're running a non-idle task.
+ // This needs to happen while our mutex is not locked!
+ mIdleTaskManager->State().FlagNotIdle();
+ } else {
+ TimeStamp idleDeadline =
+ mIdleTaskManager->State().GetCachedIdleDeadline();
+ MOZ_ASSERT(
+ idleDeadline,
+ "How can we not have a deadline if our manager is enabled?");
+ task->SetIdleDeadline(idleDeadline);
+ }
+ }
+ if (mIdleTaskManager) {
+ // We found a task to run; we can clear the idle deadline on our idle
+ // task manager. This _must_ be done before we actually run the task,
+ // because running the task could reenter via spinning the event loop
+ // and we want to make sure there's no cached idle deadline at that
+ // point. But we have to make sure we do it after out SetIdleDeadline
+ // call above, in the case when the task is actually an idle task.
+ mIdleTaskManager->State().ClearCachedIdleDeadline();
+ }
+
+ TimeStamp now = TimeStamp::Now();
+
+#ifdef MOZ_GECKO_PROFILER
+ if (task->GetPriority() < uint32_t(EventQueuePriority::InputHigh)) {
+ mainThread->SetRunningEventDelay(TimeDuration(), now);
+ } else {
+ mainThread->SetRunningEventDelay(now - task->mInsertionTime, now);
+ }
+#endif
+
+ PerformanceCounterState::Snapshot snapshot =
+ mPerformanceCounterState->RunnableWillRun(
+ task->GetPerformanceCounter(), now,
+ manager == mIdleTaskManager);
+
+ {
+ LogTask::Run log(task);
+ result = task->Run();
+ }
+
+ // Task itself should keep manager alive.
+ if (manager) {
+ manager->DidRunTask();
+ }
+
+ mPerformanceCounterState->RunnableDidRun(std::move(snapshot));
+ }
+
+ // Task itself should keep manager alive.
+ if (manager && result && manager->mTaskCount == 0) {
+ mTaskManagers.erase(manager);
+ }
+
+ task->mInProgress = false;
+
+ if (!result) {
+ // Presumably this task was interrupted, leave its dependencies
+ // unresolved and reinsert into the queue.
+ auto insertion =
+ mMainThreadTasks.insert(std::move(mCurrentTasksMT.top()));
+ MOZ_ASSERT(insertion.second);
+ task->mIterator = insertion.first;
+ manager->WillRunTask();
+ } else {
+ task->mCompleted = true;
+#ifdef DEBUG
+ task->mIsInGraph = false;
+#endif
+ // Clear dependencies to release references.
+ task->mDependencies.clear();
+
+ if (!mThreadableTasks.empty()) {
+ // Since this could have multiple dependencies thare are not
+ // restricted to the main thread. Let's wake up our thread pool.
+ // There is a cost to this, it's possible we will want to wake up
+ // only as many threads as we have unblocked tasks, but we currently
+ // have no way to determine that easily.
+ mThreadPoolCV.NotifyAll();
+ }
+ }
+
+ mCurrentTasksMT.pop();
+ return true;
+ }
+ }
+
+ mMayHaveMainThreadTask = false;
+ if (mIdleTaskManager) {
+ // We did not find a task to run. We still need to clear the cached idle
+ // deadline on our idle state, because that deadline was only relevant to
+ // the execution of this function. Had we found a task, we would have
+ // cleared the deadline before running that task.
+ mIdleTaskManager->State().ClearCachedIdleDeadline();
+ }
+ return false;
+}
+
+Task* TaskController::GetFinalDependency(Task* aTask) {
+ Task* nextTask;
+
+ while ((nextTask = aTask->GetHighestPriorityDependency())) {
+ aTask = nextTask;
+ }
+
+ return aTask;
+}
+
+void TaskController::MaybeInterruptTask(Task* aTask) {
+ mGraphMutex.AssertCurrentThreadOwns();
+
+ if (!aTask) {
+ return;
+ }
+
+ // This optimization prevents many slow lookups in long chains of similar
+ // priority.
+ if (!aTask->mDependencies.empty()) {
+ Task* firstDependency = aTask->mDependencies.begin()->get();
+ if (aTask->GetPriority() <= firstDependency->GetPriority() &&
+ !firstDependency->mCompleted &&
+ aTask->IsMainThreadOnly() == firstDependency->IsMainThreadOnly()) {
+ // This task has the same or a higher priority as one of its dependencies,
+ // never any need to interrupt.
+ return;
+ }
+ }
+
+ Task* finalDependency = GetFinalDependency(aTask);
+
+ if (finalDependency->mInProgress) {
+ // No need to wake anything, we can't schedule this task right now anyway.
+ return;
+ }
+
+ if (aTask->IsMainThreadOnly()) {
+ mMayHaveMainThreadTask = true;
+
+ EnsureMainThreadTasksScheduled();
+
+ if (mCurrentTasksMT.empty()) {
+ return;
+ }
+
+ // We could go through the steps above here and interrupt an off main
+ // thread task in case it has a lower priority.
+ if (!finalDependency->IsMainThreadOnly()) {
+ return;
+ }
+
+ if (mCurrentTasksMT.top()->GetPriority() < aTask->GetPriority()) {
+ mCurrentTasksMT.top()->RequestInterrupt(aTask->GetPriority());
+ }
+ } else {
+ Task* lowestPriorityTask = nullptr;
+ for (PoolThread& thread : mPoolThreads) {
+ if (!thread.mCurrentTask) {
+ mThreadPoolCV.Notify();
+ // There's a free thread, no need to interrupt anything.
+ return;
+ }
+
+ if (!lowestPriorityTask) {
+ lowestPriorityTask = thread.mCurrentTask.get();
+ continue;
+ }
+
+ // This should possibly select the lowest priority task which was started
+ // the latest. But for now we ignore that optimization.
+ // This also doesn't guarantee a task is interruptable, so that's an
+ // avenue for improvements as well.
+ if (lowestPriorityTask->GetPriority() > thread.mEffectiveTaskPriority) {
+ lowestPriorityTask = thread.mCurrentTask.get();
+ }
+ }
+
+ if (lowestPriorityTask->GetPriority() < aTask->GetPriority()) {
+ lowestPriorityTask->RequestInterrupt(aTask->GetPriority());
+ }
+
+ // We choose not to interrupt main thread tasks for tasks which may be
+ // executed off the main thread.
+ }
+}
+
+Task* TaskController::GetHighestPriorityMTTask() {
+ mGraphMutex.AssertCurrentThreadOwns();
+
+ if (!mMainThreadTasks.empty()) {
+ return mMainThreadTasks.begin()->get();
+ }
+ return nullptr;
+}
+
+void TaskController::EnsureMainThreadTasksScheduled() {
+ if (mObserver) {
+ mObserver->OnDispatchedEvent();
+ }
+ if (mExternalCondVar) {
+ mExternalCondVar->Notify();
+ }
+ mMainThreadCV.Notify();
+}
+
+void TaskController::ProcessUpdatedPriorityModifier(TaskManager* aManager) {
+ mGraphMutex.AssertCurrentThreadOwns();
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ int32_t modifier = aManager->mCurrentPriorityModifier;
+
+ std::vector<RefPtr<Task>> storedTasks;
+ // Find all relevant tasks.
+ for (auto iter = mMainThreadTasks.begin(); iter != mMainThreadTasks.end();) {
+ if ((*iter)->mTaskManager == aManager) {
+ storedTasks.push_back(*iter);
+ iter = mMainThreadTasks.erase(iter);
+ } else {
+ iter++;
+ }
+ }
+
+ // Reinsert found tasks with their new priorities.
+ for (RefPtr<Task>& ref : storedTasks) {
+ // Kept alive at first by the vector and then by mMainThreadTasks.
+ Task* task = ref;
+ task->mPriorityModifier = modifier;
+ auto insertion = mMainThreadTasks.insert(std::move(ref));
+ MOZ_ASSERT(insertion.second);
+ task->mIterator = insertion.first;
+ }
+}
+
+} // namespace mozilla
diff --git a/xpcom/threads/TaskController.h b/xpcom/threads/TaskController.h
new file mode 100644
index 0000000000..2efb45abcd
--- /dev/null
+++ b/xpcom/threads/TaskController.h
@@ -0,0 +1,415 @@
+/* -*- 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_TaskController_h
+#define mozilla_TaskController_h
+
+#include "mozilla/CondVar.h"
+#include "mozilla/IdlePeriodState.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/EventQueue.h"
+#include "nsISupportsImpl.h"
+#include "nsIEventTarget.h"
+
+#include <atomic>
+#include <memory>
+#include <vector>
+#include <set>
+#include <list>
+#include <stack>
+
+class nsIRunnable;
+class nsIThreadObserver;
+
+namespace mozilla {
+
+class Task;
+class TaskController;
+class PerformanceCounter;
+class PerformanceCounterState;
+
+const EventQueuePriority kDefaultPriorityValue = EventQueuePriority::Normal;
+
+// This file contains the core classes to access the Gecko scheduler. The
+// scheduler forms a graph of prioritize tasks, and is responsible for ensuring
+// the execution of tasks or their dependencies in order of inherited priority.
+//
+// The core class is the 'Task' class. The task class describes a single unit of
+// work. Users scheduling work implement this class and are required to
+// reimplement the 'Run' function in order to do work.
+//
+// The TaskManager class is reimplemented by users that require
+// the ability to reprioritize or suspend tasks.
+//
+// The TaskController is responsible for scheduling the work itself. The AddTask
+// function is used to schedule work. The ReprioritizeTask function may be used
+// to change the priority of a task already in the task graph, without
+// unscheduling it.
+
+// The TaskManager is the baseclass used to atomically manage a large set of
+// tasks. API users reimplementing TaskManager may reimplement a number of
+// functions that they may use to indicate to the scheduler changes in the state
+// for any tasks they manage. They may be used to reprioritize or suspend tasks
+// under their control, and will also be notified before and after tasks under
+// their control are executed. Their methods will only be called once per event
+// loop turn, however they may still incur some performance overhead. In
+// addition to this frequent reprioritizations may incur a significant
+// performance overhead and are discouraged. A TaskManager may currently only be
+// used to manage tasks that are bound to the Gecko Main Thread.
+class TaskManager {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TaskManager)
+
+ TaskManager() : mTaskCount(0) {}
+
+ // Subclasses implementing task manager will have this function called to
+ // determine whether their associated tasks are currently suspended. This
+ // will only be called once per iteration of the task queue, this means that
+ // suspension of tasks managed by a single TaskManager may be assumed to
+ // occur atomically.
+ virtual bool IsSuspended(const MutexAutoLock& aProofOfLock) { return false; }
+
+ // Subclasses may implement this in order to supply a priority adjustment
+ // to their managed tasks. This is called once per iteration of the task
+ // queue, and may be assumed to occur atomically for all managed tasks.
+ virtual int32_t GetPriorityModifierForEventLoopTurn(
+ const MutexAutoLock& aProofOfLock) {
+ return 0;
+ }
+
+ void DidQueueTask() { ++mTaskCount; }
+ // This is called when a managed task is about to be executed by the
+ // scheduler. Anyone reimplementing this should ensure to call the parent or
+ // decrement mTaskCount.
+ virtual void WillRunTask() { --mTaskCount; }
+ // This is called when a managed task has finished being executed by the
+ // scheduler.
+ virtual void DidRunTask() {}
+ uint32_t PendingTaskCount() { return mTaskCount; }
+
+ protected:
+ virtual ~TaskManager() {}
+
+ private:
+ friend class TaskController;
+
+ enum class IterationType { NOT_EVENT_LOOP_TURN, EVENT_LOOP_TURN };
+ bool UpdateCachesForCurrentIterationAndReportPriorityModifierChanged(
+ const MutexAutoLock& aProofOfLock, IterationType aIterationType);
+
+ bool mCurrentSuspended = false;
+ int32_t mCurrentPriorityModifier = 0;
+
+ std::atomic<uint32_t> mTaskCount;
+};
+
+// A Task is the the base class for any unit of work that may be scheduled.
+// Subclasses may specify their priority and whether they should be bound to
+// the Gecko Main thread. When not bound to the main thread tasks may be
+// executed on any available thread (including the main thread), but they may
+// also be executed in parallel to any other task they do not have a dependency
+// relationship with. Tasks will be run in order of object creation.
+class Task {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Task)
+
+ bool IsMainThreadOnly() { return mMainThreadOnly; }
+
+ // This returns the current task priority with its modifier applied.
+ uint32_t GetPriority() { return mPriority + mPriorityModifier; }
+ uint64_t GetSeqNo() { return mSeqNo; }
+
+ // Callee needs to assume this may be called on any thread.
+ // aInterruptPriority passes the priority of the higher priority task that
+ // is ready to be executed. The task may safely ignore this function, or
+ // interrupt any work being done. It may return 'false' from its run function
+ // in order to be run automatically in the future, or true if it will
+ // reschedule incomplete work manually.
+ virtual void RequestInterrupt(uint32_t aInterruptPriority) {}
+
+ // At the moment this -must- be called before the task is added to the
+ // controller. Calling this after tasks have been added to the controller
+ // results in undefined behavior!
+ // At submission, tasks must depend only on tasks managed by the same, or
+ // no idle manager.
+ void AddDependency(Task* aTask) {
+ MOZ_ASSERT(aTask);
+ MOZ_ASSERT(!mIsInGraph);
+ mDependencies.insert(aTask);
+ }
+
+ // This sets the TaskManager for the current task. Calling this after the
+ // task has been added to the TaskController results in undefined behavior.
+ void SetManager(TaskManager* aManager) {
+ MOZ_ASSERT(mMainThreadOnly);
+ MOZ_ASSERT(!mIsInGraph);
+ mTaskManager = aManager;
+ }
+ TaskManager* GetManager() { return mTaskManager; }
+
+ struct PriorityCompare {
+ bool operator()(const RefPtr<Task>& aTaskA,
+ const RefPtr<Task>& aTaskB) const {
+ uint32_t prioA = aTaskA->GetPriority();
+ uint32_t prioB = aTaskB->GetPriority();
+ return (prioA > prioB) ||
+ (prioA == prioB && (aTaskA->GetSeqNo() < aTaskB->GetSeqNo()));
+ }
+ };
+
+ // Tell the task about its idle deadline. Will only be called for
+ // tasks managed by an IdleTaskManager, right before the task runs.
+ virtual void SetIdleDeadline(TimeStamp aDeadline) {}
+
+ virtual PerformanceCounter* GetPerformanceCounter() const { return nullptr; }
+
+ // Get a name for this task. This returns false if the task has no name.
+ virtual bool GetName(nsACString& aName) { return false; }
+
+ protected:
+ Task(bool aMainThreadOnly,
+ uint32_t aPriority = static_cast<uint32_t>(kDefaultPriorityValue))
+ : mMainThreadOnly(aMainThreadOnly),
+ mSeqNo(sCurrentTaskSeqNo++),
+ mPriority(aPriority) {}
+
+ Task(bool aMainThreadOnly,
+ EventQueuePriority aPriority = kDefaultPriorityValue)
+ : mMainThreadOnly(aMainThreadOnly),
+ mSeqNo(sCurrentTaskSeqNo++),
+ mPriority(static_cast<uint32_t>(aPriority)) {}
+
+ virtual ~Task() {}
+
+ friend class TaskController;
+
+ // When this returns false, the task is considered incomplete and will be
+ // rescheduled at the current 'mPriority' level.
+ virtual bool Run() = 0;
+
+ private:
+ Task* GetHighestPriorityDependency();
+
+ // Iterator pointing to this task's position in
+ // mThreadableTasks/mMainThreadTasks if, and only if this task is currently
+ // scheduled to be executed. This allows fast access to the task's position
+ // in the set, allowing for fast removal.
+ // This is safe, and remains valid unless the task is removed from the set.
+ // See also iterator invalidation in:
+ // https://en.cppreference.com/w/cpp/container
+ //
+ // Or the spec:
+ // "All Associative Containers: The insert and emplace members shall not
+ // affect the validity of iterators and references to the container
+ // [26.2.6/9]" "All Associative Containers: The erase members shall invalidate
+ // only iterators and references to the erased elements [26.2.6/9]"
+ std::set<RefPtr<Task>, PriorityCompare>::iterator mIterator;
+ std::set<RefPtr<Task>, PriorityCompare> mDependencies;
+
+ RefPtr<TaskManager> mTaskManager;
+
+ // Access to these variables is protected by the GraphMutex.
+ bool mMainThreadOnly;
+ bool mCompleted = false;
+ bool mInProgress = false;
+#ifdef DEBUG
+ bool mIsInGraph = false;
+#endif
+
+ static std::atomic<uint64_t> sCurrentTaskSeqNo;
+ int64_t mSeqNo;
+ uint32_t mPriority;
+ // Modifier currently being applied to this task by its taskmanager.
+ int32_t mPriorityModifier = 0;
+#ifdef MOZ_GECKO_PROFILER
+ // Time this task was inserted into the task graph, this is used by the
+ // profiler.
+ mozilla::TimeStamp mInsertionTime;
+#endif
+};
+
+struct PoolThread {
+ PRThread* mThread;
+ RefPtr<Task> mCurrentTask;
+ // This may be higher than mCurrentTask's priority due to priority
+ // propagation. This is -only- valid when mCurrentTask != nullptr.
+ uint32_t mEffectiveTaskPriority;
+};
+
+// A task manager implementation for priority levels that should only
+// run during idle periods.
+class IdleTaskManager : public TaskManager {
+ public:
+ explicit IdleTaskManager(already_AddRefed<nsIIdlePeriod>&& aIdlePeriod)
+ : mIdlePeriodState(std::move(aIdlePeriod)) {}
+
+ IdlePeriodState& State() { return mIdlePeriodState; }
+
+ bool IsSuspended(const MutexAutoLock& aProofOfLock) override {
+ TimeStamp idleDeadline = State().GetCachedIdleDeadline();
+ return !idleDeadline;
+ }
+
+ private:
+ // Tracking of our idle state of various sorts.
+ IdlePeriodState mIdlePeriodState;
+};
+
+// The TaskController is the core class of the scheduler. It is used to
+// schedule tasks to be executed, as well as to reprioritize tasks that have
+// already been scheduled. The core functions to do this are AddTask and
+// ReprioritizeTask.
+class TaskController {
+ public:
+ TaskController()
+ : mGraphMutex("TaskController::mGraphMutex"),
+ mThreadPoolCV(mGraphMutex, "TaskController::mThreadPoolCV"),
+ mMainThreadCV(mGraphMutex, "TaskController::mMainThreadCV") {}
+
+ static TaskController* Get();
+
+ static bool Initialize();
+
+ void SetThreadObserver(nsIThreadObserver* aObserver) {
+ mObserver = aObserver;
+ }
+ void SetConditionVariable(CondVar* aExternalCondVar) {
+ mExternalCondVar = aExternalCondVar;
+ }
+
+ void SetIdleTaskManager(IdleTaskManager* aIdleTaskManager) {
+ mIdleTaskManager = aIdleTaskManager;
+ }
+ IdleTaskManager* GetIdleTaskManager() { return mIdleTaskManager.get(); }
+
+ // Initialization and shutdown code.
+ void SetPerformanceCounterState(
+ PerformanceCounterState* aPerformanceCounterState);
+
+ static void Shutdown();
+
+ // This adds a task to the TaskController graph.
+ // This may be called on any thread.
+ void AddTask(already_AddRefed<Task>&& aTask);
+
+ // This wait function is the theoretical function you would need if our main
+ // thread needs to also process OS messages or something along those lines.
+ void WaitForTaskOrMessage();
+
+ // This gets the next (highest priority) task that is only allowed to execute
+ // on the main thread.
+ void ExecuteNextTaskOnlyMainThread();
+
+ // Process all pending main thread tasks.
+ void ProcessPendingMTTask(bool aMayWait = false);
+
+ // This allows reprioritization of a task already in the task graph.
+ // This may be called on any thread.
+ void ReprioritizeTask(Task* aTask, uint32_t aPriority);
+
+ void DispatchRunnable(already_AddRefed<nsIRunnable>&& aRunnable,
+ uint32_t aPriority, TaskManager* aManager = nullptr);
+
+ nsIRunnable* GetRunnableForMTTask(bool aReallyWait);
+
+ bool HasMainThreadPendingTasks();
+
+ // Let users know whether the last main thread task runnable did work.
+ bool MTTaskRunnableProcessedTask() { return mMTTaskRunnableProcessedTask; }
+
+ private:
+ friend void ThreadFuncPoolThread(void* aIndex);
+
+ bool InitializeInternal();
+
+ void InitializeThreadPool();
+
+ // This gets the next (highest priority) task that is only allowed to execute
+ // on the main thread, if any, and executes it.
+ // Returns true if it succeeded.
+ bool ExecuteNextTaskOnlyMainThreadInternal(const MutexAutoLock& aProofOfLock);
+
+ // The guts of ExecuteNextTaskOnlyMainThreadInternal, which get idle handling
+ // wrapped around them. Returns whether a task actually ran.
+ bool DoExecuteNextTaskOnlyMainThreadInternal(
+ const MutexAutoLock& aProofOfLock);
+
+ Task* GetFinalDependency(Task* aTask);
+ void MaybeInterruptTask(Task* aTask);
+ Task* GetHighestPriorityMTTask();
+
+ void EnsureMainThreadTasksScheduled();
+
+ void ProcessUpdatedPriorityModifier(TaskManager* aManager);
+
+ void ShutdownThreadPoolInternal();
+ void ShutdownInternal();
+
+ void RunPoolThread();
+
+ static std::unique_ptr<TaskController> sSingleton;
+ static StaticMutex sSingletonMutex;
+
+ // This protects access to the task graph.
+ Mutex mGraphMutex;
+
+ // This protects thread pool initialization. We cannot do this from within
+ // the GraphMutex, since thread creation on Windows can generate events on
+ // the main thread that need to be handled.
+ Mutex mPoolInitializationMutex =
+ Mutex("TaskController::mPoolInitializationMutex");
+
+ CondVar mThreadPoolCV;
+ CondVar mMainThreadCV;
+
+ // Variables below are protected by mGraphMutex.
+
+ std::vector<PoolThread> mPoolThreads;
+ std::stack<RefPtr<Task>> mCurrentTasksMT;
+
+ // A list of all tasks ordered by priority.
+ std::set<RefPtr<Task>, Task::PriorityCompare> mThreadableTasks;
+ std::set<RefPtr<Task>, Task::PriorityCompare> mMainThreadTasks;
+
+ // TaskManagers currently active.
+ // We can use a raw pointer since tasks always hold on to their TaskManager.
+ std::set<TaskManager*> mTaskManagers;
+
+ // This ensures we keep running the main thread if we processed a task there.
+ bool mMayHaveMainThreadTask = true;
+ bool mShuttingDown = false;
+
+ // This stores whether the last main thread task runnable did work.
+ bool mMTTaskRunnableProcessedTask = false;
+
+ // Whether our thread pool is initialized. We use this currently to avoid
+ // starting the threads in processes where it's never used. This is protected
+ // by mPoolInitializationMutex.
+ bool mThreadPoolInitialized = false;
+
+ // Whether we have scheduled a runnable on the main thread event loop.
+ // This is used for nsIRunnable compatibility.
+ RefPtr<nsIRunnable> mMTProcessingRunnable;
+ RefPtr<nsIRunnable> mMTBlockingProcessingRunnable;
+
+ // XXX - Thread observer to notify when a new event has been dispatched
+ nsIThreadObserver* mObserver = nullptr;
+ // XXX - External condvar to notify when we have received an event
+ CondVar* mExternalCondVar = nullptr;
+ // Idle task manager so we can properly do idle state stuff.
+ RefPtr<IdleTaskManager> mIdleTaskManager;
+
+ // Our tracking of our performance counter and long task state,
+ // shared with nsThread.
+ PerformanceCounterState* mPerformanceCounterState = nullptr;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_TaskController_h
diff --git a/xpcom/threads/TaskDispatcher.h b/xpcom/threads/TaskDispatcher.h
new file mode 100644
index 0000000000..558221aa95
--- /dev/null
+++ b/xpcom/threads/TaskDispatcher.h
@@ -0,0 +1,301 @@
+/* -*- 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/. */
+
+#if !defined(TaskDispatcher_h_)
+# define TaskDispatcher_h_
+
+# include <queue>
+
+# include "mozilla/AbstractThread.h"
+# include "mozilla/Maybe.h"
+# include "mozilla/UniquePtr.h"
+# include "nsIDirectTaskDispatcher.h"
+# include "nsISupportsImpl.h"
+# include "nsTArray.h"
+# include "nsThreadUtils.h"
+
+namespace mozilla {
+
+class SimpleTaskQueue {
+ public:
+ SimpleTaskQueue() = default;
+ virtual ~SimpleTaskQueue() = default;
+
+ void AddTask(already_AddRefed<nsIRunnable> aRunnable) {
+ if (!mTasks) {
+ mTasks.emplace();
+ }
+ mTasks->push(std::move(aRunnable));
+ }
+
+ void DrainTasks() {
+ if (!mTasks) {
+ return;
+ }
+ auto& queue = mTasks.ref();
+ while (!queue.empty()) {
+ nsCOMPtr<nsIRunnable> r = std::move(queue.front());
+ queue.pop();
+ r->Run();
+ }
+ }
+
+ bool HaveTasks() const { return mTasks && !mTasks->empty(); }
+
+ private:
+ // We use a Maybe<> because (a) when used for DirectTasks it often doesn't get
+ // anything put into it, and (b) the std::queue implementation in GNU
+ // libstdc++ does two largish heap allocations when creating a new std::queue.
+ Maybe<std::queue<nsCOMPtr<nsIRunnable>>> mTasks;
+};
+
+/*
+ * A classic approach to cross-thread communication is to dispatch asynchronous
+ * runnables to perform updates on other threads. This generally works well, but
+ * there are sometimes reasons why we might want to delay the actual dispatch of
+ * these tasks until a specified moment. At present, this is primarily useful to
+ * ensure that mirrored state gets updated atomically - but there may be other
+ * applications as well.
+ *
+ * TaskDispatcher is a general abstract class that accepts tasks and dispatches
+ * them at some later point. These groups of tasks are per-target-thread, and
+ * contain separate queues for several kinds of tasks (see comments below). -
+ * "state change tasks" (which run first, and are intended to be used to update
+ * the value held by mirrors), and regular tasks, which are other arbitrary
+ * operations that the are gated to run after all the state changes have
+ * completed.
+ */
+class TaskDispatcher {
+ public:
+ TaskDispatcher() = default;
+ virtual ~TaskDispatcher() = default;
+
+ // Direct tasks are run directly (rather than dispatched asynchronously) when
+ // the tail dispatcher fires. A direct task may cause other tasks to be added
+ // to the tail dispatcher.
+ virtual void AddDirectTask(already_AddRefed<nsIRunnable> aRunnable) = 0;
+
+ // State change tasks are dispatched asynchronously always run before regular
+ // tasks. They are intended to be used to update the value held by mirrors
+ // before any other dispatched tasks are run on the target thread.
+ virtual void AddStateChangeTask(AbstractThread* aThread,
+ already_AddRefed<nsIRunnable> aRunnable) = 0;
+
+ // Regular tasks are dispatched asynchronously, and run after state change
+ // tasks.
+ virtual nsresult AddTask(AbstractThread* aThread,
+ already_AddRefed<nsIRunnable> aRunnable) = 0;
+
+ virtual nsresult DispatchTasksFor(AbstractThread* aThread) = 0;
+ virtual bool HasTasksFor(AbstractThread* aThread) = 0;
+ virtual void DrainDirectTasks() = 0;
+};
+
+/*
+ * AutoTaskDispatcher is a stack-scoped TaskDispatcher implementation that fires
+ * its queued tasks when it is popped off the stack.
+ */
+class AutoTaskDispatcher : public TaskDispatcher {
+ public:
+ explicit AutoTaskDispatcher(nsIDirectTaskDispatcher* aDirectTaskDispatcher,
+ bool aIsTailDispatcher = false)
+ : mDirectTaskDispatcher(aDirectTaskDispatcher),
+ mIsTailDispatcher(aIsTailDispatcher) {}
+
+ ~AutoTaskDispatcher() {
+ // Given that direct tasks may trigger other code that uses the tail
+ // dispatcher, it's better to avoid processing them in the tail dispatcher's
+ // destructor. So we require TailDispatchers to manually invoke
+ // DrainDirectTasks before the AutoTaskDispatcher gets destroyed. In truth,
+ // this is only necessary in the case where this AutoTaskDispatcher can be
+ // accessed by the direct tasks it dispatches (true for TailDispatchers, but
+ // potentially not true for other hypothetical AutoTaskDispatchers). Feel
+ // free to loosen this restriction to apply only to mIsTailDispatcher if a
+ // use-case requires it.
+ MOZ_ASSERT(!HaveDirectTasks());
+
+ for (size_t i = 0; i < mTaskGroups.Length(); ++i) {
+ DispatchTaskGroup(std::move(mTaskGroups[i]));
+ }
+ }
+
+ bool HaveDirectTasks() {
+ return mDirectTaskDispatcher && mDirectTaskDispatcher->HaveDirectTasks();
+ }
+
+ void DrainDirectTasks() override {
+ if (mDirectTaskDispatcher) {
+ mDirectTaskDispatcher->DrainDirectTasks();
+ }
+ }
+
+ void AddDirectTask(already_AddRefed<nsIRunnable> aRunnable) override {
+ MOZ_ASSERT(mDirectTaskDispatcher);
+ mDirectTaskDispatcher->DispatchDirectTask(std::move(aRunnable));
+ }
+
+ void AddStateChangeTask(AbstractThread* aThread,
+ already_AddRefed<nsIRunnable> aRunnable) override {
+ nsCOMPtr<nsIRunnable> r = aRunnable;
+ MOZ_RELEASE_ASSERT(r);
+ EnsureTaskGroup(aThread).mStateChangeTasks.AppendElement(r.forget());
+ }
+
+ nsresult AddTask(AbstractThread* aThread,
+ already_AddRefed<nsIRunnable> aRunnable) override {
+ nsCOMPtr<nsIRunnable> r = aRunnable;
+ MOZ_RELEASE_ASSERT(r);
+ // To preserve the event order, we need to append a new group if the last
+ // group is not targeted for |aThread|.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1318226&mark=0-3#c0
+ // for the details of the issue.
+ if (mTaskGroups.Length() == 0 ||
+ mTaskGroups.LastElement()->mThread != aThread) {
+ mTaskGroups.AppendElement(new PerThreadTaskGroup(aThread));
+ }
+
+ PerThreadTaskGroup& group = *mTaskGroups.LastElement();
+ group.mRegularTasks.AppendElement(r.forget());
+
+ return NS_OK;
+ }
+
+ bool HasTasksFor(AbstractThread* aThread) override {
+ return !!GetTaskGroup(aThread) ||
+ (aThread == AbstractThread::GetCurrent() && HaveDirectTasks());
+ }
+
+ nsresult DispatchTasksFor(AbstractThread* aThread) override {
+ nsresult rv = NS_OK;
+
+ // Dispatch all groups that match |aThread|.
+ for (size_t i = 0; i < mTaskGroups.Length(); ++i) {
+ if (mTaskGroups[i]->mThread == aThread) {
+ nsresult rv2 = DispatchTaskGroup(std::move(mTaskGroups[i]));
+
+ if (NS_WARN_IF(NS_FAILED(rv2)) && NS_SUCCEEDED(rv)) {
+ // We should try our best to call DispatchTaskGroup() as much as
+ // possible and return an error if any of DispatchTaskGroup() calls
+ // failed.
+ rv = rv2;
+ }
+
+ mTaskGroups.RemoveElementAt(i--);
+ }
+ }
+
+ return rv;
+ }
+
+ private:
+ struct PerThreadTaskGroup {
+ public:
+ explicit PerThreadTaskGroup(AbstractThread* aThread) : mThread(aThread) {
+ MOZ_COUNT_CTOR(PerThreadTaskGroup);
+ }
+
+ MOZ_COUNTED_DTOR(PerThreadTaskGroup)
+
+ RefPtr<AbstractThread> mThread;
+ nsTArray<nsCOMPtr<nsIRunnable>> mStateChangeTasks;
+ nsTArray<nsCOMPtr<nsIRunnable>> mRegularTasks;
+ };
+
+ class TaskGroupRunnable : public Runnable {
+ public:
+ explicit TaskGroupRunnable(UniquePtr<PerThreadTaskGroup>&& aTasks)
+ : Runnable("AutoTaskDispatcher::TaskGroupRunnable"),
+ mTasks(std::move(aTasks)) {}
+
+ NS_IMETHOD Run() override {
+ // State change tasks get run all together before any code is run, so
+ // that all state changes are made in an atomic unit.
+ for (size_t i = 0; i < mTasks->mStateChangeTasks.Length(); ++i) {
+ mTasks->mStateChangeTasks[i]->Run();
+ }
+
+ // Once the state changes have completed, drain any direct tasks
+ // generated by those state changes (i.e. watcher notification tasks).
+ // This needs to be outside the loop because we don't want to run code
+ // that might observe intermediate states.
+ MaybeDrainDirectTasks();
+
+ for (size_t i = 0; i < mTasks->mRegularTasks.Length(); ++i) {
+ mTasks->mRegularTasks[i]->Run();
+
+ // Scope direct tasks tightly to the task that generated them.
+ MaybeDrainDirectTasks();
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ void MaybeDrainDirectTasks() {
+ AbstractThread* currentThread = AbstractThread::GetCurrent();
+ if (currentThread && currentThread->MightHaveTailTasks()) {
+ currentThread->TailDispatcher().DrainDirectTasks();
+ }
+ }
+
+ UniquePtr<PerThreadTaskGroup> mTasks;
+ };
+
+ PerThreadTaskGroup& EnsureTaskGroup(AbstractThread* aThread) {
+ PerThreadTaskGroup* existing = GetTaskGroup(aThread);
+ if (existing) {
+ return *existing;
+ }
+
+ mTaskGroups.AppendElement(new PerThreadTaskGroup(aThread));
+ return *mTaskGroups.LastElement();
+ }
+
+ PerThreadTaskGroup* GetTaskGroup(AbstractThread* aThread) {
+ for (size_t i = 0; i < mTaskGroups.Length(); ++i) {
+ if (mTaskGroups[i]->mThread == aThread) {
+ return mTaskGroups[i].get();
+ }
+ }
+
+ // Not found.
+ return nullptr;
+ }
+
+ nsresult DispatchTaskGroup(UniquePtr<PerThreadTaskGroup> aGroup) {
+ RefPtr<AbstractThread> thread = aGroup->mThread;
+
+ AbstractThread::DispatchReason reason =
+ mIsTailDispatcher ? AbstractThread::TailDispatch
+ : AbstractThread::NormalDispatch;
+ nsCOMPtr<nsIRunnable> r = new TaskGroupRunnable(std::move(aGroup));
+ return thread->Dispatch(r.forget(), reason);
+ }
+
+ // Task groups, organized by thread.
+ nsTArray<UniquePtr<PerThreadTaskGroup>> mTaskGroups;
+
+ nsCOMPtr<nsIDirectTaskDispatcher> mDirectTaskDispatcher;
+ // True if this TaskDispatcher represents the tail dispatcher for the thread
+ // upon which it runs.
+ const bool mIsTailDispatcher;
+};
+
+// Little utility class to allow declaring AutoTaskDispatcher as a default
+// parameter for methods that take a TaskDispatcher&.
+template <typename T>
+class PassByRef {
+ public:
+ PassByRef() = default;
+ operator T&() { return mVal; }
+
+ private:
+ T mVal;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/xpcom/threads/TaskQueue.cpp b/xpcom/threads/TaskQueue.cpp
new file mode 100644
index 0000000000..3236137bec
--- /dev/null
+++ b/xpcom/threads/TaskQueue.cpp
@@ -0,0 +1,233 @@
+/* -*- 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/TaskQueue.h"
+
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+
+TaskQueue::TaskQueue(already_AddRefed<nsIEventTarget> aTarget,
+ const char* aName, bool aRequireTailDispatch)
+ : AbstractThread(aRequireTailDispatch),
+ mTarget(aTarget),
+ mQueueMonitor("TaskQueue::Queue"),
+ mTailDispatcher(nullptr),
+ mIsRunning(false),
+ mIsShutdown(false),
+ mName(aName) {}
+
+TaskQueue::TaskQueue(already_AddRefed<nsIEventTarget> aTarget,
+ bool aSupportsTailDispatch)
+ : TaskQueue(std::move(aTarget), "Unnamed", aSupportsTailDispatch) {}
+
+TaskQueue::~TaskQueue() {
+ // No one is referencing this TaskQueue anymore, meaning no tasks can be
+ // pending as all Runner hold a reference to this TaskQueue.
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(TaskQueue, AbstractThread, nsIDirectTaskDispatcher);
+
+TaskDispatcher& TaskQueue::TailDispatcher() {
+ MOZ_ASSERT(IsCurrentThreadIn());
+ MOZ_ASSERT(mTailDispatcher);
+ return *mTailDispatcher;
+}
+
+// Note aRunnable is passed by ref to support conditional ownership transfer.
+// See Dispatch() in TaskQueue.h for more details.
+nsresult TaskQueue::DispatchLocked(nsCOMPtr<nsIRunnable>& aRunnable,
+ uint32_t aFlags, DispatchReason aReason) {
+ mQueueMonitor.AssertCurrentThreadOwns();
+ if (mIsShutdown) {
+ return NS_ERROR_FAILURE;
+ }
+
+ AbstractThread* currentThread;
+ if (aReason != TailDispatch && (currentThread = GetCurrent()) &&
+ RequiresTailDispatch(currentThread) &&
+ currentThread->IsTailDispatcherAvailable()) {
+ MOZ_ASSERT(aFlags == NS_DISPATCH_NORMAL,
+ "Tail dispatch doesn't support flags");
+ return currentThread->TailDispatcher().AddTask(this, aRunnable.forget());
+ }
+
+ LogRunnable::LogDispatch(aRunnable);
+ mTasks.push({std::move(aRunnable), aFlags});
+
+ if (mIsRunning) {
+ return NS_OK;
+ }
+ RefPtr<nsIRunnable> runner(new Runner(this));
+ nsresult rv = mTarget->Dispatch(runner.forget(), aFlags);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch runnable to run TaskQueue");
+ return rv;
+ }
+ mIsRunning = true;
+
+ return NS_OK;
+}
+
+void TaskQueue::AwaitIdle() {
+ MonitorAutoLock mon(mQueueMonitor);
+ AwaitIdleLocked();
+}
+
+void TaskQueue::AwaitIdleLocked() {
+ // Make sure there are no tasks for this queue waiting in the caller's tail
+ // dispatcher.
+ MOZ_ASSERT_IF(AbstractThread::GetCurrent(),
+ !AbstractThread::GetCurrent()->HasTailTasksFor(this));
+
+ mQueueMonitor.AssertCurrentThreadOwns();
+ MOZ_ASSERT(mIsRunning || mTasks.empty());
+ while (mIsRunning) {
+ mQueueMonitor.Wait();
+ }
+}
+
+void TaskQueue::AwaitShutdownAndIdle() {
+ MOZ_ASSERT(!IsCurrentThreadIn());
+ // Make sure there are no tasks for this queue waiting in the caller's tail
+ // dispatcher.
+ MOZ_ASSERT_IF(AbstractThread::GetCurrent(),
+ !AbstractThread::GetCurrent()->HasTailTasksFor(this));
+
+ MonitorAutoLock mon(mQueueMonitor);
+ while (!mIsShutdown) {
+ mQueueMonitor.Wait();
+ }
+ AwaitIdleLocked();
+}
+
+RefPtr<ShutdownPromise> TaskQueue::BeginShutdown() {
+ // Dispatch any tasks for this queue waiting in the caller's tail dispatcher,
+ // since this is the last opportunity to do so.
+ if (AbstractThread* currentThread = AbstractThread::GetCurrent()) {
+ currentThread->TailDispatchTasksFor(this);
+ }
+ MonitorAutoLock mon(mQueueMonitor);
+ mIsShutdown = true;
+ RefPtr<ShutdownPromise> p = mShutdownPromise.Ensure(__func__);
+ MaybeResolveShutdown();
+ mon.NotifyAll();
+ return p;
+}
+
+bool TaskQueue::IsEmpty() {
+ MonitorAutoLock mon(mQueueMonitor);
+ return mTasks.empty();
+}
+
+bool TaskQueue::IsCurrentThreadIn() const {
+ bool in = mRunningThread == PR_GetCurrentThread();
+ return in;
+}
+
+nsresult TaskQueue::Runner::Run() {
+ TaskStruct event;
+ {
+ MonitorAutoLock mon(mQueue->mQueueMonitor);
+ MOZ_ASSERT(mQueue->mIsRunning);
+ if (mQueue->mTasks.empty()) {
+ mQueue->mIsRunning = false;
+ mQueue->MaybeResolveShutdown();
+ mon.NotifyAll();
+ return NS_OK;
+ }
+ event = std::move(mQueue->mTasks.front());
+ mQueue->mTasks.pop();
+ }
+ MOZ_ASSERT(event.event);
+
+ // Note that dropping the queue monitor before running the task, and
+ // taking the monitor again after the task has run ensures we have memory
+ // fences enforced. This means that if the object we're calling wasn't
+ // designed to be threadsafe, it will be, provided we're only calling it
+ // in this task queue.
+ {
+ AutoTaskGuard g(mQueue);
+ SerialEventTargetGuard tg(mQueue);
+ {
+ LogRunnable::Run log(event.event);
+
+ event.event->Run();
+
+ // Drop the reference to event. The event will hold a reference to the
+ // object it's calling, and we don't want to keep it alive, it may be
+ // making assumptions what holds references to it. This is especially
+ // the case if the object is waiting for us to shutdown, so that it
+ // can shutdown (like in the MediaDecoderStateMachine's SHUTDOWN case).
+ event.event = nullptr;
+ }
+ }
+
+ {
+ MonitorAutoLock mon(mQueue->mQueueMonitor);
+ if (mQueue->mTasks.empty()) {
+ // No more events to run. Exit the task runner.
+ mQueue->mIsRunning = false;
+ mQueue->MaybeResolveShutdown();
+ mon.NotifyAll();
+ return NS_OK;
+ }
+ }
+
+ // There's at least one more event that we can run. Dispatch this Runner
+ // to the target again to ensure it runs again. Note that we don't just
+ // run in a loop here so that we don't hog the target. This means we may
+ // run on another thread next time, but we rely on the memory fences from
+ // mQueueMonitor for thread safety of non-threadsafe tasks.
+ nsresult rv;
+ {
+ MonitorAutoLock mon(mQueue->mQueueMonitor);
+ rv = mQueue->mTarget->Dispatch(
+ this, mQueue->mTasks.front().flags | NS_DISPATCH_AT_END);
+ }
+ if (NS_FAILED(rv)) {
+ // Failed to dispatch, shutdown!
+ MonitorAutoLock mon(mQueue->mQueueMonitor);
+ mQueue->mIsRunning = false;
+ mQueue->mIsShutdown = true;
+ mQueue->MaybeResolveShutdown();
+ mon.NotifyAll();
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIDirectTaskDispatcher
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+TaskQueue::DispatchDirectTask(already_AddRefed<nsIRunnable> aEvent) {
+ if (!IsCurrentThreadIn()) {
+ return NS_ERROR_FAILURE;
+ }
+ mDirectTasks.AddTask(std::move(aEvent));
+ return NS_OK;
+}
+
+NS_IMETHODIMP TaskQueue::DrainDirectTasks() {
+ if (!IsCurrentThreadIn()) {
+ return NS_ERROR_FAILURE;
+ }
+ mDirectTasks.DrainTasks();
+ return NS_OK;
+}
+
+NS_IMETHODIMP TaskQueue::HaveDirectTasks(bool* aValue) {
+ if (!IsCurrentThreadIn()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aValue = mDirectTasks.HaveTasks();
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/xpcom/threads/TaskQueue.h b/xpcom/threads/TaskQueue.h
new file mode 100644
index 0000000000..5b9837ecbd
--- /dev/null
+++ b/xpcom/threads/TaskQueue.h
@@ -0,0 +1,224 @@
+/* -*- 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 TaskQueue_h_
+#define TaskQueue_h_
+
+#include <queue>
+
+#include "mozilla/AbstractThread.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TaskDispatcher.h"
+#include "nsIDirectTaskDispatcher.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+
+typedef MozPromise<bool, bool, false> ShutdownPromise;
+
+// Abstracts executing runnables in order on an arbitrary event target. The
+// runnables dispatched to the TaskQueue will be executed in the order in which
+// they're received, and are guaranteed to not be executed concurrently.
+// They may be executed on different threads, and a memory barrier is used
+// to make this threadsafe for objects that aren't already threadsafe.
+//
+// Note, since a TaskQueue can also be converted to an nsIEventTarget using
+// WrapAsEventTarget() its possible to construct a hierarchy of TaskQueues.
+// Consider these three TaskQueues:
+//
+// TQ1 dispatches to the main thread
+// TQ2 dispatches to TQ1
+// TQ3 dispatches to TQ1
+//
+// This ensures there is only ever a single runnable from the entire chain on
+// the main thread. It also ensures that TQ2 and TQ3 only have a single
+// runnable in TQ1 at any time.
+//
+// This arrangement lets you prioritize work by dispatching runnables directly
+// to TQ1. You can issue many runnables for important work. Meanwhile the TQ2
+// and TQ3 work will always execute at most one runnable and then yield.
+//
+// A TaskQueue does not require explicit shutdown, however it provides a
+// BeginShutdown() method that places TaskQueue in a shut down state and returns
+// a promise that gets resolved once all pending tasks have completed
+class TaskQueue : public AbstractThread, public nsIDirectTaskDispatcher {
+ class EventTargetWrapper;
+
+ public:
+ explicit TaskQueue(already_AddRefed<nsIEventTarget> aTarget,
+ bool aSupportsTailDispatch = false);
+
+ TaskQueue(already_AddRefed<nsIEventTarget> aTarget, const char* aName,
+ bool aSupportsTailDispatch = false);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIDIRECTTASKDISPATCHER
+
+ TaskDispatcher& TailDispatcher() override;
+
+ NS_IMETHOD Dispatch(already_AddRefed<nsIRunnable> aEvent,
+ uint32_t aFlags) override {
+ nsCOMPtr<nsIRunnable> runnable = aEvent;
+ {
+ MonitorAutoLock mon(mQueueMonitor);
+ return DispatchLocked(/* passed by ref */ runnable, aFlags,
+ NormalDispatch);
+ }
+ // If the ownership of |r| is not transferred in DispatchLocked() due to
+ // dispatch failure, it will be deleted here outside the lock. We do so
+ // since the destructor of the runnable might access TaskQueue and result
+ // in deadlocks.
+ }
+
+ [[nodiscard]] nsresult Dispatch(
+ already_AddRefed<nsIRunnable> aRunnable,
+ DispatchReason aReason = NormalDispatch) override {
+ nsCOMPtr<nsIRunnable> r = aRunnable;
+ {
+ MonitorAutoLock mon(mQueueMonitor);
+ return DispatchLocked(/* passed by ref */ r, NS_DISPATCH_NORMAL, aReason);
+ }
+ // If the ownership of |r| is not transferred in DispatchLocked() due to
+ // dispatch failure, it will be deleted here outside the lock. We do so
+ // since the destructor of the runnable might access TaskQueue and result
+ // in deadlocks.
+ }
+
+ // So we can access nsIEventTarget::Dispatch(nsIRunnable*, uint32_t aFlags)
+ using nsIEventTarget::Dispatch;
+
+ // Puts the queue in a shutdown state and returns immediately. The queue will
+ // remain alive at least until all the events are drained, because the Runners
+ // hold a strong reference to the task queue, and one of them is always held
+ // by the target event queue when the task queue is non-empty.
+ //
+ // The returned promise is resolved when the queue goes empty.
+ RefPtr<ShutdownPromise> BeginShutdown();
+
+ // Blocks until all task finish executing.
+ void AwaitIdle();
+
+ // Blocks until the queue is flagged for shutdown and all tasks have finished
+ // executing.
+ void AwaitShutdownAndIdle();
+
+ bool IsEmpty();
+
+ // Returns true if the current thread is currently running a Runnable in
+ // the task queue.
+ bool IsCurrentThreadIn() const override;
+ using nsISerialEventTarget::IsOnCurrentThread;
+
+ protected:
+ virtual ~TaskQueue();
+
+ // Blocks until all task finish executing. Called internally by methods
+ // that need to wait until the task queue is idle.
+ // mQueueMonitor must be held.
+ void AwaitIdleLocked();
+
+ nsresult DispatchLocked(nsCOMPtr<nsIRunnable>& aRunnable, uint32_t aFlags,
+ DispatchReason aReason = NormalDispatch);
+
+ void MaybeResolveShutdown() {
+ mQueueMonitor.AssertCurrentThreadOwns();
+ if (mIsShutdown && !mIsRunning) {
+ mShutdownPromise.ResolveIfExists(true, __func__);
+ mTarget = nullptr;
+ }
+ }
+
+ nsCOMPtr<nsIEventTarget> mTarget;
+
+ // Monitor that protects the queue and mIsRunning;
+ Monitor mQueueMonitor;
+
+ typedef struct TaskStruct {
+ nsCOMPtr<nsIRunnable> event;
+ uint32_t flags;
+ } TaskStruct;
+
+ // Queue of tasks to run.
+ std::queue<TaskStruct> mTasks;
+
+ // The thread currently running the task queue. We store a reference
+ // to this so that IsCurrentThreadIn() can tell if the current thread
+ // is the thread currently running in the task queue.
+ //
+ // This may be read on any thread, but may only be written on mRunningThread.
+ // The thread can't die while we're running in it, and we only use it for
+ // pointer-comparison with the current thread anyway - so we make it atomic
+ // and don't refcount it.
+ Atomic<PRThread*> mRunningThread;
+
+ // RAII class that gets instantiated for each dispatched task.
+ class AutoTaskGuard {
+ public:
+ explicit AutoTaskGuard(TaskQueue* aQueue)
+ : mQueue(aQueue), mLastCurrentThread(nullptr) {
+ // NB: We don't hold the lock to aQueue here. Don't do anything that
+ // might require it.
+ MOZ_ASSERT(!mQueue->mTailDispatcher);
+ mTaskDispatcher.emplace(aQueue,
+ /* aIsTailDispatcher = */ true);
+ mQueue->mTailDispatcher = mTaskDispatcher.ptr();
+
+ mLastCurrentThread = sCurrentThreadTLS.get();
+ sCurrentThreadTLS.set(aQueue);
+
+ MOZ_ASSERT(mQueue->mRunningThread == nullptr);
+ mQueue->mRunningThread = PR_GetCurrentThread();
+ }
+
+ ~AutoTaskGuard() {
+ mTaskDispatcher->DrainDirectTasks();
+ mTaskDispatcher.reset();
+
+ MOZ_ASSERT(mQueue->mRunningThread == PR_GetCurrentThread());
+ mQueue->mRunningThread = nullptr;
+
+ sCurrentThreadTLS.set(mLastCurrentThread);
+ mQueue->mTailDispatcher = nullptr;
+ }
+
+ private:
+ Maybe<AutoTaskDispatcher> mTaskDispatcher;
+ TaskQueue* mQueue;
+ AbstractThread* mLastCurrentThread;
+ };
+
+ TaskDispatcher* mTailDispatcher;
+
+ // True if we've dispatched an event to the target to execute events from
+ // the queue.
+ bool mIsRunning;
+
+ // True if we've started our shutdown process.
+ bool mIsShutdown;
+ MozPromiseHolder<ShutdownPromise> mShutdownPromise;
+
+ // The name of this TaskQueue. Useful when debugging dispatch failures.
+ const char* const mName;
+
+ SimpleTaskQueue mDirectTasks;
+
+ class Runner : public Runnable {
+ public:
+ explicit Runner(TaskQueue* aQueue)
+ : Runnable("TaskQueue::Runner"), mQueue(aQueue) {}
+ NS_IMETHOD Run() override;
+
+ private:
+ RefPtr<TaskQueue> mQueue;
+ };
+};
+
+} // namespace mozilla
+
+#endif // TaskQueue_h_
diff --git a/xpcom/threads/ThreadBound.h b/xpcom/threads/ThreadBound.h
new file mode 100644
index 0000000000..4d5e0088b5
--- /dev/null
+++ b/xpcom/threads/ThreadBound.h
@@ -0,0 +1,143 @@
+/* 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/. */
+
+// A class for values only accessible from a single designated thread.
+
+#ifndef mozilla_ThreadBound_h
+#define mozilla_ThreadBound_h
+
+#include "mozilla/Atomics.h"
+#include "prthread.h"
+
+#include <type_traits>
+
+namespace mozilla {
+
+template <typename T>
+class ThreadBound;
+
+namespace detail {
+
+template <bool Condition, typename T>
+struct AddConstIf {
+ using type = T;
+};
+
+template <typename T>
+struct AddConstIf<true, T> {
+ using type = typename std::add_const<T>::type;
+};
+
+} // namespace detail
+
+// A ThreadBound<T> is a T that can only be accessed by a specific
+// thread. To enforce this rule, the inner T is only accessible
+// through a non-copyable, immovable accessor object.
+// Given a ThreadBound<T> threadBoundData, it can be accessed like so:
+//
+// auto innerData = threadBoundData.Access();
+// innerData->DoStuff();
+//
+// Trying to access a ThreadBound<T> from a different thread will
+// trigger a MOZ_DIAGNOSTIC_ASSERT.
+// The encapsulated T is constructed during the construction of the
+// enclosing ThreadBound<T> by forwarding all of the latter's
+// constructor parameters to the former. A newly constructed
+// ThreadBound<T> is bound to the thread it's constructed in. It's
+// possible to rebind the data to some otherThread by calling
+//
+// threadBoundData.Transfer(otherThread);
+//
+// on the thread that threadBoundData is currently bound to, as long
+// as it's not currently being accessed. (Trying to rebind from
+// another thread or while an accessor exists will trigger an
+// assertion.)
+//
+// Note: A ThreadBound<T> may be destructed from any thread, not just
+// its designated thread at the time the destructor is invoked.
+template <typename T>
+class ThreadBound final {
+ public:
+ template <typename... Args>
+ explicit ThreadBound(Args&&... aArgs)
+ : mData(std::forward<Args>(aArgs)...),
+ mThread(PR_GetCurrentThread()),
+ mAccessCount(0) {}
+
+ ~ThreadBound() { AssertIsNotCurrentlyAccessed(); }
+
+ void Transfer(const PRThread* const aDest) {
+ AssertIsCorrectThread();
+ AssertIsNotCurrentlyAccessed();
+ mThread = aDest;
+ }
+
+ private:
+ T mData;
+
+ // This member is (potentially) accessed by multiple threads and is
+ // thus the first point of synchronization between them.
+ Atomic<const PRThread*, ReleaseAcquire> mThread;
+
+ // In order to support nested accesses (e.g. from different stack
+ // frames) it's necessary to maintain a counter of the existing
+ // accessor. Since it's possible to access a const ThreadBound, the
+ // counter is mutable. It's atomic because accessing it synchronizes
+ // access to mData (see comment in Accessor's constructor).
+ using AccessCountType = Atomic<int, ReleaseAcquire>;
+ mutable AccessCountType mAccessCount;
+
+ public:
+ template <bool IsConst>
+ class MOZ_STACK_CLASS Accessor final {
+ using DataType = typename detail::AddConstIf<IsConst, T>::type;
+
+ public:
+ explicit Accessor(
+ typename detail::AddConstIf<IsConst, ThreadBound>::type& aThreadBound)
+ : mData(aThreadBound.mData), mAccessCount(aThreadBound.mAccessCount) {
+ aThreadBound.AssertIsCorrectThread();
+
+ // This load/store serves as a memory fence that guards mData
+ // against accesses that would trip the thread assertion.
+ // (Otherwise one of the loads in the caller's instruction
+ // stream might be scheduled before the assertion.)
+ ++mAccessCount;
+ }
+
+ Accessor(const Accessor&) = delete;
+ Accessor(Accessor&&) = delete;
+ Accessor& operator=(const Accessor&) = delete;
+ Accessor& operator=(Accessor&&) = delete;
+
+ ~Accessor() { --mAccessCount; }
+
+ DataType* operator->() { return &mData; }
+
+ private:
+ DataType& mData;
+ AccessCountType& mAccessCount;
+ };
+
+ auto Access() { return Accessor<false>{*this}; }
+
+ auto Access() const { return Accessor<true>{*this}; }
+
+ private:
+ bool IsCorrectThread() const { return mThread == PR_GetCurrentThread(); }
+
+ bool IsNotCurrentlyAccessed() const { return mAccessCount == 0; }
+
+#define MOZ_DEFINE_THREAD_BOUND_ASSERT(predicate) \
+ void Assert##predicate() const { MOZ_DIAGNOSTIC_ASSERT(predicate()); }
+
+ MOZ_DEFINE_THREAD_BOUND_ASSERT(IsCorrectThread)
+ MOZ_DEFINE_THREAD_BOUND_ASSERT(IsNotCurrentlyAccessed)
+
+#undef MOZ_DEFINE_THREAD_BOUND_ASSERT
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ThreadBound_h
diff --git a/xpcom/threads/ThreadDelay.cpp b/xpcom/threads/ThreadDelay.cpp
new file mode 100644
index 0000000000..1c38e25510
--- /dev/null
+++ b/xpcom/threads/ThreadDelay.cpp
@@ -0,0 +1,38 @@
+/* -*- 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 "ThreadDelay.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ChaosMode.h"
+
+#if defined(XP_WIN)
+# include <windows.h>
+#else
+# include <unistd.h>
+#endif
+
+namespace mozilla {
+
+void DelayForChaosMode(ChaosFeature aFeature,
+ const uint32_t aMicrosecondLimit) {
+ if (!ChaosMode::isActive(aFeature)) {
+ return;
+ }
+
+ MOZ_ASSERT(aMicrosecondLimit <= 1000);
+#if defined(XP_WIN)
+ // Windows doesn't support sleeping at less than millisecond resolution.
+ // We could spin here, or we could just sleep for one millisecond.
+ // Sleeping for a full millisecond causes heavy delays, so we don't do
+ // anything here for now until we have found a good way to sleep more
+ // precisely here.
+#else
+ const uint32_t duration = ChaosMode::randomUint32LessThan(aMicrosecondLimit);
+ ::usleep(duration);
+#endif
+}
+
+} // namespace mozilla
diff --git a/xpcom/threads/ThreadDelay.h b/xpcom/threads/ThreadDelay.h
new file mode 100644
index 0000000000..5cfc116e4d
--- /dev/null
+++ b/xpcom/threads/ThreadDelay.h
@@ -0,0 +1,16 @@
+/* -*- 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/ChaosMode.h"
+
+namespace mozilla {
+
+// Sleep for a random number of microseconds less than aMicrosecondLimit
+// if aFeature is enabled. On Windows, the sleep will always be 1 millisecond
+// due to platform limitations.
+void DelayForChaosMode(ChaosFeature aFeature, const uint32_t aMicrosecondLimit);
+
+} // namespace mozilla
diff --git a/xpcom/threads/ThreadEventQueue.cpp b/xpcom/threads/ThreadEventQueue.cpp
new file mode 100644
index 0000000000..22d8ec3a70
--- /dev/null
+++ b/xpcom/threads/ThreadEventQueue.cpp
@@ -0,0 +1,262 @@
+/* -*- 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/ThreadEventQueue.h"
+#include "mozilla/EventQueue.h"
+
+#include "GeckoProfiler.h"
+#include "LeakRefPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIThreadInternal.h"
+#include "nsThreadUtils.h"
+#include "nsThread.h"
+#include "ThreadEventTarget.h"
+#include "mozilla/TaskController.h"
+
+using namespace mozilla;
+
+class ThreadEventQueue::NestedSink : public ThreadTargetSink {
+ public:
+ NestedSink(EventQueue* aQueue, ThreadEventQueue* aOwner)
+ : mQueue(aQueue), mOwner(aOwner) {}
+
+ bool PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
+ EventQueuePriority aPriority) final {
+ return mOwner->PutEventInternal(std::move(aEvent), aPriority, this);
+ }
+
+ void Disconnect(const MutexAutoLock& aProofOfLock) final { mQueue = nullptr; }
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ if (mQueue) {
+ return mQueue->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ return 0;
+ }
+
+ private:
+ friend class ThreadEventQueue;
+
+ // This is a non-owning reference. It must live at least until Disconnect is
+ // called to clear it out.
+ EventQueue* mQueue;
+ RefPtr<ThreadEventQueue> mOwner;
+};
+
+ThreadEventQueue::ThreadEventQueue(UniquePtr<EventQueue> aQueue,
+ bool aIsMainThread)
+ : mBaseQueue(std::move(aQueue)),
+ mLock("ThreadEventQueue"),
+ mEventsAvailable(mLock, "EventsAvail"),
+ mIsMainThread(aIsMainThread) {
+ if (aIsMainThread) {
+ TaskController::Get()->SetConditionVariable(&mEventsAvailable);
+ }
+}
+
+ThreadEventQueue::~ThreadEventQueue() { MOZ_ASSERT(mNestedQueues.IsEmpty()); }
+
+bool ThreadEventQueue::PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
+ EventQueuePriority aPriority) {
+ return PutEventInternal(std::move(aEvent), aPriority, nullptr);
+}
+
+bool ThreadEventQueue::PutEventInternal(already_AddRefed<nsIRunnable>&& aEvent,
+ EventQueuePriority aPriority,
+ NestedSink* aSink) {
+ // We want to leak the reference when we fail to dispatch it, so that
+ // we won't release the event in a wrong thread.
+ LeakRefPtr<nsIRunnable> event(std::move(aEvent));
+ nsCOMPtr<nsIThreadObserver> obs;
+
+ {
+ // Check if the runnable wants to override the passed-in priority.
+ // Do this outside the lock, so runnables implemented in JS can QI
+ // (and possibly GC) outside of the lock.
+ if (mIsMainThread) {
+ auto* e = event.get(); // can't do_QueryInterface on LeakRefPtr.
+ if (nsCOMPtr<nsIRunnablePriority> runnablePrio = do_QueryInterface(e)) {
+ uint32_t prio = nsIRunnablePriority::PRIORITY_NORMAL;
+ runnablePrio->GetPriority(&prio);
+ if (prio == nsIRunnablePriority::PRIORITY_HIGH) {
+ aPriority = EventQueuePriority::High;
+ } else if (prio == nsIRunnablePriority::PRIORITY_INPUT_HIGH) {
+ aPriority = EventQueuePriority::InputHigh;
+ } else if (prio == nsIRunnablePriority::PRIORITY_MEDIUMHIGH) {
+ aPriority = EventQueuePriority::MediumHigh;
+ } else if (prio == nsIRunnablePriority::PRIORITY_DEFERRED_TIMERS) {
+ aPriority = EventQueuePriority::DeferredTimers;
+ } else if (prio == nsIRunnablePriority::PRIORITY_IDLE) {
+ aPriority = EventQueuePriority::Idle;
+ }
+ }
+ }
+
+ MutexAutoLock lock(mLock);
+
+ if (mEventsAreDoomed) {
+ return false;
+ }
+
+ if (aSink) {
+ if (!aSink->mQueue) {
+ return false;
+ }
+
+ aSink->mQueue->PutEvent(event.take(), aPriority, lock);
+ } else {
+ mBaseQueue->PutEvent(event.take(), aPriority, lock);
+ }
+
+ mEventsAvailable.Notify();
+
+ // Make sure to grab the observer before dropping the lock, otherwise the
+ // event that we just placed into the queue could run and eventually delete
+ // this nsThread before the calling thread is scheduled again. We would then
+ // crash while trying to access a dead nsThread.
+ obs = mObserver;
+ }
+
+ if (obs) {
+ obs->OnDispatchedEvent();
+ }
+
+ return true;
+}
+
+already_AddRefed<nsIRunnable> ThreadEventQueue::GetEvent(
+ bool aMayWait, mozilla::TimeDuration* aLastEventDelay) {
+ nsCOMPtr<nsIRunnable> event;
+ {
+ // Scope for lock. When we are about to return, we will exit this
+ // scope so we can do some work after releasing the lock but
+ // before returning.
+ MutexAutoLock lock(mLock);
+
+ for (;;) {
+ const bool noNestedQueue = mNestedQueues.IsEmpty();
+ if (noNestedQueue) {
+ event = mBaseQueue->GetEvent(lock, aLastEventDelay);
+ } else {
+ // We always get events from the topmost queue when there are nested
+ // queues.
+ event =
+ mNestedQueues.LastElement().mQueue->GetEvent(lock, aLastEventDelay);
+ }
+
+ if (event) {
+ break;
+ }
+
+ // No runnable available. Sleep waiting for one if if we're supposed to.
+ // Otherwise just go ahead and return null.
+ if (!aMayWait) {
+ break;
+ }
+
+ AUTO_PROFILER_LABEL("ThreadEventQueue::GetEvent::Wait", IDLE);
+ mEventsAvailable.Wait();
+ }
+ }
+
+ return event.forget();
+}
+
+bool ThreadEventQueue::HasPendingEvent() {
+ MutexAutoLock lock(mLock);
+
+ // We always get events from the topmost queue when there are nested queues.
+ if (mNestedQueues.IsEmpty()) {
+ return mBaseQueue->HasReadyEvent(lock);
+ } else {
+ return mNestedQueues.LastElement().mQueue->HasReadyEvent(lock);
+ }
+}
+
+bool ThreadEventQueue::ShutdownIfNoPendingEvents() {
+ MutexAutoLock lock(mLock);
+ if (mNestedQueues.IsEmpty() && mBaseQueue->IsEmpty(lock)) {
+ mEventsAreDoomed = true;
+ return true;
+ }
+ return false;
+}
+
+already_AddRefed<nsISerialEventTarget> ThreadEventQueue::PushEventQueue() {
+ auto queue = MakeUnique<EventQueue>();
+ RefPtr<NestedSink> sink = new NestedSink(queue.get(), this);
+ RefPtr<ThreadEventTarget> eventTarget =
+ new ThreadEventTarget(sink, NS_IsMainThread());
+
+ MutexAutoLock lock(mLock);
+
+ mNestedQueues.AppendElement(NestedQueueItem(std::move(queue), eventTarget));
+ return eventTarget.forget();
+}
+
+void ThreadEventQueue::PopEventQueue(nsIEventTarget* aTarget) {
+ MutexAutoLock lock(mLock);
+
+ MOZ_ASSERT(!mNestedQueues.IsEmpty());
+
+ NestedQueueItem& item = mNestedQueues.LastElement();
+
+ MOZ_ASSERT(aTarget == item.mEventTarget);
+
+ // Disconnect the event target that will be popped.
+ item.mEventTarget->Disconnect(lock);
+
+ EventQueue* prevQueue =
+ mNestedQueues.Length() == 1
+ ? mBaseQueue.get()
+ : mNestedQueues[mNestedQueues.Length() - 2].mQueue.get();
+
+ // Move events from the old queue to the new one.
+ nsCOMPtr<nsIRunnable> event;
+ TimeDuration delay;
+ while ((event = item.mQueue->GetEvent(lock, &delay))) {
+ // preserve the event delay so far
+ prevQueue->PutEvent(event.forget(), EventQueuePriority::Normal, lock,
+ &delay);
+ }
+
+ mNestedQueues.RemoveLastElement();
+}
+
+size_t ThreadEventQueue::SizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t n = 0;
+
+ n += mBaseQueue->SizeOfIncludingThis(aMallocSizeOf);
+
+ n += mNestedQueues.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto& queue : mNestedQueues) {
+ n += queue.mEventTarget->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ return SynchronizedEventQueue::SizeOfExcludingThis(aMallocSizeOf) + n;
+}
+
+already_AddRefed<nsIThreadObserver> ThreadEventQueue::GetObserver() {
+ MutexAutoLock lock(mLock);
+ return do_AddRef(mObserver);
+}
+
+already_AddRefed<nsIThreadObserver> ThreadEventQueue::GetObserverOnThread() {
+ return do_AddRef(mObserver);
+}
+
+void ThreadEventQueue::SetObserver(nsIThreadObserver* aObserver) {
+ MutexAutoLock lock(mLock);
+ mObserver = aObserver;
+ if (NS_IsMainThread()) {
+ TaskController::Get()->SetThreadObserver(aObserver);
+ }
+}
+
+ThreadEventQueue::NestedQueueItem::NestedQueueItem(
+ UniquePtr<EventQueue> aQueue, ThreadEventTarget* aEventTarget)
+ : mQueue(std::move(aQueue)), mEventTarget(aEventTarget) {}
diff --git a/xpcom/threads/ThreadEventQueue.h b/xpcom/threads/ThreadEventQueue.h
new file mode 100644
index 0000000000..995441e43c
--- /dev/null
+++ b/xpcom/threads/ThreadEventQueue.h
@@ -0,0 +1,88 @@
+/* -*- 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_ThreadEventQueue_h
+#define mozilla_ThreadEventQueue_h
+
+#include "mozilla/EventQueue.h"
+#include "mozilla/CondVar.h"
+#include "mozilla/SynchronizedEventQueue.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+
+class nsIEventTarget;
+class nsISerialEventTarget;
+class nsIThreadObserver;
+
+namespace mozilla {
+
+class EventQueue;
+class ThreadEventTarget;
+
+// A ThreadEventQueue implements normal monitor-style synchronization over the
+// EventQueue. It also implements PushEventQueue and PopEventQueue for workers
+// (see the documentation below for an explanation of those). All threads use a
+// ThreadEventQueue as their event queue. Although for the main thread this
+// simply forwards events to the TaskController.
+class ThreadEventQueue final : public SynchronizedEventQueue {
+ public:
+ explicit ThreadEventQueue(UniquePtr<EventQueue> aQueue,
+ bool aIsMainThread = false);
+
+ bool PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
+ EventQueuePriority aPriority) final;
+
+ already_AddRefed<nsIRunnable> GetEvent(
+ bool aMayWait, mozilla::TimeDuration* aLastEventDelay = nullptr) final;
+ bool HasPendingEvent() final;
+
+ bool ShutdownIfNoPendingEvents() final;
+
+ void Disconnect(const MutexAutoLock& aProofOfLock) final {}
+
+ already_AddRefed<nsISerialEventTarget> PushEventQueue() final;
+ void PopEventQueue(nsIEventTarget* aTarget) final;
+
+ already_AddRefed<nsIThreadObserver> GetObserver() final;
+ already_AddRefed<nsIThreadObserver> GetObserverOnThread() final;
+ void SetObserver(nsIThreadObserver* aObserver) final;
+
+ Mutex& MutexRef() { return mLock; }
+
+ size_t SizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const override;
+
+ private:
+ class NestedSink;
+
+ virtual ~ThreadEventQueue();
+
+ bool PutEventInternal(already_AddRefed<nsIRunnable>&& aEvent,
+ EventQueuePriority aPriority, NestedSink* aQueue);
+
+ UniquePtr<EventQueue> mBaseQueue;
+
+ struct NestedQueueItem {
+ UniquePtr<EventQueue> mQueue;
+ RefPtr<ThreadEventTarget> mEventTarget;
+
+ NestedQueueItem(UniquePtr<EventQueue> aQueue,
+ ThreadEventTarget* aEventTarget);
+ };
+
+ nsTArray<NestedQueueItem> mNestedQueues;
+
+ Mutex mLock;
+ CondVar mEventsAvailable;
+
+ bool mEventsAreDoomed = false;
+ nsCOMPtr<nsIThreadObserver> mObserver;
+ bool mIsMainThread;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ThreadEventQueue_h
diff --git a/xpcom/threads/ThreadEventTarget.cpp b/xpcom/threads/ThreadEventTarget.cpp
new file mode 100644
index 0000000000..75a109cceb
--- /dev/null
+++ b/xpcom/threads/ThreadEventTarget.cpp
@@ -0,0 +1,135 @@
+/* -*- 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 "ThreadEventTarget.h"
+#include "mozilla/ThreadEventQueue.h"
+
+#include "LeakRefPtr.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/TimeStamp.h"
+#include "nsComponentManagerUtils.h"
+#include "nsITimer.h"
+#include "nsThreadManager.h"
+#include "nsThreadSyncDispatch.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOMPrivate.h" // for gXPCOMThreadsShutDown
+#include "ThreadDelay.h"
+
+#ifdef MOZ_TASK_TRACER
+# include "GeckoTaskTracer.h"
+# include "TracedTaskCommon.h"
+using namespace mozilla::tasktracer;
+#endif
+
+using namespace mozilla;
+
+ThreadEventTarget::ThreadEventTarget(ThreadTargetSink* aSink,
+ bool aIsMainThread)
+ : mSink(aSink), mIsMainThread(aIsMainThread) {
+ mThread = PR_GetCurrentThread();
+}
+
+void ThreadEventTarget::SetCurrentThread() { mThread = PR_GetCurrentThread(); }
+
+void ThreadEventTarget::ClearCurrentThread() { mThread = nullptr; }
+
+NS_IMPL_ISUPPORTS(ThreadEventTarget, nsIEventTarget, nsISerialEventTarget)
+
+NS_IMETHODIMP
+ThreadEventTarget::DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags) {
+ return Dispatch(do_AddRef(aRunnable), aFlags);
+}
+
+NS_IMETHODIMP
+ThreadEventTarget::Dispatch(already_AddRefed<nsIRunnable> aEvent,
+ uint32_t aFlags) {
+ // We want to leak the reference when we fail to dispatch it, so that
+ // we won't release the event in a wrong thread.
+ LeakRefPtr<nsIRunnable> event(std::move(aEvent));
+ if (NS_WARN_IF(!event)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (gXPCOMThreadsShutDown && !mIsMainThread) {
+ NS_ASSERTION(false, "Failed Dispatch after xpcom-shutdown-threads");
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+#ifdef MOZ_TASK_TRACER
+ nsCOMPtr<nsIRunnable> tracedRunnable = CreateTracedRunnable(event.take());
+ (static_cast<TracedRunnable*>(tracedRunnable.get()))->DispatchTask();
+ // XXX tracedRunnable will always leaked when we fail to disptch.
+ event = tracedRunnable.forget();
+#endif
+
+ LogRunnable::LogDispatch(event.get());
+
+ if (aFlags & DISPATCH_SYNC) {
+ nsCOMPtr<nsIEventTarget> current = GetCurrentEventTarget();
+ if (NS_WARN_IF(!current)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // XXX we should be able to do something better here... we should
+ // be able to monitor the slot occupied by this event and use
+ // that to tell us when the event has been processed.
+
+ RefPtr<nsThreadSyncDispatch> wrapper =
+ new nsThreadSyncDispatch(current.forget(), event.take());
+ bool success = mSink->PutEvent(do_AddRef(wrapper),
+ EventQueuePriority::Normal); // hold a ref
+ if (!success) {
+ // PutEvent leaked the wrapper runnable object on failure, so we
+ // explicitly release this object once for that. Note that this
+ // object will be released again soon because it exits the scope.
+ wrapper.get()->Release();
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Allows waiting; ensure no locks are held that would deadlock us!
+ SpinEventLoopUntil(
+ [&, wrapper]() -> bool { return !wrapper->IsPending(); });
+
+ return NS_OK;
+ }
+
+ NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL || aFlags == NS_DISPATCH_AT_END,
+ "unexpected dispatch flags");
+ if (!mSink->PutEvent(event.take(), EventQueuePriority::Normal)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ // Delay to encourage the receiving task to run before we do work.
+ DelayForChaosMode(ChaosFeature::TaskDispatching, 1000);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThreadEventTarget::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
+ uint32_t aDelayMs) {
+ nsCOMPtr<nsIRunnable> event = aEvent;
+ NS_ENSURE_TRUE(!!aDelayMs, NS_ERROR_UNEXPECTED);
+
+ RefPtr<DelayedRunnable> r =
+ new DelayedRunnable(do_AddRef(this), event.forget(), aDelayMs);
+ nsresult rv = r->Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+ThreadEventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread) {
+ *aIsOnCurrentThread = IsOnCurrentThread();
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(bool)
+ThreadEventTarget::IsOnCurrentThreadInfallible() {
+ // This method is only going to be called if `mThread` is null, which
+ // only happens when the thread has exited the event loop. Therefore, when
+ // we are called, we can never be on this thread.
+ return false;
+}
diff --git a/xpcom/threads/ThreadEventTarget.h b/xpcom/threads/ThreadEventTarget.h
new file mode 100644
index 0000000000..1b298521cd
--- /dev/null
+++ b/xpcom/threads/ThreadEventTarget.h
@@ -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/. */
+
+#ifndef mozilla_ThreadEventTarget_h
+#define mozilla_ThreadEventTarget_h
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/SynchronizedEventQueue.h" // for ThreadTargetSink
+#include "nsISerialEventTarget.h"
+
+namespace mozilla {
+
+// ThreadEventTarget handles the details of posting an event to a thread. It can
+// be used with any ThreadTargetSink implementation.
+class ThreadEventTarget final : public nsISerialEventTarget {
+ public:
+ ThreadEventTarget(ThreadTargetSink* aSink, bool aIsMainThread);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIEVENTTARGET_FULL
+
+ // Disconnects the target so that it can no longer post events.
+ void Disconnect(const MutexAutoLock& aProofOfLock) {
+ mSink->Disconnect(aProofOfLock);
+ }
+
+ // Sets the thread for which IsOnCurrentThread returns true to the current
+ // thread.
+ void SetCurrentThread();
+ // Call ClearCurrentThread() before the PRThread is deleted on thread join.
+ void ClearCurrentThread();
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t n = 0;
+ if (mSink) {
+ n += mSink->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ return aMallocSizeOf(this) + n;
+ }
+
+ private:
+ ~ThreadEventTarget() = default;
+
+ RefPtr<ThreadTargetSink> mSink;
+ bool mIsMainThread;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ThreadEventTarget_h
diff --git a/xpcom/threads/ThreadLocalVariables.cpp b/xpcom/threads/ThreadLocalVariables.cpp
new file mode 100644
index 0000000000..606c0c6384
--- /dev/null
+++ b/xpcom/threads/ThreadLocalVariables.cpp
@@ -0,0 +1,16 @@
+/* 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/ThreadLocal.h"
+
+// This variable is used to ensure creating new URI doesn't put us in an
+// infinite loop
+MOZ_THREAD_LOCAL(uint32_t) gTlsURLRecursionCount;
+
+void InitThreadLocalVariables() {
+ if (!gTlsURLRecursionCount.init()) {
+ MOZ_CRASH("Could not init gTlsURLRecursionCount");
+ }
+ gTlsURLRecursionCount.set(0);
+}
diff --git a/xpcom/threads/ThrottledEventQueue.cpp b/xpcom/threads/ThrottledEventQueue.cpp
new file mode 100644
index 0000000000..578d7a4cd7
--- /dev/null
+++ b/xpcom/threads/ThrottledEventQueue.cpp
@@ -0,0 +1,439 @@
+/* -*- 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 "ThrottledEventQueue.h"
+
+#include "mozilla/Atomics.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/CondVar.h"
+#include "mozilla/EventQueue.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Unused.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+
+namespace {} // anonymous namespace
+
+// The ThrottledEventQueue is designed with inner and outer objects:
+//
+// XPCOM code base event target
+// | |
+// v v
+// +-------+ +--------+
+// | Outer | +-->|executor|
+// +-------+ | +--------+
+// | | |
+// | +-------+ |
+// +-->| Inner |<--+
+// +-------+
+//
+// Client code references the outer nsIEventTarget which in turn references
+// an inner object, which actually holds the queue of runnables.
+//
+// Whenever the queue is non-empty (and not paused), it keeps an "executor"
+// runnable dispatched to the base event target. Each time the executor is run,
+// it draws the next event from Inner's queue and runs it. If that queue has
+// more events, the executor is dispatched to the base again.
+//
+// The executor holds a strong reference to the Inner object. This means that if
+// the outer object is dereferenced and destroyed, the Inner object will remain
+// live for as long as the executor exists - that is, until the Inner's queue is
+// empty.
+//
+// A Paused ThrottledEventQueue does not enqueue an executor when new events are
+// added. Any executor previously queued on the base event target draws no
+// events from a Paused ThrottledEventQueue, and returns without re-enqueueing
+// itself. Since there is no executor keeping the Inner object alive until its
+// queue is empty, dropping a Paused ThrottledEventQueue may drop the Inner
+// while it still owns events. This is the correct behavior: if there are no
+// references to it, it will never be Resumed, and thus it will never dispatch
+// events again.
+//
+// Resuming a ThrottledEventQueue must dispatch an executor, so calls to Resume
+// are fallible for the same reasons as calls to Dispatch.
+//
+// The xpcom shutdown process drains the main thread's event queue several
+// times, so if a ThrottledEventQueue is being driven by the main thread, it
+// should get emptied out by the time we reach the "eventq shutdown" phase.
+class ThrottledEventQueue::Inner final : public nsISupports {
+ // The runnable which is dispatched to the underlying base target. Since
+ // we only execute one event at a time we just re-use a single instance
+ // of this class while there are events left in the queue.
+ class Executor final : public Runnable, public nsIRunnablePriority {
+ // The Inner whose runnables we execute. mInner->mExecutor points
+ // to this executor, forming a reference loop.
+ RefPtr<Inner> mInner;
+
+ ~Executor() = default;
+
+ public:
+ explicit Executor(Inner* aInner)
+ : Runnable("ThrottledEventQueue::Inner::Executor"), mInner(aInner) {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_IMETHODIMP
+ Run() override {
+ mInner->ExecuteRunnable();
+ return NS_OK;
+ }
+
+ NS_IMETHODIMP
+ GetPriority(uint32_t* aPriority) override {
+ *aPriority = mInner->mPriority;
+ return NS_OK;
+ }
+
+#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+ NS_IMETHODIMP
+ GetName(nsACString& aName) override { return mInner->CurrentName(aName); }
+#endif
+ };
+
+ mutable Mutex mMutex;
+ mutable CondVar mIdleCondVar;
+
+ // As-of-yet unexecuted runnables queued on this ThrottledEventQueue.
+ //
+ // Used from any thread; protected by mMutex. Signals mIdleCondVar when
+ // emptied.
+ EventQueueSized<64> mEventQueue;
+
+ // The event target we dispatch our events (actually, just our Executor) to.
+ //
+ // Written only during construction. Readable by any thread without locking.
+ nsCOMPtr<nsISerialEventTarget> mBaseTarget;
+
+ // The Executor that we dispatch to mBaseTarget to draw runnables from our
+ // queue. mExecutor->mInner points to this Inner, forming a reference loop.
+ //
+ // Used from any thread; protected by mMutex.
+ nsCOMPtr<nsIRunnable> mExecutor;
+
+ const char* mName;
+
+ const uint32_t mPriority;
+
+ // True if this queue is currently paused.
+ // Used from any thread; protected by mMutex.
+ bool mIsPaused;
+
+ explicit Inner(nsISerialEventTarget* aBaseTarget, const char* aName,
+ uint32_t aPriority)
+ : mMutex("ThrottledEventQueue"),
+ mIdleCondVar(mMutex, "ThrottledEventQueue:Idle"),
+ mBaseTarget(aBaseTarget),
+ mName(aName),
+ mPriority(aPriority),
+ mIsPaused(false) {
+ MOZ_ASSERT(mName, "Must pass a valid name!");
+ }
+
+ ~Inner() {
+#ifdef DEBUG
+ MutexAutoLock lock(mMutex);
+
+ // As long as an executor exists, it had better keep us alive, since it's
+ // going to call ExecuteRunnable on us.
+ MOZ_ASSERT(!mExecutor);
+
+ // If we have any events in our queue, there should be an executor queued
+ // for them, and that should have kept us alive. The exception is that, if
+ // we're paused, we don't enqueue an executor.
+ MOZ_ASSERT(mEventQueue.IsEmpty(lock) || IsPaused(lock));
+
+ // Some runnables are only safe to drop on the main thread, so if our queue
+ // isn't empty, we'd better be on the main thread.
+ MOZ_ASSERT_IF(!mEventQueue.IsEmpty(lock), NS_IsMainThread());
+#endif
+ }
+
+ // Make sure an executor has been queued on our base target. If we already
+ // have one, do nothing; otherwise, create and dispatch it.
+ nsresult EnsureExecutor(MutexAutoLock& lock) {
+ if (mExecutor) return NS_OK;
+
+ // Note, this creates a ref cycle keeping the inner alive
+ // until the queue is drained.
+ mExecutor = new Executor(this);
+ nsresult rv = mBaseTarget->Dispatch(mExecutor, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mExecutor = nullptr;
+ return rv;
+ }
+
+ return NS_OK;
+ }
+
+ nsresult CurrentName(nsACString& aName) {
+ nsCOMPtr<nsIRunnable> event;
+
+#ifdef DEBUG
+ bool currentThread = false;
+ mBaseTarget->IsOnCurrentThread(&currentThread);
+ MOZ_ASSERT(currentThread);
+#endif
+
+ {
+ MutexAutoLock lock(mMutex);
+ event = mEventQueue.PeekEvent(lock);
+ // It is possible that mEventQueue wasn't empty when the executor
+ // was added to the queue, but someone processed events from mEventQueue
+ // before the executor, this is why mEventQueue is empty here
+ if (!event) {
+ aName.AssignLiteral("no runnables left in the ThrottledEventQueue");
+ return NS_OK;
+ }
+ }
+
+ if (nsCOMPtr<nsINamed> named = do_QueryInterface(event)) {
+ nsresult rv = named->GetName(aName);
+ return rv;
+ }
+
+ aName.AssignASCII(mName);
+ return NS_OK;
+ }
+
+ void ExecuteRunnable() {
+ // Any thread
+ nsCOMPtr<nsIRunnable> event;
+
+#ifdef DEBUG
+ bool currentThread = false;
+ mBaseTarget->IsOnCurrentThread(&currentThread);
+ MOZ_ASSERT(currentThread);
+#endif
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ // Normally, a paused queue doesn't dispatch any executor, but we might
+ // have been paused after the executor was already in flight. There's no
+ // way to yank the executor out of the base event target, so we just check
+ // for a paused queue here and return without running anything. We'll
+ // create a new executor when we're resumed.
+ if (IsPaused(lock)) {
+ // Note, this breaks a ref cycle.
+ mExecutor = nullptr;
+ return;
+ }
+
+ // We only dispatch an executor runnable when we know there is something
+ // in the queue, so this should never fail.
+ event = mEventQueue.GetEvent(lock);
+ MOZ_ASSERT(event);
+
+ // If there are more events in the queue, then dispatch the next
+ // executor. We do this now, before running the event, because
+ // the event might spin the event loop and we don't want to stall
+ // the queue.
+ if (mEventQueue.HasReadyEvent(lock)) {
+ // Dispatch the next base target runnable to attempt to execute
+ // the next throttled event. We must do this before executing
+ // the event in case the event spins the event loop.
+ MOZ_ALWAYS_SUCCEEDS(
+ mBaseTarget->Dispatch(mExecutor, NS_DISPATCH_NORMAL));
+ }
+
+ // Otherwise the queue is empty and we can stop dispatching the
+ // executor.
+ else {
+ // Break the Executor::mInner / Inner::mExecutor reference loop.
+ mExecutor = nullptr;
+ mIdleCondVar.NotifyAll();
+ }
+ }
+
+ // Execute the event now that we have unlocked.
+ LogRunnable::Run log(event);
+ Unused << event->Run();
+
+ // To cover the event's destructor code in the LogRunnable log
+ event = nullptr;
+ }
+
+ public:
+ static already_AddRefed<Inner> Create(nsISerialEventTarget* aBaseTarget,
+ const char* aName, uint32_t aPriority) {
+ MOZ_ASSERT(NS_IsMainThread());
+ // FIXME: This assertion only worked when `sCurrentShutdownPhase` was not
+ // being updated.
+ // MOZ_ASSERT(ClearOnShutdown_Internal::sCurrentShutdownPhase ==
+ // ShutdownPhase::NotInShutdown);
+
+ RefPtr<Inner> ref = new Inner(aBaseTarget, aName, aPriority);
+ return ref.forget();
+ }
+
+ bool IsEmpty() const {
+ // Any thread
+ return Length() == 0;
+ }
+
+ uint32_t Length() const {
+ // Any thread
+ MutexAutoLock lock(mMutex);
+ return mEventQueue.Count(lock);
+ }
+
+ already_AddRefed<nsIRunnable> GetEvent() {
+ MutexAutoLock lock(mMutex);
+ return mEventQueue.GetEvent(lock);
+ }
+
+ void AwaitIdle() const {
+ // Any thread, except the main thread or our base target. Blocking the
+ // main thread is forbidden. Blocking the base target is guaranteed to
+ // produce a deadlock.
+ MOZ_ASSERT(!NS_IsMainThread());
+#ifdef DEBUG
+ bool onBaseTarget = false;
+ Unused << mBaseTarget->IsOnCurrentThread(&onBaseTarget);
+ MOZ_ASSERT(!onBaseTarget);
+#endif
+
+ MutexAutoLock lock(mMutex);
+ while (mExecutor || IsPaused(lock)) {
+ mIdleCondVar.Wait();
+ }
+ }
+
+ bool IsPaused() const {
+ MutexAutoLock lock(mMutex);
+ return IsPaused(lock);
+ }
+
+ bool IsPaused(const MutexAutoLock& aProofOfLock) const { return mIsPaused; }
+
+ nsresult SetIsPaused(bool aIsPaused) {
+ MutexAutoLock lock(mMutex);
+
+ // If we will be unpaused, and we have events in our queue, make sure we
+ // have an executor queued on the base event target to run them. Do this
+ // before we actually change mIsPaused, since this is fallible.
+ if (!aIsPaused && !mEventQueue.IsEmpty(lock)) {
+ nsresult rv = EnsureExecutor(lock);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ mIsPaused = aIsPaused;
+ return NS_OK;
+ }
+
+ nsresult DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) {
+ // Any thread
+ nsCOMPtr<nsIRunnable> r = aEvent;
+ return Dispatch(r.forget(), aFlags);
+ }
+
+ nsresult Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags) {
+ MOZ_ASSERT(aFlags == NS_DISPATCH_NORMAL || aFlags == NS_DISPATCH_AT_END);
+
+ // Any thread
+ MutexAutoLock lock(mMutex);
+
+ if (!IsPaused(lock)) {
+ // Make sure we have an executor in flight to process events. This is
+ // fallible, so do it first. Our lock will prevent the executor from
+ // accessing the event queue before we add the event below.
+ nsresult rv = EnsureExecutor(lock);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // Only add the event to the underlying queue if are able to
+ // dispatch to our base target.
+ nsCOMPtr<nsIRunnable> event(aEvent);
+ LogRunnable::LogDispatch(event);
+ mEventQueue.PutEvent(event.forget(), EventQueuePriority::Normal, lock);
+ return NS_OK;
+ }
+
+ nsresult DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
+ uint32_t aDelay) {
+ // The base target may implement this, but we don't. Always fail
+ // to provide consistent behavior.
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ bool IsOnCurrentThread() { return mBaseTarget->IsOnCurrentThread(); }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+};
+
+NS_IMPL_ISUPPORTS(ThrottledEventQueue::Inner, nsISupports);
+
+NS_IMPL_ISUPPORTS_INHERITED(ThrottledEventQueue::Inner::Executor, Runnable,
+ nsIRunnablePriority)
+
+NS_IMPL_ISUPPORTS(ThrottledEventQueue, ThrottledEventQueue, nsIEventTarget,
+ nsISerialEventTarget);
+
+ThrottledEventQueue::ThrottledEventQueue(already_AddRefed<Inner> aInner)
+ : mInner(aInner) {
+ MOZ_ASSERT(mInner);
+}
+
+already_AddRefed<ThrottledEventQueue> ThrottledEventQueue::Create(
+ nsISerialEventTarget* aBaseTarget, const char* aName, uint32_t aPriority) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aBaseTarget);
+
+ RefPtr<Inner> inner = Inner::Create(aBaseTarget, aName, aPriority);
+
+ RefPtr<ThrottledEventQueue> ref = new ThrottledEventQueue(inner.forget());
+ return ref.forget();
+}
+
+bool ThrottledEventQueue::IsEmpty() const { return mInner->IsEmpty(); }
+
+uint32_t ThrottledEventQueue::Length() const { return mInner->Length(); }
+
+// Get the next runnable from the queue
+already_AddRefed<nsIRunnable> ThrottledEventQueue::GetEvent() {
+ return mInner->GetEvent();
+}
+
+void ThrottledEventQueue::AwaitIdle() const { return mInner->AwaitIdle(); }
+
+nsresult ThrottledEventQueue::SetIsPaused(bool aIsPaused) {
+ return mInner->SetIsPaused(aIsPaused);
+}
+
+bool ThrottledEventQueue::IsPaused() const { return mInner->IsPaused(); }
+
+NS_IMETHODIMP
+ThrottledEventQueue::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) {
+ return mInner->DispatchFromScript(aEvent, aFlags);
+}
+
+NS_IMETHODIMP
+ThrottledEventQueue::Dispatch(already_AddRefed<nsIRunnable> aEvent,
+ uint32_t aFlags) {
+ return mInner->Dispatch(std::move(aEvent), aFlags);
+}
+
+NS_IMETHODIMP
+ThrottledEventQueue::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
+ uint32_t aFlags) {
+ return mInner->DelayedDispatch(std::move(aEvent), aFlags);
+}
+
+NS_IMETHODIMP
+ThrottledEventQueue::IsOnCurrentThread(bool* aResult) {
+ *aResult = mInner->IsOnCurrentThread();
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(bool)
+ThrottledEventQueue::IsOnCurrentThreadInfallible() {
+ return mInner->IsOnCurrentThread();
+}
+
+} // namespace mozilla
diff --git a/xpcom/threads/ThrottledEventQueue.h b/xpcom/threads/ThrottledEventQueue.h
new file mode 100644
index 0000000000..cf37a10a6d
--- /dev/null
+++ b/xpcom/threads/ThrottledEventQueue.h
@@ -0,0 +1,118 @@
+/* -*- 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/. */
+
+// nsIEventTarget wrapper for throttling event dispatch.
+
+#ifndef mozilla_ThrottledEventQueue_h
+#define mozilla_ThrottledEventQueue_h
+
+#include "nsISerialEventTarget.h"
+
+#define NS_THROTTLEDEVENTQUEUE_IID \
+ { \
+ 0x8f3cf7dc, 0xfc14, 0x4ad5, { \
+ 0x9f, 0xd5, 0xdb, 0x79, 0xbc, 0xe6, 0xd5, 0x08 \
+ } \
+ }
+
+namespace mozilla {
+
+// A ThrottledEventQueue is an event target that can be used to throttle
+// events being dispatched to another base target. It maintains its
+// own queue of events and only dispatches one at a time to the wrapped
+// target. This can be used to avoid flooding the base target.
+//
+// Flooding is avoided via a very simple principle. Runnables dispatched
+// to the ThrottledEventQueue are only dispatched to the base target
+// one at a time. Only once that runnable has executed will we dispatch
+// the next runnable to the base target. This in effect makes all
+// runnables passing through the ThrottledEventQueue yield to other work
+// on the base target.
+//
+// ThrottledEventQueue keeps runnables waiting to be dispatched to the
+// base in its own internal queue. Code can query the length of this
+// queue using IsEmpty() and Length(). Further, code implement back
+// pressure by checking the depth of the queue and deciding to stop
+// issuing runnables if they see the ThrottledEventQueue is backed up.
+// Code running on other threads could even use AwaitIdle() to block
+// all operation until the ThrottledEventQueue drains.
+//
+// Note, this class is similar to TaskQueue, but also differs in a few
+// ways. First, it is a very simple nsIEventTarget implementation. It
+// does not use the AbstractThread API.
+//
+// In addition, ThrottledEventQueue currently dispatches its next
+// runnable to the base target *before* running the current event. This
+// allows the event code to spin the event loop without stalling the
+// ThrottledEventQueue. In contrast, TaskQueue only dispatches its next
+// runnable after running the current event. That approach is necessary
+// for TaskQueue in order to work with thread pool targets.
+//
+// So, if you are targeting a thread pool you probably want a TaskQueue.
+// If you are targeting a single thread or other non-concurrent event
+// target, you probably want a ThrottledEventQueue.
+//
+// If you drop a ThrottledEventQueue while its queue still has events to be run,
+// they will continue to be dispatched as usual to the base. Only once the last
+// event has run will all the ThrottledEventQueue's memory be freed.
+class ThrottledEventQueue final : public nsISerialEventTarget {
+ class Inner;
+ RefPtr<Inner> mInner;
+
+ explicit ThrottledEventQueue(already_AddRefed<Inner> aInner);
+ ~ThrottledEventQueue() = default;
+
+ public:
+ // Create a ThrottledEventQueue for the given target.
+ static already_AddRefed<ThrottledEventQueue> Create(
+ nsISerialEventTarget* aBaseTarget, const char* aName,
+ uint32_t aPriority = nsIRunnablePriority::PRIORITY_NORMAL);
+
+ // Determine if there are any events pending in the queue.
+ bool IsEmpty() const;
+
+ // Determine how many events are pending in the queue.
+ uint32_t Length() const;
+
+ already_AddRefed<nsIRunnable> GetEvent();
+
+ // Block the current thread until the queue is empty. This may not be called
+ // on the main thread or the base target. The ThrottledEventQueue must not be
+ // paused.
+ void AwaitIdle() const;
+
+ // If |aIsPaused| is true, pause execution of events from this queue. No
+ // events from this queue will be run until this is called with |aIsPaused|
+ // false.
+ //
+ // To un-pause a ThrottledEventQueue, we need to dispatch a runnable to the
+ // underlying event target. That operation may fail, so this method is
+ // fallible as well.
+ //
+ // Note that, although ThrottledEventQueue's behavior is descibed as queueing
+ // events on the base target, an event queued on a TEQ is never actually moved
+ // to any other queue. What is actually dispatched to the base is an
+ // "executor" event which, when run, removes an event from the TEQ and runs it
+ // immediately. This means that you can pause a TEQ even after the executor
+ // has been queued on the base target, and even so, no events from the TEQ
+ // will run. When the base target gets around to running the executor, the
+ // executor will see that the TEQ is paused, and do nothing.
+ [[nodiscard]] nsresult SetIsPaused(bool aIsPaused);
+
+ // Return true if this ThrottledEventQueue is paused.
+ bool IsPaused() const;
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIEVENTTARGET_FULL
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_THROTTLEDEVENTQUEUE_IID);
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(ThrottledEventQueue, NS_THROTTLEDEVENTQUEUE_IID);
+
+} // namespace mozilla
+
+#endif // mozilla_ThrottledEventQueue_h
diff --git a/xpcom/threads/TimerThread.cpp b/xpcom/threads/TimerThread.cpp
new file mode 100644
index 0000000000..9cd60e1bae
--- /dev/null
+++ b/xpcom/threads/TimerThread.cpp
@@ -0,0 +1,779 @@
+/* -*- 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 "nsTimerImpl.h"
+#include "TimerThread.h"
+
+#include "nsThreadUtils.h"
+#include "pratom.h"
+
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+#include "mozilla/ChaosMode.h"
+#include "mozilla/ArenaAllocator.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/BinarySearch.h"
+#include "mozilla/OperatorNewExtensions.h"
+
+#include <math.h>
+
+using namespace mozilla;
+#ifdef MOZ_TASK_TRACER
+# include "GeckoTaskTracerImpl.h"
+using namespace mozilla::tasktracer;
+#endif
+
+NS_IMPL_ISUPPORTS_INHERITED(TimerThread, Runnable, nsIObserver)
+
+TimerThread::TimerThread()
+ : Runnable("TimerThread"),
+ mInitialized(false),
+ mMonitor("TimerThread.mMonitor"),
+ mShutdown(false),
+ mWaiting(false),
+ mNotified(false),
+ mSleeping(false),
+ mAllowedEarlyFiringMicroseconds(0) {}
+
+TimerThread::~TimerThread() {
+ mThread = nullptr;
+
+ NS_ASSERTION(mTimers.IsEmpty(), "Timers remain in TimerThread::~TimerThread");
+}
+
+nsresult TimerThread::InitLocks() { return NS_OK; }
+
+namespace {
+
+class TimerObserverRunnable : public Runnable {
+ public:
+ explicit TimerObserverRunnable(nsIObserver* aObserver)
+ : mozilla::Runnable("TimerObserverRunnable"), mObserver(aObserver) {}
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsCOMPtr<nsIObserver> mObserver;
+};
+
+NS_IMETHODIMP
+TimerObserverRunnable::Run() {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->AddObserver(mObserver, "sleep_notification", false);
+ observerService->AddObserver(mObserver, "wake_notification", false);
+ observerService->AddObserver(mObserver, "suspend_process_notification",
+ false);
+ observerService->AddObserver(mObserver, "resume_process_notification",
+ false);
+ }
+ return NS_OK;
+}
+
+} // namespace
+
+namespace {
+
+// TimerEventAllocator is a thread-safe allocator used only for nsTimerEvents.
+// It's needed to avoid contention over the default allocator lock when
+// firing timer events (see bug 733277). The thread-safety is required because
+// nsTimerEvent objects are allocated on the timer thread, and freed on another
+// thread. Because TimerEventAllocator has its own lock, contention over that
+// lock is limited to the allocation and deallocation of nsTimerEvent objects.
+//
+// Because this is layered over ArenaAllocator, it never shrinks -- even
+// "freed" nsTimerEvents aren't truly freed, they're just put onto a free-list
+// for later recycling. So the amount of memory consumed will always be equal
+// to the high-water mark consumption. But nsTimerEvents are small and it's
+// unusual to have more than a few hundred of them, so this shouldn't be a
+// problem in practice.
+
+class TimerEventAllocator {
+ private:
+ struct FreeEntry {
+ FreeEntry* mNext;
+ };
+
+ ArenaAllocator<4096> mPool;
+ FreeEntry* mFirstFree;
+ mozilla::Monitor mMonitor;
+
+ public:
+ TimerEventAllocator()
+ : mPool(), mFirstFree(nullptr), mMonitor("TimerEventAllocator") {}
+
+ ~TimerEventAllocator() = default;
+
+ void* Alloc(size_t aSize);
+ void Free(void* aPtr);
+};
+
+} // namespace
+
+// This is a nsICancelableRunnable because we can dispatch it to Workers and
+// those can be shut down at any time, and in these cases, Cancel() is called
+// instead of Run().
+class nsTimerEvent final : public CancelableRunnable {
+ public:
+ NS_IMETHOD Run() override;
+
+ nsresult Cancel() override {
+ mTimer->Cancel();
+ return NS_OK;
+ }
+
+#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+ NS_IMETHOD GetName(nsACString& aName) override;
+#endif
+
+ explicit nsTimerEvent(already_AddRefed<nsTimerImpl> aTimer)
+ : mozilla::CancelableRunnable("nsTimerEvent"),
+ mTimer(aTimer),
+ mGeneration(mTimer->GetGeneration()) {
+ // Note: We override operator new for this class, and the override is
+ // fallible!
+ sAllocatorUsers++;
+
+ if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) {
+ mInitTime = TimeStamp::Now();
+ }
+ }
+
+ static void Init();
+ static void Shutdown();
+ static void DeleteAllocatorIfNeeded();
+
+ static void* operator new(size_t aSize) noexcept(true) {
+ return sAllocator->Alloc(aSize);
+ }
+ void operator delete(void* aPtr) {
+ sAllocator->Free(aPtr);
+ DeleteAllocatorIfNeeded();
+ }
+
+ already_AddRefed<nsTimerImpl> ForgetTimer() { return mTimer.forget(); }
+
+ private:
+ nsTimerEvent(const nsTimerEvent&) = delete;
+ nsTimerEvent& operator=(const nsTimerEvent&) = delete;
+ nsTimerEvent& operator=(const nsTimerEvent&&) = delete;
+
+ ~nsTimerEvent() {
+ MOZ_ASSERT(!sCanDeleteAllocator || sAllocatorUsers > 0,
+ "This will result in us attempting to deallocate the "
+ "nsTimerEvent allocator twice");
+ sAllocatorUsers--;
+ }
+
+ TimeStamp mInitTime;
+ RefPtr<nsTimerImpl> mTimer;
+ const int32_t mGeneration;
+
+ static TimerEventAllocator* sAllocator;
+
+ static Atomic<int32_t, SequentiallyConsistent> sAllocatorUsers;
+ static Atomic<bool, SequentiallyConsistent> sCanDeleteAllocator;
+};
+
+TimerEventAllocator* nsTimerEvent::sAllocator = nullptr;
+Atomic<int32_t, SequentiallyConsistent> nsTimerEvent::sAllocatorUsers;
+Atomic<bool, SequentiallyConsistent> nsTimerEvent::sCanDeleteAllocator;
+
+namespace {
+
+void* TimerEventAllocator::Alloc(size_t aSize) {
+ MOZ_ASSERT(aSize == sizeof(nsTimerEvent));
+
+ mozilla::MonitorAutoLock lock(mMonitor);
+
+ void* p;
+ if (mFirstFree) {
+ p = mFirstFree;
+ mFirstFree = mFirstFree->mNext;
+ } else {
+ p = mPool.Allocate(aSize, fallible);
+ }
+
+ return p;
+}
+
+void TimerEventAllocator::Free(void* aPtr) {
+ mozilla::MonitorAutoLock lock(mMonitor);
+
+ FreeEntry* entry = reinterpret_cast<FreeEntry*>(aPtr);
+
+ entry->mNext = mFirstFree;
+ mFirstFree = entry;
+}
+
+} // namespace
+
+void nsTimerEvent::Init() { sAllocator = new TimerEventAllocator(); }
+
+void nsTimerEvent::Shutdown() {
+ sCanDeleteAllocator = true;
+ DeleteAllocatorIfNeeded();
+}
+
+void nsTimerEvent::DeleteAllocatorIfNeeded() {
+ if (sCanDeleteAllocator && sAllocatorUsers == 0) {
+ delete sAllocator;
+ sAllocator = nullptr;
+ }
+}
+
+#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+NS_IMETHODIMP
+nsTimerEvent::GetName(nsACString& aName) {
+ bool current;
+ MOZ_RELEASE_ASSERT(
+ NS_SUCCEEDED(mTimer->mEventTarget->IsOnCurrentThread(&current)) &&
+ current);
+
+ mTimer->GetName(aName);
+ return NS_OK;
+}
+#endif
+
+NS_IMETHODIMP
+nsTimerEvent::Run() {
+ if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) {
+ TimeStamp now = TimeStamp::Now();
+ MOZ_LOG(GetTimerLog(), LogLevel::Debug,
+ ("[this=%p] time between PostTimerEvent() and Fire(): %fms\n", this,
+ (now - mInitTime).ToMilliseconds()));
+ }
+
+ mTimer->Fire(mGeneration);
+
+ return NS_OK;
+}
+
+nsresult TimerThread::Init() {
+ mMonitor.AssertCurrentThreadOwns();
+ MOZ_LOG(GetTimerLog(), LogLevel::Debug,
+ ("TimerThread::Init [%d]\n", mInitialized));
+
+ if (!mInitialized) {
+ nsTimerEvent::Init();
+
+ // We hold on to mThread to keep the thread alive.
+ nsresult rv =
+ NS_NewNamedThread("Timer Thread", getter_AddRefs(mThread), this);
+ if (NS_FAILED(rv)) {
+ mThread = nullptr;
+ } else {
+ RefPtr<TimerObserverRunnable> r = new TimerObserverRunnable(this);
+ if (NS_IsMainThread()) {
+ r->Run();
+ } else {
+ NS_DispatchToMainThread(r);
+ }
+ }
+
+ mInitialized = true;
+ }
+
+ if (!mThread) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult TimerThread::Shutdown() {
+ MOZ_LOG(GetTimerLog(), LogLevel::Debug, ("TimerThread::Shutdown begin\n"));
+
+ if (!mThread) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsTArray<RefPtr<nsTimerImpl>> timers;
+ {
+ // lock scope
+ MonitorAutoLock lock(mMonitor);
+
+ mShutdown = true;
+
+ // notify the cond var so that Run() can return
+ if (mWaiting) {
+ mNotified = true;
+ mMonitor.Notify();
+ }
+
+ // Need to copy content of mTimers array to a local array
+ // because call to timers' Cancel() (and release its self)
+ // must not be done under the lock. Destructor of a callback
+ // might potentially call some code reentering the same lock
+ // that leads to unexpected behavior or deadlock.
+ // See bug 422472.
+ for (const UniquePtr<Entry>& entry : mTimers) {
+ timers.AppendElement(entry->Take());
+ }
+
+ mTimers.Clear();
+ }
+
+ for (const RefPtr<nsTimerImpl>& timer : timers) {
+ if (timer) {
+ timer->Cancel();
+ }
+ }
+
+ mThread->Shutdown(); // wait for the thread to die
+
+ nsTimerEvent::Shutdown();
+
+ MOZ_LOG(GetTimerLog(), LogLevel::Debug, ("TimerThread::Shutdown end\n"));
+ return NS_OK;
+}
+
+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
+
+NS_IMETHODIMP
+TimerThread::Run() {
+ NS_SetCurrentThreadName("Timer");
+
+ MonitorAutoLock lock(mMonitor);
+
+ // We need to know how many microseconds give a positive PRIntervalTime. This
+ // 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
+ mAllowedEarlyFiringMicroseconds = usIntervalResolution / 2;
+ bool forceRunNextTimer = false;
+
+ while (!mShutdown) {
+ // Have to use PRIntervalTime here, since PR_WaitCondVar takes it
+ TimeDuration waitFor;
+ bool forceRunThisTimer = forceRunNextTimer;
+ forceRunNextTimer = false;
+
+ if (mSleeping) {
+ // Sleep for 0.1 seconds while not firing timers.
+ uint32_t milliseconds = 100;
+ if (ChaosMode::isActive(ChaosFeature::TimerScheduling)) {
+ milliseconds = ChaosMode::randomUint32LessThan(200);
+ }
+ waitFor = TimeDuration::FromMilliseconds(milliseconds);
+ } else {
+ waitFor = TimeDuration::Forever();
+ TimeStamp now = TimeStamp::Now();
+
+ RemoveLeadingCanceledTimersInternal();
+
+ if (!mTimers.IsEmpty()) {
+ if (now >= mTimers[0]->Value()->mTimeout || forceRunThisTimer) {
+ next:
+ // NB: AddRef before the Release under RemoveTimerInternal to avoid
+ // mRefCnt passing through zero, in case all other refs than the one
+ // from mTimers have gone away (the last non-mTimers[i]-ref's Release
+ // must be racing with us, blocked in gThread->RemoveTimer waiting
+ // for TimerThread::mMonitor, under nsTimerImpl::Release.
+
+ RefPtr<nsTimerImpl> timerRef(mTimers[0]->Take());
+ RemoveFirstTimerInternal();
+
+ MOZ_LOG(GetTimerLog(), LogLevel::Debug,
+ ("Timer thread woke up %fms from when it was supposed to\n",
+ fabs((now - timerRef->mTimeout).ToMilliseconds())));
+
+ // We are going to let the call to PostTimerEvent here handle the
+ // release of the timer so that we don't end up releasing the timer
+ // on the TimerThread instead of on the thread it targets.
+ {
+ LogTimerEvent::Run run(timerRef.get());
+ timerRef = 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().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
+ // code that created and used the timer.
+ //
+ // Further, note that this should never happen even with a
+ // misbehaving user, because nsTimerImpl::Release checks for a
+ // refcount of 1 with an armed timer (a timer whose only reference
+ // is from the timer thread) and when it hits this will remove the
+ // timer from the timer thread and thus destroy the last reference,
+ // preventing this situation from occurring.
+ MOZ_ASSERT(rc != 0, "destroyed timer off its target thread!");
+ }
+
+ if (mShutdown) {
+ break;
+ }
+
+ // Update now, as PostTimerEvent plus the locking may have taken a
+ // tick or two, and we may goto next below.
+ now = TimeStamp::Now();
+ }
+ }
+
+ RemoveLeadingCanceledTimersInternal();
+
+ if (!mTimers.IsEmpty()) {
+ TimeStamp timeout = mTimers[0]->Value()->mTimeout;
+
+ // Don't wait at all (even for PR_INTERVAL_NO_WAIT) if the next timer
+ // is due now or overdue.
+ //
+ // Note that we can only sleep for integer values of a certain
+ // resolution. We use mAllowedEarlyFiringMicroseconds, 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 (ChaosMode::isActive(ChaosFeature::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 < mAllowedEarlyFiringMicroseconds) {
+ forceRunNextTimer = false;
+ goto next; // round down; execute event now
+ }
+ waitFor = TimeDuration::FromMicroseconds(microseconds);
+ if (waitFor.IsZero()) {
+ // round up, wait the minimum time we can wait
+ waitFor = TimeDuration::FromMicroseconds(1);
+ }
+ }
+
+ if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) {
+ if (waitFor == TimeDuration::Forever())
+ MOZ_LOG(GetTimerLog(), LogLevel::Debug, ("waiting forever\n"));
+ else
+ MOZ_LOG(GetTimerLog(), LogLevel::Debug,
+ ("waiting for %f\n", waitFor.ToMilliseconds()));
+ }
+ }
+
+ mWaiting = true;
+ mNotified = false;
+ mMonitor.Wait(waitFor);
+ if (mNotified) {
+ forceRunNextTimer = false;
+ }
+ mWaiting = false;
+ }
+
+ return NS_OK;
+}
+
+nsresult TimerThread::AddTimer(nsTimerImpl* aTimer) {
+ MonitorAutoLock lock(mMonitor);
+
+ if (!aTimer->mEventTarget) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsresult rv = Init();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Add the timer to our list.
+ if (!AddTimerInternal(aTimer)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // Awaken the timer thread.
+ if (mWaiting && mTimers[0]->Value() == aTimer) {
+ mNotified = true;
+ mMonitor.Notify();
+ }
+
+ return NS_OK;
+}
+
+nsresult TimerThread::RemoveTimer(nsTimerImpl* aTimer) {
+ MonitorAutoLock lock(mMonitor);
+
+ // Remove the timer from our array. Tell callers that aTimer was not found
+ // by returning NS_ERROR_NOT_AVAILABLE.
+
+ if (!RemoveTimerInternal(aTimer)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Awaken the timer thread.
+ if (mWaiting) {
+ mNotified = true;
+ mMonitor.Notify();
+ }
+
+ return NS_OK;
+}
+
+TimeStamp TimerThread::FindNextFireTimeForCurrentThread(TimeStamp aDefault,
+ uint32_t aSearchBound) {
+ MonitorAutoLock lock(mMonitor);
+ TimeStamp timeStamp = aDefault;
+ uint32_t index = 0;
+
+#ifdef DEBUG
+ TimeStamp firstTimeStamp;
+ Entry* initialFirstEntry = nullptr;
+ if (!mTimers.IsEmpty()) {
+ initialFirstEntry = mTimers[0].get();
+ firstTimeStamp = mTimers[0]->Timeout();
+ }
+#endif
+
+ auto end = mTimers.end();
+ while (end != mTimers.begin()) {
+ nsTimerImpl* timer = mTimers[0]->Value();
+ if (timer) {
+ if (timer->mTimeout > aDefault) {
+ timeStamp = aDefault;
+ break;
+ }
+
+ // Don't yield to timers created with the *_LOW_PRIORITY type.
+ if (!timer->IsLowPriority()) {
+ bool isOnCurrentThread = false;
+ nsresult rv =
+ timer->mEventTarget->IsOnCurrentThread(&isOnCurrentThread);
+ if (NS_SUCCEEDED(rv) && isOnCurrentThread) {
+ timeStamp = timer->mTimeout;
+ break;
+ }
+ }
+
+ if (++index > aSearchBound) {
+ // Track the currently highest timeout so that we can bail out when we
+ // reach the bound or when we find a timer for the current thread.
+ // This won't give accurate information if we stop before finding
+ // any timer for the current thread, but at least won't report too
+ // long idle period.
+ timeStamp = timer->mTimeout;
+ break;
+ }
+ }
+
+ std::pop_heap(mTimers.begin(), end, Entry::UniquePtrLessThan);
+ --end;
+ }
+
+ while (end != mTimers.end()) {
+ ++end;
+ std::push_heap(mTimers.begin(), end, Entry::UniquePtrLessThan);
+ }
+
+#ifdef DEBUG
+ if (!mTimers.IsEmpty()) {
+ if (firstTimeStamp != mTimers[0]->Timeout()) {
+ TimeStamp now = TimeStamp::Now();
+ printf_stderr(
+ "firstTimeStamp %f, mTimers[0]->Timeout() %f, "
+ "initialFirstTimer %p, current first %p\n",
+ (firstTimeStamp - now).ToMilliseconds(),
+ (mTimers[0]->Timeout() - now).ToMilliseconds(), initialFirstEntry,
+ mTimers[0].get());
+ }
+ }
+ MOZ_ASSERT_IF(!mTimers.IsEmpty(), firstTimeStamp == mTimers[0]->Timeout());
+#endif
+
+ return timeStamp;
+}
+
+// This function must be called from within a lock
+bool TimerThread::AddTimerInternal(nsTimerImpl* aTimer) {
+ mMonitor.AssertCurrentThreadOwns();
+ if (mShutdown) {
+ return false;
+ }
+
+ TimeStamp now = TimeStamp::Now();
+
+ LogTimerEvent::LogDispatch(aTimer);
+
+ UniquePtr<Entry>* entry = mTimers.AppendElement(
+ MakeUnique<Entry>(now, aTimer->mTimeout, aTimer), mozilla::fallible);
+ if (!entry) {
+ return false;
+ }
+
+ std::push_heap(mTimers.begin(), mTimers.end(), Entry::UniquePtrLessThan);
+
+#ifdef MOZ_TASK_TRACER
+ // Caller of AddTimer is the parent task of its timer event, so we store the
+ // TraceInfo here for later used.
+ aTimer->GetTLSTraceInfo();
+#endif
+
+ return true;
+}
+
+bool TimerThread::RemoveTimerInternal(nsTimerImpl* aTimer) {
+ mMonitor.AssertCurrentThreadOwns();
+ if (!aTimer || !aTimer->mHolder) {
+ return false;
+ }
+ aTimer->mHolder->Forget(aTimer);
+ return true;
+}
+
+void TimerThread::RemoveLeadingCanceledTimersInternal() {
+ mMonitor.AssertCurrentThreadOwns();
+
+ // Move all canceled timers from the front of the list to
+ // the back of the list using std::pop_heap(). We do this
+ // without actually removing them from the list so we can
+ // modify the nsTArray in a single bulk operation.
+ auto sortedEnd = mTimers.end();
+ while (sortedEnd != mTimers.begin() && !mTimers[0]->Value()) {
+ std::pop_heap(mTimers.begin(), sortedEnd, Entry::UniquePtrLessThan);
+ --sortedEnd;
+ }
+
+ // If there were no canceled timers then we are done.
+ if (sortedEnd == mTimers.end()) {
+ return;
+ }
+
+ // Finally, remove the canceled timers from the back of the
+ // nsTArray.
+ mTimers.RemoveLastElements(mTimers.end() - sortedEnd);
+}
+
+void TimerThread::RemoveFirstTimerInternal() {
+ mMonitor.AssertCurrentThreadOwns();
+ MOZ_ASSERT(!mTimers.IsEmpty());
+ std::pop_heap(mTimers.begin(), mTimers.end(), Entry::UniquePtrLessThan);
+ mTimers.RemoveLastElement();
+}
+
+already_AddRefed<nsTimerImpl> TimerThread::PostTimerEvent(
+ already_AddRefed<nsTimerImpl> aTimerRef) {
+ mMonitor.AssertCurrentThreadOwns();
+
+ RefPtr<nsTimerImpl> timer(aTimerRef);
+ if (!timer->mEventTarget) {
+ NS_ERROR("Attempt to post timer event to NULL event target");
+ return timer.forget();
+ }
+
+ // XXX we may want to reuse this nsTimerEvent in the case of repeating timers.
+
+ // Since we already addref'd 'timer', we don't need to addref here.
+ // We will release either in ~nsTimerEvent(), or pass the reference back to
+ // the caller. We need to copy the generation number from this timer into the
+ // event, so we can avoid firing a timer that was re-initialized after being
+ // canceled.
+
+#ifdef MOZ_TASK_TRACER
+ // During the dispatch of TimerEvent, we overwrite the current TraceInfo
+ // partially with the info saved in timer earlier, and restore it back by
+ // AutoSaveCurTraceInfo.
+ AutoSaveCurTraceInfo saveCurTraceInfo;
+ (timer->GetTracedTask()).SetTLSTraceInfo();
+#endif
+
+ nsCOMPtr<nsIEventTarget> target = timer->mEventTarget;
+
+ void* p = nsTimerEvent::operator new(sizeof(nsTimerEvent));
+ if (!p) {
+ return timer.forget();
+ }
+ RefPtr<nsTimerEvent> event =
+ ::new (KnownNotNull, p) nsTimerEvent(timer.forget());
+
+ nsresult rv;
+ {
+ // We release mMonitor around the Dispatch because if this timer is targeted
+ // at the TimerThread we'll deadlock.
+ MonitorAutoUnlock unlock(mMonitor);
+ rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+
+ if (NS_FAILED(rv)) {
+ timer = event->ForgetTimer();
+ RemoveTimerInternal(timer);
+ return timer.forget();
+ }
+
+ return nullptr;
+}
+
+void TimerThread::DoBeforeSleep() {
+ // Mainthread
+ MonitorAutoLock lock(mMonitor);
+ mSleeping = true;
+}
+
+// Note: wake may be notified without preceding sleep notification
+void TimerThread::DoAfterSleep() {
+ // Mainthread
+ MonitorAutoLock lock(mMonitor);
+ mSleeping = false;
+
+ // Wake up the timer thread to re-process the array to ensure the sleep delay
+ // is correct, and fire any expired timers (perhaps quite a few)
+ mNotified = true;
+ mMonitor.Notify();
+}
+
+NS_IMETHODIMP
+TimerThread::Observe(nsISupports* /* aSubject */, const char* aTopic,
+ const char16_t* /* aData */) {
+ if (strcmp(aTopic, "sleep_notification") == 0 ||
+ strcmp(aTopic, "suspend_process_notification") == 0) {
+ DoBeforeSleep();
+ } else if (strcmp(aTopic, "wake_notification") == 0 ||
+ strcmp(aTopic, "resume_process_notification") == 0) {
+ DoAfterSleep();
+ }
+
+ return NS_OK;
+}
+
+uint32_t TimerThread::AllowedEarlyFiringMicroseconds() const {
+ return mAllowedEarlyFiringMicroseconds;
+}
diff --git a/xpcom/threads/TimerThread.h b/xpcom/threads/TimerThread.h
new file mode 100644
index 0000000000..1a843e4d5f
--- /dev/null
+++ b/xpcom/threads/TimerThread.h
@@ -0,0 +1,115 @@
+/* -*- 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 TimerThread_h___
+#define TimerThread_h___
+
+#include "nsIObserver.h"
+#include "nsIRunnable.h"
+#include "nsIThread.h"
+
+#include "nsTimerImpl.h"
+#include "nsThreadUtils.h"
+
+#include "nsTArray.h"
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/UniquePtr.h"
+
+#include <algorithm>
+
+namespace mozilla {
+class TimeStamp;
+} // namespace mozilla
+
+class TimerThread final : public mozilla::Runnable, public nsIObserver {
+ public:
+ typedef mozilla::Monitor Monitor;
+ typedef mozilla::TimeStamp TimeStamp;
+ typedef mozilla::TimeDuration TimeDuration;
+
+ TimerThread();
+ nsresult InitLocks();
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSIOBSERVER
+
+ nsresult Shutdown();
+
+ nsresult AddTimer(nsTimerImpl* aTimer);
+ nsresult RemoveTimer(nsTimerImpl* aTimer);
+ TimeStamp FindNextFireTimeForCurrentThread(TimeStamp aDefault,
+ uint32_t aSearchBound);
+
+ void DoBeforeSleep();
+ void DoAfterSleep();
+
+ bool IsOnTimerThread() const {
+ return mThread->SerialEventTarget()->IsOnCurrentThread();
+ }
+
+ uint32_t AllowedEarlyFiringMicroseconds() const;
+
+ private:
+ ~TimerThread();
+
+ bool mInitialized;
+
+ // These internal helper methods must be called while mMonitor is held.
+ // AddTimerInternal returns false if the insertion failed.
+ bool AddTimerInternal(nsTimerImpl* aTimer);
+ bool RemoveTimerInternal(nsTimerImpl* aTimer);
+ void RemoveLeadingCanceledTimersInternal();
+ void RemoveFirstTimerInternal();
+ nsresult Init();
+
+ already_AddRefed<nsTimerImpl> PostTimerEvent(
+ already_AddRefed<nsTimerImpl> aTimerRef);
+
+ nsCOMPtr<nsIThread> mThread;
+ Monitor mMonitor;
+
+ bool mShutdown;
+ bool mWaiting;
+ bool mNotified;
+ bool mSleeping;
+
+ class Entry final : public nsTimerImplHolder {
+ const TimeStamp mTimeout;
+
+ public:
+ Entry(const TimeStamp& aMinTimeout, const TimeStamp& aTimeout,
+ nsTimerImpl* aTimerImpl)
+ : nsTimerImplHolder(aTimerImpl),
+ mTimeout(std::max(aMinTimeout, aTimeout)) {}
+
+ nsTimerImpl* Value() const { return mTimerImpl; }
+
+ already_AddRefed<nsTimerImpl> Take() {
+ if (mTimerImpl) {
+ mTimerImpl->SetHolder(nullptr);
+ }
+ return mTimerImpl.forget();
+ }
+
+ static bool UniquePtrLessThan(mozilla::UniquePtr<Entry>& aLeft,
+ mozilla::UniquePtr<Entry>& aRight) {
+ // This is reversed because std::push_heap() sorts the "largest" to
+ // the front of the heap. We want that to be the earliest timer.
+ return aRight->mTimeout < aLeft->mTimeout;
+ }
+
+ TimeStamp Timeout() const { return mTimeout; }
+ };
+
+ nsTArray<mozilla::UniquePtr<Entry>> mTimers;
+ uint32_t mAllowedEarlyFiringMicroseconds;
+};
+
+#endif /* TimerThread_h___ */
diff --git a/xpcom/threads/components.conf b/xpcom/threads/components.conf
new file mode 100644
index 0000000000..c8b5c4912a
--- /dev/null
+++ b/xpcom/threads/components.conf
@@ -0,0 +1,21 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Classes = [
+ {
+ 'cid': '{03d68f92-9513-4e25-9be9-7cb239874172}',
+ 'contract_ids': ['@mozilla.org/process/environment;1'],
+ 'legacy_constructor': 'nsEnvironment::Create',
+ 'headers': ['/xpcom/threads/nsEnvironment.h'],
+ },
+ {
+ 'cid': '{5ff24248-1dd2-11b2-8427-fbab44f29bc8}',
+ 'contract_ids': ['@mozilla.org/timer;1'],
+ 'legacy_constructor': 'nsTimer::XPCOMConstructor',
+ 'headers': ['/xpcom/threads/nsTimerImpl.h'],
+ 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS,
+ },
+]
diff --git a/xpcom/threads/moz.build b/xpcom/threads/moz.build
new file mode 100644
index 0000000000..ed89ab0150
--- /dev/null
+++ b/xpcom/threads/moz.build
@@ -0,0 +1,129 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+XPIDL_SOURCES += [
+ "nsIDirectTaskDispatcher.idl",
+ "nsIEnvironment.idl",
+ "nsIEventTarget.idl",
+ "nsIIdlePeriod.idl",
+ "nsINamed.idl",
+ "nsIProcess.idl",
+ "nsIRunnable.idl",
+ "nsISerialEventTarget.idl",
+ "nsISupportsPriority.idl",
+ "nsIThread.idl",
+ "nsIThreadInternal.idl",
+ "nsIThreadManager.idl",
+ "nsIThreadPool.idl",
+ "nsITimer.idl",
+]
+
+XPIDL_MODULE = "xpcom_threads"
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+EXPORTS += [
+ "MainThreadUtils.h",
+ "nsICancelableRunnable.h",
+ "nsIDiscardableRunnable.h",
+ "nsIIdleRunnable.h",
+ "nsMemoryPressure.h",
+ "nsProcess.h",
+ "nsProxyRelease.h",
+ "nsThread.h",
+ "nsThreadManager.h",
+ "nsThreadPool.h",
+ "nsThreadUtils.h",
+]
+
+EXPORTS.mozilla += [
+ "AbstractThread.h",
+ "BlockingResourceBase.h",
+ "CondVar.h",
+ "CPUUsageWatcher.h",
+ "DataMutex.h",
+ "DeadlockDetector.h",
+ "EventQueue.h",
+ "IdlePeriodState.h",
+ "IdleTaskRunner.h",
+ "InputTaskManager.h",
+ "LazyIdleThread.h",
+ "MainThreadIdlePeriod.h",
+ "Monitor.h",
+ "MozPromise.h",
+ "MozPromiseInlines.h",
+ "Mutex.h",
+ "PerformanceCounter.h",
+ "Queue.h",
+ "RecursiveMutex.h",
+ "ReentrantMonitor.h",
+ "RWLock.h",
+ "SchedulerGroup.h",
+ "SharedThreadPool.h",
+ "SpinEventLoopUntil.h",
+ "StateMirroring.h",
+ "StateWatching.h",
+ "SynchronizedEventQueue.h",
+ "SyncRunnable.h",
+ "TaskCategory.h",
+ "TaskController.h",
+ "TaskDispatcher.h",
+ "TaskQueue.h",
+ "ThreadBound.h",
+ "ThreadEventQueue.h",
+ "ThrottledEventQueue.h",
+]
+
+SOURCES += [
+ "IdleTaskRunner.cpp",
+ "ThreadDelay.cpp",
+]
+
+UNIFIED_SOURCES += [
+ "AbstractThread.cpp",
+ "BlockingResourceBase.cpp",
+ "CPUUsageWatcher.cpp",
+ "EventQueue.cpp",
+ "IdlePeriodState.cpp",
+ "InputEventStatistics.cpp",
+ "InputTaskManager.cpp",
+ "LazyIdleThread.cpp",
+ "MainThreadIdlePeriod.cpp",
+ "nsEnvironment.cpp",
+ "nsMemoryPressure.cpp",
+ "nsProcessCommon.cpp",
+ "nsProxyRelease.cpp",
+ "nsThread.cpp",
+ "nsThreadManager.cpp",
+ "nsThreadPool.cpp",
+ "nsThreadUtils.cpp",
+ "nsTimerImpl.cpp",
+ "PerformanceCounter.cpp",
+ "RecursiveMutex.cpp",
+ "RWLock.cpp",
+ "SchedulerGroup.cpp",
+ "SharedThreadPool.cpp",
+ "SynchronizedEventQueue.cpp",
+ "TaskController.cpp",
+ "TaskQueue.cpp",
+ "ThreadEventQueue.cpp",
+ "ThreadEventTarget.cpp",
+ "ThreadLocalVariables.cpp",
+ "ThrottledEventQueue.cpp",
+ "TimerThread.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "../build",
+ "/caps",
+ "/tools/profiler",
+]
+
+FINAL_LIBRARY = "xul"
+
+include("/ipc/chromium/chromium-config.mozbuild")
diff --git a/xpcom/threads/nsEnvironment.cpp b/xpcom/threads/nsEnvironment.cpp
new file mode 100644
index 0000000000..99aae49bd5
--- /dev/null
+++ b/xpcom/threads/nsEnvironment.cpp
@@ -0,0 +1,152 @@
+/* -*- 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 "nsEnvironment.h"
+#include "prenv.h"
+#include "nsBaseHashtable.h"
+#include "nsHashKeys.h"
+#include "nsPromiseFlatString.h"
+#include "nsDependentString.h"
+#include "nsNativeCharsetUtils.h"
+#include "mozilla/Printf.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsEnvironment, nsIEnvironment)
+
+nsresult nsEnvironment::Create(nsISupports* aOuter, REFNSIID aIID,
+ void** aResult) {
+ nsresult rv;
+ *aResult = nullptr;
+
+ if (aOuter) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+
+ nsEnvironment* obj = new nsEnvironment();
+
+ rv = obj->QueryInterface(aIID, aResult);
+ if (NS_FAILED(rv)) {
+ delete obj;
+ }
+ return rv;
+}
+
+nsEnvironment::~nsEnvironment() = default;
+
+NS_IMETHODIMP
+nsEnvironment::Exists(const nsAString& aName, bool* aOutValue) {
+ nsAutoCString nativeName;
+ nsresult rv = NS_CopyUnicodeToNative(aName, nativeName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoCString nativeVal;
+#if defined(XP_UNIX)
+ /* 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();
+#endif /* XP_UNIX */
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsEnvironment::Get(const nsAString& aName, nsAString& aOutValue) {
+ 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.
+ */
+
+typedef nsBaseHashtableET<nsCharPtrHashKey, char*> EnvEntryType;
+typedef nsTHashtable<EnvEntryType> EnvHashType;
+
+static EnvHashType* gEnvHash = nullptr;
+
+static bool EnsureEnvHash() {
+ if (gEnvHash) {
+ return true;
+ }
+
+ gEnvHash = new EnvHashType;
+ if (!gEnvHash) {
+ return false;
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP
+nsEnvironment::Set(const nsAString& aName, const nsAString& aValue) {
+ 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;
+ }
+
+ SmprintfPointer newData =
+ mozilla::Smprintf("%s=%s", nativeName.get(), nativeVal.get());
+ if (!newData) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ PR_SetEnv(newData.get());
+ if (entry->GetData()) {
+ free(entry->GetData());
+ }
+ entry->SetData(newData.release());
+ return NS_OK;
+}
diff --git a/xpcom/threads/nsEnvironment.h b/xpcom/threads/nsEnvironment.h
new file mode 100644
index 0000000000..acc9200bac
--- /dev/null
+++ b/xpcom/threads/nsEnvironment.h
@@ -0,0 +1,36 @@
+/* -*- 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 nsEnvironment_h__
+#define nsEnvironment_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Mutex.h"
+#include "nsIEnvironment.h"
+
+#define NS_ENVIRONMENT_CID \
+ { \
+ 0X3D68F92UL, 0X9513, 0X4E25, { \
+ 0X9B, 0XE9, 0X7C, 0XB2, 0X39, 0X87, 0X41, 0X72 \
+ } \
+ }
+#define NS_ENVIRONMENT_CONTRACTID "@mozilla.org/process/environment;1"
+
+class nsEnvironment final : public nsIEnvironment {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIENVIRONMENT
+
+ static nsresult Create(nsISupports* aOuter, REFNSIID aIID, void** aResult);
+
+ private:
+ nsEnvironment() : mLock("nsEnvironment.mLock") {}
+ ~nsEnvironment();
+
+ mozilla::Mutex mLock;
+};
+
+#endif /* !nsEnvironment_h__ */
diff --git a/xpcom/threads/nsICancelableRunnable.h b/xpcom/threads/nsICancelableRunnable.h
new file mode 100644
index 0000000000..7aa98c86b6
--- /dev/null
+++ b/xpcom/threads/nsICancelableRunnable.h
@@ -0,0 +1,40 @@
+/* -*- 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 nsICancelableRunnable_h__
+#define nsICancelableRunnable_h__
+
+#include "nsISupports.h"
+
+#define NS_ICANCELABLERUNNABLE_IID \
+ { \
+ 0xde93dc4c, 0x5eea, 0x4eb7, { \
+ 0xb6, 0xd1, 0xdb, 0xf1, 0xe0, 0xce, 0xf6, 0x5c \
+ } \
+ }
+
+class nsICancelableRunnable : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICANCELABLERUNNABLE_IID)
+
+ /*
+ * Cancels a pending task, so that calling run() on the task is a no-op.
+ * Calling cancel after the task execution has begun will be a no-op.
+ * Calling this method twice is considered an error.
+ *
+ * @throws NS_ERROR_UNEXPECTED
+ * Indicates that the runnable has already been canceled.
+ */
+ virtual nsresult Cancel() = 0;
+
+ protected:
+ nsICancelableRunnable() = default;
+ virtual ~nsICancelableRunnable() = default;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsICancelableRunnable, NS_ICANCELABLERUNNABLE_IID)
+
+#endif // nsICancelableRunnable_h__
diff --git a/xpcom/threads/nsIDirectTaskDispatcher.idl b/xpcom/threads/nsIDirectTaskDispatcher.idl
new file mode 100644
index 0000000000..2729ddb7dc
--- /dev/null
+++ b/xpcom/threads/nsIDirectTaskDispatcher.idl
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRunnable.idl"
+
+%{C++
+#include "mozilla/AlreadyAddRefed.h"
+%}
+
+native alreadyAddRefed_nsIRunnable(already_AddRefed<nsIRunnable>);
+
+/*
+ * The primary use of this interface is to allow any nsISerialEventTarget to
+ * provide Direct Task dispatching which is similar (but not identical to) the
+ * microtask semantics of JS promises.
+ * New direct task may be dispatched when a current direct task is running. In
+ * which case they will be run in FIFO order.
+ */
+[builtinclass, uuid(e05bf0fe-94b7-4e28-8462-a8368da9c136)]
+interface nsIDirectTaskDispatcher : nsISupports
+{
+ /**
+ * Dispatch an event for the nsISerialEventTarget, using the direct task
+ * queue.
+ *
+ * This function must be called from the same nsISerialEventTarget
+ * implementing direct task dispatching.
+ *
+ * @param event
+ * The alreadyAddRefed<> event to dispatch.
+ *
+ */
+ [noscript] void dispatchDirectTask(in alreadyAddRefed_nsIRunnable event);
+
+ /**
+ * Synchronously run any pending direct tasks queued.
+ */
+ [noscript] void drainDirectTasks();
+
+ /**
+ * Returns true if any direct tasks are pending.
+ */
+ [noscript] bool haveDirectTasks();
+
+ %{C++
+ // Infallible version of the above. Will assert that it is successful.
+ bool HaveDirectTasks() {
+ bool value = false;
+ MOZ_ALWAYS_SUCCEEDS(HaveDirectTasks(&value));
+ return value;
+ }
+ %}
+
+};
diff --git a/xpcom/threads/nsIDiscardableRunnable.h b/xpcom/threads/nsIDiscardableRunnable.h
new file mode 100644
index 0000000000..873b1f5d93
--- /dev/null
+++ b/xpcom/threads/nsIDiscardableRunnable.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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 XPCOM_THREADS_NSIDISCARDABLERUNNABLE_H_
+#define XPCOM_THREADS_NSIDISCARDABLERUNNABLE_H_
+
+#include "nsISupports.h"
+
+/**
+ * An interface implemented by nsIRunnable tasks for which nsIRunnable::Run()
+ * might not be called.
+ */
+#define NS_IDISCARDABLERUNNABLE_IID \
+ { \
+ 0xde93dc4c, 0x755c, 0x4cdc, { \
+ 0x96, 0x76, 0x35, 0xc6, 0x48, 0x81, 0x59, 0x78 \
+ } \
+ }
+
+class NS_NO_VTABLE nsIDiscardableRunnable : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IDISCARDABLERUNNABLE_IID)
+
+ /**
+ * Called exactly once on a queued task only if nsIRunnable::Run() is not
+ * called.
+ */
+ virtual void OnDiscard() = 0;
+
+ protected:
+ nsIDiscardableRunnable() = default;
+ virtual ~nsIDiscardableRunnable() = default;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIDiscardableRunnable,
+ NS_IDISCARDABLERUNNABLE_IID)
+
+#endif // XPCOM_THREADS_NSIDISCARDABLERUNNABLE_H_
diff --git a/xpcom/threads/nsIEnvironment.idl b/xpcom/threads/nsIEnvironment.idl
new file mode 100644
index 0000000000..aad03191d7
--- /dev/null
+++ b/xpcom/threads/nsIEnvironment.idl
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsISupports.idl"
+
+/**
+ * Scriptable access to the current process environment.
+ *
+ */
+[scriptable, uuid(101d5941-d820-4e85-a266-9a3469940807)]
+interface nsIEnvironment : nsISupports
+{
+ /**
+ * Set the value of an environment variable.
+ *
+ * @param aName the variable name to set.
+ * @param aValue the value to set.
+ */
+ void set(in AString aName, in AString aValue);
+
+ /**
+ * Get the value of an environment variable.
+ *
+ * @param aName the variable name to retrieve.
+ * @return returns the value of the env variable. An empty string
+ * will be returned when the env variable does not exist or
+ * when the value itself is an empty string - please use
+ * |exists()| to probe whether the env variable exists
+ * or not.
+ */
+ AString get(in AString aName);
+
+ /**
+ * Check the existence of an environment variable.
+ * This method checks whether an environment variable is present in
+ * the environment or not.
+ *
+ * - 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.
+ * - 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.
+ *
+ * @param aName the variable name to probe.
+ * @return if the variable has been set, the value returned is
+ * PR_TRUE. If the variable was not defined in the
+ * environment PR_FALSE will be returned.
+ */
+ boolean exists(in AString aName);
+};
+
diff --git a/xpcom/threads/nsIEventTarget.idl b/xpcom/threads/nsIEventTarget.idl
new file mode 100644
index 0000000000..c0d7bc2272
--- /dev/null
+++ b/xpcom/threads/nsIEventTarget.idl
@@ -0,0 +1,184 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIRunnable.idl"
+%{C++
+#include "nsCOMPtr.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Atomics.h"
+%}
+
+native alreadyAddRefed_nsIRunnable(already_AddRefed<nsIRunnable>);
+
+[builtinclass, scriptable, uuid(a03b8b63-af8b-4164-b0e5-c41e8b2b7cfa)]
+interface nsIEventTarget : nsISupports
+{
+ /* until we can get rid of all uses, keep the non-alreadyAddRefed<> version */
+%{C++
+ nsresult Dispatch(nsIRunnable* aEvent, uint32_t aFlags) {
+ return Dispatch(nsCOMPtr<nsIRunnable>(aEvent).forget(), aFlags);
+ }
+%}
+
+ /**
+ * This flag specifies the default mode of event dispatch, whereby the event
+ * is simply queued for later processing. When this flag is specified,
+ * dispatch returns immediately after the event is queued.
+ */
+ const unsigned long DISPATCH_NORMAL = 0;
+
+ /**
+ * This flag specifies the synchronous mode of event dispatch, in which the
+ * dispatch method does not return until the event has been processed.
+ *
+ * NOTE: passing this flag to dispatch may have the side-effect of causing
+ * other events on the current thread to be processed while waiting for the
+ * given event to be processed.
+ */
+ const unsigned long DISPATCH_SYNC = 1;
+
+ /**
+ * This flag specifies that the dispatch is occurring from a running event
+ * that was dispatched to the same event target, and that event is about to
+ * finish.
+ *
+ * A thread pool can use this as an optimization hint to not spin up
+ * another thread, since the current thread is about to become idle.
+ *
+ * These events are always async.
+ */
+ const unsigned long DISPATCH_AT_END = 2;
+
+ /**
+ * This flag specifies that the dispatched event may block the thread on
+ * which it executes, usually by doing some sort of I/O. This information
+ * may be used by the event target to execute the job on a thread
+ * specifically dedicated to doing I/O, leaving other threads available for
+ * CPU-intensive work.
+ */
+ const unsigned long DISPATCH_EVENT_MAY_BLOCK = 4;
+
+ /**
+ * IsOnCurrentThread() should return true if events dispatched to this target
+ * can possibly run on the current thread, and false otherwise. In the case
+ * of an nsIEventTarget for a thread pool, it should return true on all
+ * threads in the pool. In the case of a non-thread nsIEventTarget such as
+ * ThrottledEventQueue, it should return true on the thread where events are
+ * expected to be processed, even if no events from the queue are actually
+ * being processed right now.
+ *
+ * When called on an nsISerialEventTarget, IsOnCurrentThread can be used to
+ * ensure that no other thread has "ownership" of the event target. As such,
+ * it's useful for asserting that an object is only used on a particular
+ * thread. IsOnCurrentThread can't guarantee that the current event has been
+ * dispatched through a particular event target.
+ *
+ * The infallible version of IsOnCurrentThread() is optimized to avoid a
+ * virtual call for non-thread event targets. Thread targets should set
+ * mThread to their virtual PRThread. Non-thread targets should leave
+ * mThread null and implement IsOnCurrentThreadInfallible() to
+ * return the correct answer.
+ *
+ * The fallible version of IsOnCurrentThread may return errors, such as during
+ * shutdown. If it does not return an error, it should return the same result
+ * as the infallible version. The infallible method should return the correct
+ * result regardless of whether the fallible method returns an error.
+ */
+ %{C++
+public:
+ // Infallible. Defined in nsThreadUtils.cpp. Delegates to
+ // IsOnCurrentThreadInfallible when mThread is null.
+ bool IsOnCurrentThread();
+
+protected:
+ mozilla::Atomic<PRThread*> mThread;
+
+ nsIEventTarget() : mThread(nullptr) {}
+ %}
+ // Note that this method is protected. We define it through IDL, rather than
+ // in a %{C++ block, to ensure that the correct method indices are recorded
+ // for XPConnect purposes.
+ [noscript,notxpcom] boolean isOnCurrentThreadInfallible();
+ %{C++
+public:
+ %}
+
+ // Fallible version of IsOnCurrentThread.
+ boolean isOnCurrentThread();
+
+ /**
+ * Dispatch an event to this event target. This function may be called from
+ * any thread, and it may be called re-entrantly.
+ *
+ * @param event
+ * The alreadyAddRefed<> event to dispatch.
+ * NOTE that the event will be leaked if it fails to dispatch.
+ * @param flags
+ * The flags modifying event dispatch. The flags are described in detail
+ * below.
+ *
+ * @throws NS_ERROR_INVALID_ARG
+ * Indicates that event is null.
+ * @throws NS_ERROR_UNEXPECTED
+ * Indicates that the thread is shutting down and has finished processing
+ * events, so this event would never run and has not been dispatched.
+ */
+ [noscript, binaryname(Dispatch)] void dispatchFromC(in alreadyAddRefed_nsIRunnable event,
+ [default(DISPATCH_NORMAL)] in unsigned long flags);
+ /**
+ * Version of Dispatch to expose to JS, which doesn't require an alreadyAddRefed<>
+ * (it will be converted to that internally)
+ *
+ * @param event
+ * The (raw) event to dispatch.
+ * @param flags
+ * The flags modifying event dispatch. The flags are described in detail
+ * below.
+ *
+ * @throws NS_ERROR_INVALID_ARG
+ * Indicates that event is null.
+ * @throws NS_ERROR_UNEXPECTED
+ * Indicates that the thread is shutting down and has finished processing
+ * events, so this event would never run and has not been dispatched.
+ */
+ [binaryname(DispatchFromScript)] void dispatch(in nsIRunnable event, in unsigned long flags);
+ /**
+ * Dispatch an event to this event target, but do not run it before delay
+ * milliseconds have passed. This function may be called from any thread.
+ *
+ * @param event
+ * The alreadyAddrefed<> event to dispatch.
+ * @param delay
+ * The delay (in ms) before running the event. If event does not rise to
+ * the top of the event queue before the delay has passed, it will be set
+ * aside to execute once the delay has passed. Otherwise, it will be
+ * executed immediately.
+ *
+ * @throws NS_ERROR_INVALID_ARG
+ * Indicates that event is null.
+ * @throws NS_ERROR_UNEXPECTED
+ * Indicates that the thread is shutting down and has finished processing
+ * events, so this event would never run and has not been dispatched, or
+ * that delay is zero.
+ */
+ [noscript] void delayedDispatch(in alreadyAddRefed_nsIRunnable event, in unsigned long delay);
+};
+
+%{C++
+// convenient aliases:
+#define NS_DISPATCH_NORMAL nsIEventTarget::DISPATCH_NORMAL
+#define NS_DISPATCH_SYNC nsIEventTarget::DISPATCH_SYNC
+#define NS_DISPATCH_AT_END nsIEventTarget::DISPATCH_AT_END
+#define NS_DISPATCH_EVENT_MAY_BLOCK nsIEventTarget::DISPATCH_EVENT_MAY_BLOCK
+
+// Convenient NS_DECL variant that includes some C++-only methods.
+#define NS_DECL_NSIEVENTTARGET_FULL \
+ NS_DECL_NSIEVENTTARGET \
+ /* Avoid hiding these methods */ \
+ using nsIEventTarget::Dispatch; \
+ using nsIEventTarget::IsOnCurrentThread;
+%}
diff --git a/xpcom/threads/nsIIdlePeriod.idl b/xpcom/threads/nsIIdlePeriod.idl
new file mode 100644
index 0000000000..aa72b21711
--- /dev/null
+++ b/xpcom/threads/nsIIdlePeriod.idl
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsISupports.idl"
+
+%{C++
+namespace mozilla {
+class TimeStamp;
+}
+%}
+
+native TimeStamp(mozilla::TimeStamp);
+
+/**
+ * An instance implementing nsIIdlePeriod is used by an associated
+ * nsIThread to estimate when it is likely that it will receive an
+ * event.
+ */
+[builtinclass, uuid(21dd35a2-eae9-4bd8-b470-0dfa35a0e3b9)]
+interface nsIIdlePeriod : nsISupports
+{
+ /**
+ * Return an estimate of a point in time in the future when we
+ * think that the associated thread will become busy. Should
+ * return TimeStamp() (i.e. the null time) or a time less than
+ * TimeStamp::Now() if the thread is currently busy or will become
+ * busy very soon.
+ */
+ TimeStamp getIdlePeriodHint();
+};
diff --git a/xpcom/threads/nsIIdleRunnable.h b/xpcom/threads/nsIIdleRunnable.h
new file mode 100644
index 0000000000..7fe6149154
--- /dev/null
+++ b/xpcom/threads/nsIIdleRunnable.h
@@ -0,0 +1,48 @@
+/* -*- 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 nsIIdleRunnable_h__
+#define nsIIdleRunnable_h__
+
+#include "nsISupports.h"
+#include "mozilla/TimeStamp.h"
+
+#define NS_IIDLERUNNABLE_IID \
+ { \
+ 0x688be92e, 0x7ade, 0x4fdc, { \
+ 0x9d, 0x83, 0x74, 0xcb, 0xef, 0xf4, 0xa5, 0x2c \
+ } \
+ }
+
+class nsIEventTarget;
+
+/**
+ * A task interface for tasks that can schedule their work to happen
+ * in increments bounded by a deadline.
+ */
+class nsIIdleRunnable : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IIDLERUNNABLE_IID)
+
+ /**
+ * Notify the task of a point in time in the future when the task
+ * should stop executing.
+ */
+ virtual void SetDeadline(mozilla::TimeStamp aDeadline){};
+ virtual void SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) {
+ MOZ_ASSERT_UNREACHABLE(
+ "The nsIIdleRunnable instance does not support "
+ "idle dispatch with timeout!");
+ };
+
+ protected:
+ nsIIdleRunnable() = default;
+ virtual ~nsIIdleRunnable() = default;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIIdleRunnable, NS_IIDLERUNNABLE_IID)
+
+#endif // nsIIdleRunnable_h__
diff --git a/xpcom/threads/nsINamed.idl b/xpcom/threads/nsINamed.idl
new file mode 100644
index 0000000000..cdb7d88f30
--- /dev/null
+++ b/xpcom/threads/nsINamed.idl
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsISupports.idl"
+
+/**
+ * Represents an object with a name, such as a runnable or a timer.
+ */
+
+[scriptable, uuid(0c5fe7de-7e83-4d0d-a8a6-4a6518b9a7b3)]
+interface nsINamed : nsISupports
+{
+ /*
+ * A string describing the purpose of the runnable/timer/whatever. Useful
+ * for debugging. This attribute is read-only, but you can change it to a
+ * compile-time string literal with setName.
+ *
+ * WARNING: This attribute will be included in telemetry, so it should
+ * never contain privacy sensitive information.
+ */
+ readonly attribute AUTF8String name;
+};
diff --git a/xpcom/threads/nsIProcess.idl b/xpcom/threads/nsIProcess.idl
new file mode 100644
index 0000000000..88c1d75d44
--- /dev/null
+++ b/xpcom/threads/nsIProcess.idl
@@ -0,0 +1,112 @@
+/* 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 "nsISupports.idl"
+
+interface nsIFile;
+interface nsIObserver;
+
+[scriptable, uuid(609610de-9954-4a63-8a7c-346350a86403)]
+interface nsIProcess : nsISupports
+{
+ /**
+ * Initialises the process with an executable to be run. Call the run method
+ * to run the executable.
+ * @param executable The executable to run.
+ */
+ void init(in nsIFile executable);
+
+ /**
+ * Kills the running process. After exiting the process will either have
+ * been killed or a failure will have been returned.
+ */
+ void kill();
+
+ /**
+ * Executes the file this object was initialized with
+ * @param blocking Whether to wait until the process terminates before
+ returning or not.
+ * @param args An array of arguments to pass to the process in the
+ * native character set.
+ * @param count The length of the args array.
+ */
+ void run(in boolean blocking, [array, size_is(count)] in string args,
+ in unsigned long count);
+
+ /**
+ * Executes the file this object was initialized with optionally calling
+ * an observer after the process has finished running.
+ * @param args An array of arguments to pass to the process in the
+ * native character set.
+ * @param count The length of the args array.
+ * @param observer An observer to notify when the process has completed. It
+ * will receive this process instance as the subject and
+ * "process-finished" or "process-failed" as the topic. The
+ * observer will be notified on the main thread.
+ * @param holdWeak Whether to use a weak reference to hold the observer.
+ */
+ void runAsync([array, size_is(count)] in string args, in unsigned long count,
+ [optional] in nsIObserver observer, [optional] in boolean holdWeak);
+
+ /**
+ * Executes the file this object was initialized with
+ * @param blocking Whether to wait until the process terminates before
+ returning or not.
+ * @param args An array of arguments to pass to the process in UTF-16
+ * @param count The length of the args array.
+ */
+ void runw(in boolean blocking, [array, size_is(count)] in wstring args,
+ in unsigned long count);
+
+ /**
+ * Executes the file this object was initialized with optionally calling
+ * an observer after the process has finished running.
+ * @param args An array of arguments to pass to the process in UTF-16
+ * @param count The length of the args array.
+ * @param observer An observer to notify when the process has completed. It
+ * will receive this process instance as the subject and
+ * "process-finished" or "process-failed" as the topic. The
+ * observer will be notified on the main thread.
+ * @param holdWeak Whether to use a weak reference to hold the observer.
+ */
+ void runwAsync([array, size_is(count)] in wstring args,
+ in unsigned long count,
+ [optional] in nsIObserver observer, [optional] in boolean holdWeak);
+
+ /**
+ * When set to true the process will not open a new window when started and
+ * will run hidden from the user. This currently affects only the Windows
+ * platform.
+ */
+ attribute boolean startHidden;
+
+ /**
+ * When set to true the process will be launched directly without using the
+ * shell. This currently affects only the Windows platform.
+ */
+ attribute boolean noShell;
+
+ /**
+ * The process identifier of the currently running process. This will only
+ * be available after the process has started and may not be available on
+ * some platforms.
+ */
+ readonly attribute unsigned long pid;
+
+ /**
+ * The exit value of the process. This is only valid after the process has
+ * exited.
+ */
+ readonly attribute long exitValue;
+
+ /**
+ * Returns whether the process is currently running or not.
+ */
+ readonly attribute boolean isRunning;
+};
+
+%{C++
+
+#define NS_PROCESS_CONTRACTID "@mozilla.org/process/util;1"
+%}
diff --git a/xpcom/threads/nsIRunnable.idl b/xpcom/threads/nsIRunnable.idl
new file mode 100644
index 0000000000..f7c7f4ffe6
--- /dev/null
+++ b/xpcom/threads/nsIRunnable.idl
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsISupports.idl"
+
+/**
+ * Represents a task which can be dispatched to a thread for execution.
+ */
+
+[scriptable, function, uuid(4a2abaf0-6886-11d3-9382-00104ba0fd40)]
+interface nsIRunnable : nsISupports
+{
+ /**
+ * The function implementing the task to be run.
+ */
+ void run();
+};
+
+[scriptable, uuid(e75aa42a-80a9-11e6-afb5-e89d87348e2c)]
+interface nsIRunnablePriority : nsISupports
+{
+ const unsigned short PRIORITY_IDLE = 0;
+ const unsigned short PRIORITY_DEFERRED_TIMERS = 1;
+ // INPUT_LOW isn't supposed to be used directly.
+ // const unsigned short PRIORITY_INPUT_LOW = 2;
+ const unsigned short PRIORITY_NORMAL = 3;
+ const unsigned short PRIORITY_MEDIUMHIGH = 4;
+ const unsigned short PRIORITY_INPUT_HIGH = 5;
+ const unsigned short PRIORITY_HIGH = 6;
+ readonly attribute unsigned long priority;
+};
+
+[builtinclass, uuid(3114c36c-a482-4c6e-9523-1dcfc6f605b9)]
+interface nsIRunnableIPCMessageType : nsISupports
+{
+ readonly attribute unsigned long type;
+};
diff --git a/xpcom/threads/nsISerialEventTarget.idl b/xpcom/threads/nsISerialEventTarget.idl
new file mode 100644
index 0000000000..9cf7768b37
--- /dev/null
+++ b/xpcom/threads/nsISerialEventTarget.idl
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIEventTarget.idl"
+
+/**
+ * A serial event target is an event dispatching interface like
+ * nsIEventTarget. Runnables dispatched to an nsISerialEventTarget are required
+ * to execute serially. That is, two different runnables dispatched to the
+ * target should never be allowed to execute simultaneously. One exception to
+ * this rule is nested event loops. If a runnable spins a nested event loop,
+ * causing another runnable dispatched to the target to run, the target may
+ * still be considered "serial".
+ *
+ * Examples:
+ * - nsIThread is a serial event target.
+ * - Thread pools are not serial event targets.
+ * - However, one can "convert" a thread pool into an nsISerialEventTarget
+ * by putting a TaskQueue in front of it.
+ */
+[builtinclass, scriptable, uuid(9f982380-24b4-49f3-88f6-45e2952036c7)]
+interface nsISerialEventTarget : nsIEventTarget
+{
+};
diff --git a/xpcom/threads/nsISupportsPriority.idl b/xpcom/threads/nsISupportsPriority.idl
new file mode 100644
index 0000000000..d0b8b9a3dd
--- /dev/null
+++ b/xpcom/threads/nsISupportsPriority.idl
@@ -0,0 +1,45 @@
+/* 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 "nsISupports.idl"
+
+/**
+ * This interface exposes the general notion of a scheduled object with a
+ * integral priority value. Following UNIX conventions, smaller (and possibly
+ * negative) values have higher priority.
+ *
+ * This interface does not strictly define what happens when the priority of an
+ * object is changed. An implementation of this interface is free to define
+ * the side-effects of changing the priority of an object. In some cases,
+ * changing the priority of an object may be disallowed (resulting in an
+ * exception being thrown) or may simply be ignored.
+ */
+[scriptable, uuid(aa578b44-abd5-4c19-8b14-36d4de6fdc36)]
+interface nsISupportsPriority : nsISupports
+{
+ /**
+ * Typical priority values.
+ */
+ const long PRIORITY_HIGHEST = -20;
+ const long PRIORITY_HIGH = -10;
+ const long PRIORITY_NORMAL = 0;
+ const long PRIORITY_LOW = 10;
+ const long PRIORITY_LOWEST = 20;
+
+ /**
+ * This attribute may be modified to change the priority of this object. The
+ * implementation of this interface is free to truncate a given priority
+ * value to whatever limits are appropriate. Typically, this attribute is
+ * initialized to PRIORITY_NORMAL, but implementations may choose to assign a
+ * different initial value.
+ */
+ attribute long priority;
+
+ /**
+ * This method adjusts the priority attribute by a given delta. It helps
+ * reduce the amount of coding required to increment or decrement the value
+ * of the priority attribute.
+ */
+ void adjustPriority(in long delta);
+};
diff --git a/xpcom/threads/nsIThread.idl b/xpcom/threads/nsIThread.idl
new file mode 100644
index 0000000000..ef3fd3258d
--- /dev/null
+++ b/xpcom/threads/nsIThread.idl
@@ -0,0 +1,213 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISerialEventTarget.idl"
+
+%{C++
+#include "mozilla/AlreadyAddRefed.h"
+namespace mozilla {
+class TimeStamp;
+class TimeDurationValueCalculator;
+template <typename T> class BaseTimeDuration;
+typedef BaseTimeDuration<TimeDurationValueCalculator> TimeDuration;
+enum class EventQueuePriority;
+}
+%}
+
+[ptr] native PRThread(PRThread);
+native EventQueuePriority(mozilla::EventQueuePriority);
+
+native nsIEventTargetPtr(nsIEventTarget*);
+native nsISerialEventTargetPtr(nsISerialEventTarget*);
+native TimeStamp(mozilla::TimeStamp);
+native TimeDuration(mozilla::TimeDuration);
+
+/**
+ * This interface provides a high-level abstraction for an operating system
+ * thread.
+ *
+ * Threads have a built-in event queue, and a thread is an event target that
+ * can receive nsIRunnable objects (events) to be processed on the thread.
+ *
+ * See nsIThreadManager for the API used to create and locate threads.
+ */
+[builtinclass, scriptable, uuid(5801d193-29d1-4964-a6b7-70eb697ddf2b)]
+interface nsIThread : nsISerialEventTarget
+{
+ /**
+ * @returns
+ * The NSPR thread object corresponding to this nsIThread.
+ */
+ [noscript] readonly attribute PRThread PRThread;
+
+ /**
+ * @returns
+ * Whether or not this thread may call into JS. Used in the profiler
+ * to avoid some unnecessary locking.
+ */
+ [noscript] attribute boolean CanInvokeJS;
+
+
+ /**
+ * Shutdown the thread. This method prevents further dispatch of events to
+ * the thread, and it causes any pending events to run to completion before
+ * the thread joins (see PR_JoinThread) with the current thread. During this
+ * method call, events for the current thread may be processed.
+ *
+ * This method MAY NOT be executed from the thread itself. Instead, it is
+ * meant to be executed from another thread (usually the thread that created
+ * this thread or the main application thread). When this function returns,
+ * the thread will be shutdown, and it will no longer be possible to dispatch
+ * events to the thread.
+ *
+ * @throws NS_ERROR_UNEXPECTED
+ * Indicates that this method was erroneously called when this thread was
+ * the current thread, that this thread was not created with a call to
+ * nsIThreadManager::NewThread, or if this method was called more than once
+ * on the thread object.
+ */
+ void shutdown();
+
+ /**
+ * This method may be called to determine if there are any events ready to be
+ * processed. It may only be called when this thread is the current thread.
+ *
+ * Because events may be added to this thread by another thread, a "false"
+ * result does not mean that this thread has no pending events. It only
+ * means that there were no pending events when this method was called.
+ *
+ * @returns
+ * A boolean value that if "true" indicates that this thread has one or
+ * more pending events.
+ *
+ * @throws NS_ERROR_UNEXPECTED
+ * Indicates that this method was erroneously called when this thread was
+ * not the current thread.
+ */
+ boolean hasPendingEvents();
+
+ /**
+ * Similar to above, but checks only possible high priority queue.
+ */
+ boolean hasPendingHighPriorityEvents();
+
+ /**
+ * Process the next event. If there are no pending events, then this method
+ * may wait -- depending on the value of the mayWait parameter -- until an
+ * event is dispatched to this thread. This method is re-entrant but may
+ * only be called if this thread is the current thread.
+ *
+ * @param mayWait
+ * A boolean parameter that if "true" indicates that the method may block
+ * the calling thread to wait for a pending event.
+ *
+ * @returns
+ * A boolean value that if "true" indicates that an event was processed.
+ *
+ * @throws NS_ERROR_UNEXPECTED
+ * Indicates that this method was erroneously called when this thread was
+ * not the current thread.
+ */
+ boolean processNextEvent(in boolean mayWait);
+
+ /**
+ * Shutdown the thread asynchronously. This method immediately prevents
+ * further dispatch of events to the thread, and it causes any pending events
+ * to run to completion before this thread joins with the current thread.
+ *
+ * UNLIKE shutdown() this does not process events on the current thread.
+ * Instead it merely ensures that the current thread continues running until
+ * this thread has shut down.
+ *
+ * This method MAY NOT be executed from the thread itself. Instead, it is
+ * meant to be executed from another thread (usually the thread that created
+ * this thread or the main application thread). When this function returns,
+ * the thread will continue running until it exhausts its event queue.
+ *
+ * @throws NS_ERROR_UNEXPECTED
+ * Indicates that this method was erroneously called when this thread was
+ * the current thread, that this thread was not created with a call to
+ * nsIThreadManager::NewThread, or if this method was called more than once
+ * on the thread object.
+ */
+ void asyncShutdown();
+
+ /**
+ * Dispatch an event to a specified queue for the thread. This function
+ * may be called from any thread, and it may be called re-entrantly.
+ * Most users should use the NS_Dispatch*() functions in nsThreadUtils instead
+ * of calling this directly.
+ *
+ * @param event
+ * The alreadyAddRefed<> event to dispatch.
+ * NOTE that the event will be leaked if it fails to dispatch.
+ * @param queue
+ * Which event priority queue this should be added to
+ *
+ * @throws NS_ERROR_INVALID_ARG
+ * Indicates that event is null.
+ * @throws NS_ERROR_UNEXPECTED
+ * Indicates that the thread is shutting down and has finished processing
+ * events, so this event would never run and has not been dispatched.
+ */
+ [noscript] void dispatchToQueue(in alreadyAddRefed_nsIRunnable event,
+ in EventQueuePriority queue);
+
+ /**
+ * Use this attribute to dispatch runnables to the thread. Eventually, the
+ * eventTarget attribute will be the only way to dispatch events to a
+ * thread--nsIThread will no longer inherit from nsIEventTarget.
+ */
+ readonly attribute nsIEventTarget eventTarget;
+
+ /**
+ * A fast C++ getter for the eventTarget.
+ */
+ [noscript,notxpcom] nsIEventTargetPtr EventTarget();
+
+ /**
+ * A fast C++ getter for the eventTarget. It asserts that the thread's event
+ * target is an nsISerialEventTarget and then returns it.
+ */
+ [noscript,notxpcom] nsISerialEventTargetPtr SerialEventTarget();
+
+ /**
+ * This is set to the end of the last 50+ms event that was executed on
+ * this thread (for MainThread only). Otherwise returns a null TimeStamp.
+ */
+ [noscript] readonly attribute TimeStamp lastLongTaskEnd;
+ [noscript] readonly attribute TimeStamp lastLongNonIdleTaskEnd;
+
+ /**
+ * Get information on the timing of the currently-running event.
+ *
+ * @param delay
+ * The amount of time the current running event in the specified queue waited
+ * to run. Will return TimeDuration() if the queue is empty or has not run any
+ * new events since event delay monitoring started. NOTE: delay will be
+ * TimeDuration() if this thread uses a PrioritizedEventQueue (i.e. MainThread)
+ * and the event priority is below Input.
+ * @param start
+ * The time the currently running event began to run, or TimeStamp() if no
+ * event is running.
+ */
+ [noscript] void getRunningEventDelay(out TimeDuration delay, out TimeStamp start);
+
+ /**
+ * Set information on the timing of the currently-running event.
+ * Overrides the values returned by getRunningEventDelay
+ *
+ * @param delay
+ * Delay the running event spent in queues, or TimeDuration() if
+ * there's no running event.
+ * @param start
+ * The time the currently running event began to run, or TimeStamp() if no
+ * event is running.
+ */
+ [noscript] void setRunningEventDelay(in TimeDuration delay, in TimeStamp start);
+
+ [noscript] void setNameForWakeupTelemetry(in ACString name);
+};
diff --git a/xpcom/threads/nsIThreadInternal.idl b/xpcom/threads/nsIThreadInternal.idl
new file mode 100644
index 0000000000..fca9e711b0
--- /dev/null
+++ b/xpcom/threads/nsIThreadInternal.idl
@@ -0,0 +1,110 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIThread.idl"
+
+interface nsIRunnable;
+interface nsIThreadObserver;
+
+/**
+ * The XPCOM thread object implements this interface, which allows a consumer
+ * to observe dispatch activity on the thread.
+ */
+[builtinclass, scriptable, uuid(a3a72e5f-71d9-4add-8f30-59a78fb6d5eb)]
+interface nsIThreadInternal : nsIThread
+{
+ /**
+ * Get/set the current thread observer (may be null). This attribute may be
+ * read from any thread, but must only be set on the thread corresponding to
+ * this thread object. The observer will be released on the thread
+ * corresponding to this thread object after all other events have been
+ * processed during a call to Shutdown.
+ */
+ attribute nsIThreadObserver observer;
+
+ /**
+ * 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);
+
+ /**
+ * Remove an observer added via the addObserver call. Once removed the
+ * observer will never be called again by the thread.
+ */
+ void removeObserver(in nsIThreadObserver observer);
+};
+
+/**
+ * This interface provides the observer with hooks to implement a layered
+ * event queue. For example, it is possible to overlay processing events
+ * for a GUI toolkit on top of the events for a thread:
+ *
+ * var NativeQueue;
+ * Observer = {
+ * onDispatchedEvent() {
+ * NativeQueue.signal();
+ * }
+ * onProcessNextEvent(thread, mayWait) {
+ * if (NativeQueue.hasNextEvent())
+ * NativeQueue.processNextEvent();
+ * while (mayWait && !thread.hasPendingEvent()) {
+ * NativeQueue.wait();
+ * NativeQueue.processNextEvent();
+ * }
+ * }
+ * };
+ *
+ * NOTE: The implementation of this interface must be threadsafe.
+ *
+ * NOTE: It is valid to change the thread's observer during a call to an
+ * observer method.
+ *
+ * NOTE: Will be split into two interfaces soon: one for onProcessNextEvent and
+ * afterProcessNextEvent, then another that inherits the first and adds
+ * onDispatchedEvent.
+ */
+[uuid(cc8da053-1776-44c2-9199-b5a629d0a19d)]
+interface nsIThreadObserver : nsISupports
+{
+ /**
+ * This method is called after an event has been dispatched to the thread.
+ * This method may be called from any thread.
+ */
+ void onDispatchedEvent();
+
+ /**
+ * 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.
+ * @param mayWait
+ * Indicates whether or not the method is allowed to block the calling
+ * thread. For example, this parameter is false during thread shutdown.
+ */
+ void onProcessNextEvent(in nsIThreadInternal thread, in boolean mayWait);
+
+ /**
+ * This method is called (from nsIThread::ProcessNextEvent) after an event
+ * 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. DO NOT EVER RUN SCRIPT FROM THIS CALLBACK!!!
+ *
+ * @param thread
+ * The thread that processed another event.
+ * @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 bool eventWasProcessed);
+};
diff --git a/xpcom/threads/nsIThreadManager.idl b/xpcom/threads/nsIThreadManager.idl
new file mode 100644
index 0000000000..86f7db6e3c
--- /dev/null
+++ b/xpcom/threads/nsIThreadManager.idl
@@ -0,0 +1,156 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[ptr] native PRThread(PRThread);
+
+interface nsIEventTarget;
+interface nsIRunnable;
+interface nsIThread;
+
+[scriptable, function, uuid(039a227d-0cb7-44a5-a8f9-dbb7071979f2)]
+interface nsINestedEventLoopCondition : nsISupports
+{
+ /**
+ * Returns true if the current nested event loop should stop spinning.
+ */
+ bool isDone();
+};
+
+/**
+ * An interface for creating and locating nsIThread instances.
+ */
+[scriptable, uuid(1be89eca-e2f7-453b-8d38-c11ba247f6f3)]
+interface nsIThreadManager : nsISupports
+{
+ /**
+ * Default number of bytes reserved for a thread's stack, if no stack size
+ * is specified in newThread().
+ *
+ * Defaults can be a little overzealous for many platforms.
+ *
+ * On Linux and OS X, for instance, the default thread stack size is whatever
+ * getrlimit(RLIMIT_STACK) returns, which is often set at 8MB. Or, on Linux,
+ * if the stack size is unlimited, we fall back to 2MB. This causes particular
+ * problems on Linux, which allocates 2MB huge VM pages, and will often
+ * immediately allocate them for any stacks which are 2MB or larger.
+ *
+ * The default on Windows is 1MB, which is a little more reasonable. But the
+ * vast majority of our threads don't need anywhere near that much space.
+ *
+ * ASan, TSan and non-opt builds, however, often need a bit more, so give
+ * them the platform default.
+ */
+%{C++
+#if defined(MOZ_ASAN) || defined(MOZ_TSAN) || !defined(__OPTIMIZE__)
+ static constexpr uint32_t DEFAULT_STACK_SIZE = 0;
+#else
+ static constexpr uint32_t DEFAULT_STACK_SIZE = 256 * 1024;
+#endif
+
+ static const uint32_t kThreadPoolStackSize = DEFAULT_STACK_SIZE;
+%}
+
+ /**
+ * Create a new thread (a global, user PRThread).
+ *
+ * @param creationFlags
+ * Reserved for future use. Pass 0.
+ * @param stackSize
+ * Number of bytes to reserve for the thread's stack. 0 means use platform
+ * default.
+ *
+ * @returns
+ * The newly created nsIThread object.
+ */
+ nsIThread newThread(in unsigned long creationFlags, [optional] in unsigned long stackSize);
+
+ /**
+ * Create a new thread (a global, user PRThread) with the specified name.
+ *
+ * @param name
+ * The name of the thread. Passing an empty name is equivalent to
+ * calling newThread(0, stackSize), i.e. the thread will not be named.
+ * @param stackSize
+ * Number of bytes to reserve for the thread's stack. 0 means use platform
+ * default.
+ *
+ * @returns
+ * The newly created nsIThread object.
+ */
+ [noscript] nsIThread newNamedThread(in ACString name, [optional] in unsigned long stackSize);
+
+ /**
+ * Get the main thread.
+ */
+ readonly attribute nsIThread mainThread;
+
+ /**
+ * Get the current thread. If the calling thread does not already have a
+ * nsIThread associated with it, then a new nsIThread will be created and
+ * associated with the current PRThread.
+ */
+ readonly attribute nsIThread currentThread;
+
+ /**
+ * This queues a runnable to the main thread. It's a shortcut for JS callers
+ * to be used instead of
+ * .mainThread.dispatch(runnable, Ci.nsIEventTarget.DISPATCH_NORMAL);
+ * or
+ * .currentThread.dispatch(runnable, Ci.nsIEventTarget.DISPATCH_NORMAL);
+ * C++ callers should instead use NS_DispatchToMainThread.
+ */
+ [optional_argc]
+ void dispatchToMainThread(in nsIRunnable event, [optional] in uint32_t priority);
+
+ /**
+ * This queues a runnable to the main thread's idle queue.
+ *
+ * @param event
+ * The event to dispatch.
+ * @param timeout
+ * The time in milliseconds until this event should be moved from the idle
+ * queue to the regular queue if it hasn't been executed by then. If not
+ * passed or a zero value is specified, the event will never be moved to
+ * the regular queue.
+ */
+ void idleDispatchToMainThread(in nsIRunnable event,
+ [optional] in uint32_t timeout);
+
+ /**
+ * Enter a nested event loop on the current thread, waiting on, and
+ * processing events until condition.isDone() returns true.
+ *
+ * If condition.isDone() throws, this function will throw as well.
+ *
+ * C++ code should not use this function, instead preferring
+ * mozilla::SpinEventLoopUntil.
+ */
+ void spinEventLoopUntil(in nsINestedEventLoopCondition condition);
+
+ /**
+ * Similar to the previous method, but the spinning of the event loop
+ * terminates when the shutting down starts.
+ *
+ * C++ code should not use this function, instead preferring
+ * mozilla::SpinEventLoopUntil.
+ */
+ void spinEventLoopUntilOrShutdown(in nsINestedEventLoopCondition condition);
+
+ /**
+ * Spin the current thread's event loop until there are no more pending
+ * events. This could be done with spinEventLoopUntil, but that would
+ * require access to the current thread from JavaScript, which we are
+ * moving away from.
+ */
+ void spinEventLoopUntilEmpty();
+
+ /**
+ * Return the EventTarget for the main thread.
+ */
+ readonly attribute nsIEventTarget mainThreadEventTarget;
+};
diff --git a/xpcom/threads/nsIThreadPool.idl b/xpcom/threads/nsIThreadPool.idl
new file mode 100644
index 0000000000..70922b7adf
--- /dev/null
+++ b/xpcom/threads/nsIThreadPool.idl
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIEventTarget.idl"
+
+[uuid(ef194cab-3f86-4b61-b132-e5e96a79e5d1)]
+interface nsIThreadPoolListener : nsISupports
+{
+ /**
+ * Called when a new thread is created by the thread pool. The notification
+ * happens on the newly-created thread.
+ */
+ void onThreadCreated();
+
+ /**
+ * Called when a thread is about to be destroyed by the thread pool. The
+ * notification happens on the thread that is about to be destroyed.
+ */
+ void onThreadShuttingDown();
+};
+
+/**
+ * An interface to a thread pool. A thread pool creates a limited number of
+ * anonymous (unnamed) worker threads. An event dispatched to the thread pool
+ * will be run on the next available worker thread.
+ */
+[uuid(76ce99c9-8e43-489a-9789-f27cc4424965)]
+interface nsIThreadPool : nsIEventTarget
+{
+ /**
+ * Shutdown the thread pool. This method may not be executed from any thread
+ * in the thread pool. Instead, it is meant to be executed from another
+ * 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();
+
+ /**
+ * Shutdown the thread pool, but only wait for aTimeoutMs. After the timeout
+ * expires, any threads that have not shutdown yet are leaked and will not
+ * block shutdown.
+ *
+ * This method should only be used at during shutdown to cleanup threads that
+ * made blocking calls to code outside our control, and can't be safely
+ * terminated. We choose to leak them intentionally to avoid a shutdown hang.
+ */
+ [noscript] void shutdownWithTimeout(in long aTimeoutMs);
+
+ /**
+ * Get/set the maximum number of threads allowed at one time in this pool.
+ */
+ attribute unsigned long threadLimit;
+
+ /**
+ * Get/set the maximum number of idle threads kept alive.
+ */
+ attribute unsigned long idleThreadLimit;
+
+ /**
+ * Get/set the amount of time in milliseconds before an idle thread is
+ * destroyed.
+ */
+ attribute unsigned long idleThreadTimeout;
+
+ /**
+ * If set to true the idle timeout will be calculated as idleThreadTimeout
+ * divideded by the number of idle threads at the moment. This may help
+ * save memory allocations but still keep reasonable amount of idle threads.
+ * Default is false, use |idleThreadTimeout| for all threads.
+ */
+ attribute boolean idleThreadTimeoutRegressive;
+
+ /**
+ * 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.
+ *
+ * A listener will only receive notifications about threads created after the
+ * listener is set so it is recommended that the consumer set the listener
+ * before dispatching the first event. A listener that receives an
+ * onThreadCreated() notification is guaranteed to always receive the
+ * corresponding onThreadShuttingDown() notification.
+ *
+ * The thread pool takes ownership of the listener and releases it when the
+ * shutdown() method is called. Threads created after the listener is set will
+ * also take ownership of the listener so that the listener will be kept alive
+ * long enough to receive the guaranteed onThreadShuttingDown() notification.
+ */
+ attribute nsIThreadPoolListener listener;
+
+ /**
+ * Set the label for threads in the pool. All threads will be named
+ * "<aName> #<n>", where <n> is a serial number.
+ */
+ void setName(in ACString aName);
+};
diff --git a/xpcom/threads/nsITimer.idl b/xpcom/threads/nsITimer.idl
new file mode 100644
index 0000000000..96ea2943a5
--- /dev/null
+++ b/xpcom/threads/nsITimer.idl
@@ -0,0 +1,341 @@
+/* -*- 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
+ * 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 "nsISupports.idl"
+
+interface nsIObserver;
+interface nsIEventTarget;
+
+%{C++
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/TimeStamp.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
+ * timer is initialized via initWithFuncCallback.
+ *
+ * @param aTimer the timer which has expired
+ * @param aClosure opaque parameter passed to initWithFuncCallback
+ */
+class nsITimer;
+typedef void (*nsTimerCallbackFunc) (nsITimer *aTimer, void *aClosure);
+
+/**
+ * The signature of the timer name callback function passed to
+ * initWithNameableFuncCallback.
+ * This is the function that will get called when timer profiling is enabled
+ * via the "TimerFirings" log module.
+ *
+ * @param aTimer the timer which has expired
+ * @param aAnonymize whether the name should avoid including privacy sensitive info
+ * @param aClosure opaque parameter passed to initWithFuncCallback
+ * @param aBuf a buffer in which to put the name
+ * @param aLen the length of the buffer
+ */
+typedef void (*nsTimerNameCallbackFunc) (nsITimer *aTimer,
+ bool aAnonymize,
+ void *aClosure,
+ char *aBuf, size_t aLen);
+%}
+
+native nsTimerCallbackFunc(nsTimerCallbackFunc);
+native nsTimerNameCallbackFunc(nsTimerNameCallbackFunc);
+[ref] native TimeDuration(mozilla::TimeDuration);
+
+/**
+ * The callback interface for timers.
+ */
+interface nsITimer;
+
+[function, scriptable, uuid(a796816d-7d47-4348-9ab8-c7aeb3216a7d)]
+interface nsITimerCallback : nsISupports
+{
+ /**
+ * @param aTimer the timer which has expired
+ */
+ void notify(in nsITimer timer);
+};
+
+%{C++
+// Two timer deadlines must differ by less than half the PRIntervalTime domain.
+#define DELAY_INTERVAL_LIMIT PR_BIT(8 * sizeof(PRIntervalTime) - 1)
+%}
+
+/**
+ * nsITimer instances must be initialized by calling one of the "init" methods
+ * documented below. You may also re-initialize (using one of the init()
+ * methods) an existing instance to avoid the overhead of destroying and
+ * creating a timer. It is not necessary to cancel the timer in that case.
+ *
+ * By default a timer will fire on the thread that created it. Set the .target
+ * attribute to fire on a different thread. Once you have set a timer's .target
+ * and called one of its init functions, any further interactions with the timer
+ * (calling cancel(), changing member fields, etc) should only be done by the
+ * target thread, or races may occur with bad results like timers firing after
+ * they've been canceled, and/or not firing after re-initiatization.
+ */
+[scriptable, builtinclass, uuid(3de4b105-363c-482c-a409-baac83a01bfc)]
+interface nsITimer : nsISupports
+{
+ /* Timer types */
+
+ /**
+ * Type of a timer that fires once only.
+ */
+ const short TYPE_ONE_SHOT = 0;
+
+ /**
+ * After firing, a TYPE_REPEATING_SLACK timer is stopped and not restarted
+ * until its callback completes. Specified timer period will be at least
+ * the time between when processing for last firing the callback completes
+ * and when the next firing occurs.
+ *
+ * This is the preferable repeating type for most situations.
+ */
+ 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. So the distinction was removed.
+ */
+ const short TYPE_REPEATING_PRECISE = 2;
+
+ /**
+ * A TYPE_REPEATING_PRECISE_CAN_SKIP repeating timer aims to have constant
+ * period between firings. The processing time for each timer callback
+ * should not influence the timer period. However this timer type
+ * 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.
+ */
+ const short TYPE_REPEATING_PRECISE_CAN_SKIP = 3;
+
+ /**
+ * Same as TYPE_REPEATING_SLACK with the exception that idle events
+ * won't yield to timers with this type. Use this when you want an
+ * idle callback to be scheduled to run even though this timer is
+ * about to fire.
+ */
+ const short TYPE_REPEATING_SLACK_LOW_PRIORITY = 4;
+
+ /**
+ * Same as TYPE_ONE_SHOT with the exception that idle events won't
+ * yield to timers with this type. Use this when you want an idle
+ * callback to be scheduled to run even though this timer is about
+ * to fire.
+ */
+ const short TYPE_ONE_SHOT_LOW_PRIORITY = 5;
+
+ /**
+ * Initialize a timer that will fire after the said delay.
+ * A user must keep a reference to this timer till it is
+ * is no longer needed or has been cancelled.
+ *
+ * @param aObserver the callback object that observes the
+ * ``timer-callback'' topic with the subject being
+ * the timer itself when the timer fires:
+ *
+ * observe(nsISupports aSubject, => nsITimer
+ * string aTopic, => ``timer-callback''
+ * wstring data => null
+ *
+ * @param aDelayInMs delay in milliseconds for timer to fire
+ * @param aType timer type per TYPE* consts defined above
+ */
+ void init(in nsIObserver aObserver, in unsigned long aDelayInMs,
+ in unsigned long aType);
+
+
+ /**
+ * Initialize a timer to fire after the given millisecond interval.
+ * This version takes a callback object.
+ *
+ * @param aFunc nsITimerCallback interface to call when timer expires
+ * @param aDelayInMs The millisecond interval
+ * @param aType Timer type per TYPE* consts defined above
+ */
+ void initWithCallback(in nsITimerCallback aCallback,
+ in unsigned long aDelayInMs,
+ in unsigned long aType);
+
+ /**
+ * Initialize a timer to fire after the high resolution TimeDuration.
+ * This version takes a callback object.
+ *
+ * @param aFunc nsITimerCallback interface to call when timer expires
+ * @param aDelay The high resolution interval
+ * @param aType Timer type per TYPE* consts defined above
+ */
+ [noscript] void InitHighResolutionWithCallback(in nsITimerCallback aCallback,
+ [const] in TimeDuration aDelay,
+ in unsigned long aType);
+
+ /**
+ * Cancel the timer. This method works on all types, not just on repeating
+ * timers -- you might want to cancel a TYPE_ONE_SHOT timer, and even reuse
+ * it by re-initializing it (to avoid object destruction and creation costs
+ * by conserving one timer instance).
+ */
+ void cancel();
+
+ /**
+ * Like initWithFuncCallback, but also takes a name for the timer; the name
+ * will be used when timer profiling is enabled via the "TimerFirings" log
+ * module.
+ *
+ * @param aFunc The function to invoke
+ * @param aClosure An opaque pointer to pass to that function
+ * @param aDelay The millisecond interval
+ * @param aType Timer type per TYPE* consts defined above
+ * @param aName The timer's name
+ */
+ [noscript] void initWithNamedFuncCallback(in nsTimerCallbackFunc aCallback,
+ in voidPtr aClosure,
+ in unsigned long aDelay,
+ in unsigned long aType,
+ in string aName);
+
+ /**
+ * Like initWithNamedFuncCallback, but instead of a timer name it takes a
+ * callback that will provide a name when the timer fires.
+ *
+ * @param aFunc The function to invoke
+ * @param aClosure An opaque pointer to pass to that function
+ * @param aDelay The millisecond interval
+ * @param aType Timer type per TYPE* consts defined above
+ * @param aNameCallback The callback function
+ */
+ [noscript] void initWithNameableFuncCallback(
+ in nsTimerCallbackFunc aCallback,
+ in voidPtr aClosure,
+ in unsigned long aDelay,
+ in unsigned long aType,
+ in nsTimerNameCallbackFunc aNameCallback);
+
+ /**
+ * The millisecond delay of the timeout.
+ *
+ * NOTE: Re-setting the delay on a one-shot timer that has already fired
+ * doesn't restart the timer. Call one of the init() methods to restart
+ * a one-shot timer.
+ */
+ attribute unsigned long delay;
+
+ /**
+ * The timer type - one of the above TYPE_* constants.
+ */
+ attribute unsigned long type;
+
+ /**
+ * The opaque pointer pass to initWithFuncCallback.
+ */
+ [noscript] readonly attribute voidPtr closure;
+
+ /**
+ * The nsITimerCallback object passed to initWithCallback.
+ */
+ readonly attribute nsITimerCallback callback;
+
+ /**
+ * The nsIEventTarget where the callback will be dispatched. Note that this
+ * target may only be set before the call to one of the init methods above.
+ *
+ * By default the target is the thread that created the timer.
+ */
+ attribute nsIEventTarget target;
+
+ /**
+ * The number of microseconds this nsITimer implementation can possibly
+ * fire early.
+ */
+ [noscript] readonly attribute unsigned long allowedEarlyFiringMicroseconds;
+
+%{C++
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const = 0;
+%}
+};
+
+%{C++
+#include "nsCOMPtr.h"
+
+already_AddRefed<nsITimer> NS_NewTimer();
+
+already_AddRefed<nsITimer> NS_NewTimer(nsIEventTarget* aTarget);
+
+nsresult
+NS_NewTimerWithObserver(nsITimer** aTimer,
+ nsIObserver* aObserver,
+ uint32_t aDelay,
+ uint32_t aType,
+ nsIEventTarget* aTarget = nullptr);
+mozilla::Result<nsCOMPtr<nsITimer>, nsresult>
+NS_NewTimerWithObserver(nsIObserver* aObserver,
+ uint32_t aDelay,
+ uint32_t aType,
+ nsIEventTarget* aTarget = nullptr);
+
+nsresult
+NS_NewTimerWithCallback(nsITimer** aTimer,
+ nsITimerCallback* aCallback,
+ uint32_t aDelay,
+ uint32_t aType,
+ nsIEventTarget* aTarget = nullptr);
+mozilla::Result<nsCOMPtr<nsITimer>, nsresult>
+NS_NewTimerWithCallback(nsITimerCallback* aCallback,
+ uint32_t aDelay,
+ uint32_t aType,
+ nsIEventTarget* aTarget = nullptr);
+
+nsresult
+NS_NewTimerWithCallback(nsITimer** aTimer,
+ nsITimerCallback* aCallback,
+ const mozilla::TimeDuration& aDelay,
+ uint32_t aType,
+ nsIEventTarget* aTarget = nullptr);
+mozilla::Result<nsCOMPtr<nsITimer>, nsresult>
+NS_NewTimerWithCallback(nsITimerCallback* aCallback,
+ const mozilla::TimeDuration& aDelay,
+ uint32_t aType,
+ nsIEventTarget* aTarget = nullptr);
+
+nsresult
+NS_NewTimerWithFuncCallback(nsITimer** aTimer,
+ nsTimerCallbackFunc aCallback,
+ void* aClosure,
+ uint32_t aDelay,
+ uint32_t aType,
+ const char* aNameString,
+ nsIEventTarget* aTarget = nullptr);
+mozilla::Result<nsCOMPtr<nsITimer>, nsresult>
+NS_NewTimerWithFuncCallback(nsTimerCallbackFunc aCallback,
+ void* aClosure,
+ uint32_t aDelay,
+ uint32_t aType,
+ const char* aNameString,
+ nsIEventTarget* aTarget = nullptr);
+
+nsresult
+NS_NewTimerWithFuncCallback(nsITimer** aTimer,
+ nsTimerCallbackFunc aCallback,
+ void* aClosure,
+ uint32_t aDelay,
+ uint32_t aType,
+ nsTimerNameCallbackFunc aNameCallback,
+ nsIEventTarget* aTarget = nullptr);
+mozilla::Result<nsCOMPtr<nsITimer>, nsresult>
+NS_NewTimerWithFuncCallback(nsTimerCallbackFunc aCallback,
+ void* aClosure,
+ uint32_t aDelay,
+ uint32_t aType,
+ nsTimerNameCallbackFunc aNameCallback,
+ nsIEventTarget* aTarget = nullptr);
+
+#define NS_TIMER_CONTRACTID "@mozilla.org/timer;1"
+#define NS_TIMER_CALLBACK_TOPIC "timer-callback"
+%}
diff --git a/xpcom/threads/nsMemoryPressure.cpp b/xpcom/threads/nsMemoryPressure.cpp
new file mode 100644
index 0000000000..4f0ee3a332
--- /dev/null
+++ b/xpcom/threads/nsMemoryPressure.cpp
@@ -0,0 +1,49 @@
+/* -*- 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 (or stop of) memory pressure,
+ * but an existing "new" memory pressure event takes precedence over a new
+ * "ongoing" or "stop" memory pressure event.
+ */
+ switch (aState) {
+ case MemPressure_None:
+ sMemoryPressurePending = MemPressure_None;
+ break;
+ case MemPressure_New:
+ sMemoryPressurePending = MemPressure_New;
+ break;
+ case MemPressure_Ongoing:
+ case MemPressure_Stopping:
+ sMemoryPressurePending.compareExchange(MemPressure_None, aState);
+ break;
+ }
+}
+
+nsresult NS_DispatchMemoryPressure(MemoryPressureState aState) {
+ NS_DispatchEventualMemoryPressure(aState);
+ nsCOMPtr<nsIRunnable> event =
+ new Runnable("NS_DispatchEventualMemoryPressure");
+ return NS_DispatchToMainThread(event);
+}
diff --git a/xpcom/threads/nsMemoryPressure.h b/xpcom/threads/nsMemoryPressure.h
new file mode 100644
index 0000000000..eadde20bb3
--- /dev/null
+++ b/xpcom/threads/nsMemoryPressure.h
@@ -0,0 +1,87 @@
+/* -*- 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
+ * Gecko 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 Gecko alive as long as possible.
+ */
+ MemPressure_Ongoing,
+
+ /*
+ * Memory pressure stopped.
+ *
+ * We're no longer under acute memory pressure, so we might want to have a
+ * chance of (cautiously) re-enabling some things we previously turned off.
+ * As above, an already enqueued new memory pressure event takes precedence.
+ * The priority ordering between concurrent attempts to queue both stopped
+ * and ongoing memory pressure is currently not defined.
+ */
+ MemPressure_Stopping
+};
+
+/**
+ * Return and erase the latest state of the memory pressure event set by any of
+ * the corresponding dispatch functions.
+ *
+ * This is called when processing events on the main thread to check whether to
+ * fire a memory pressure notification.
+ */
+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
new file mode 100644
index 0000000000..c7cd0a3d05
--- /dev/null
+++ b/xpcom/threads/nsProcess.h
@@ -0,0 +1,81 @@
+/* -*- 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 _nsPROCESSWIN_H_
+#define _nsPROCESSWIN_H_
+
+#if defined(XP_WIN)
+# define PROCESSMODEL_WINAPI
+#endif
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Mutex.h"
+#include "nsIProcess.h"
+#include "nsIObserver.h"
+#include "nsMaybeWeakPtr.h"
+#include "nsString.h"
+#ifndef XP_UNIX
+# include "prproces.h"
+#endif
+#if defined(PROCESSMODEL_WINAPI)
+# include <windows.h>
+# include <shellapi.h>
+#endif
+
+#define NS_PROCESS_CID \
+ { \
+ 0x7b4eeb20, 0xd781, 0x11d4, { \
+ 0x8A, 0x83, 0x00, 0x10, 0xa4, 0xe0, 0xc9, 0xca \
+ } \
+ }
+
+class nsIFile;
+
+class nsProcess final : public nsIProcess, public nsIObserver {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPROCESS
+ NS_DECL_NSIOBSERVER
+
+ nsProcess();
+
+ private:
+ ~nsProcess();
+ static void Monitor(void* aArg);
+ void ProcessComplete();
+ 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 aBlocking, char** aArgs, nsIObserver* aObserver,
+ bool aHoldWeak, bool aArgsUTF8);
+
+ PRThread* mThread;
+ mozilla::Mutex mLock;
+ bool mShutdown;
+ bool mBlocking;
+ bool mStartHidden;
+ bool mNoShell;
+
+ nsCOMPtr<nsIFile> mExecutable;
+ nsString mTargetPath;
+ int32_t mPid;
+ nsMaybeWeakPtr<nsIObserver> mObserver;
+
+ // These members are modified by multiple threads, any accesses should be
+ // protected with mLock.
+ int32_t mExitValue;
+#if defined(PROCESSMODEL_WINAPI)
+ HANDLE mProcess;
+#elif !defined(XP_UNIX)
+ PRProcess* mProcess;
+#endif
+};
+
+#endif
diff --git a/xpcom/threads/nsProcessCommon.cpp b/xpcom/threads/nsProcessCommon.cpp
new file mode 100644
index 0000000000..10da15fd7e
--- /dev/null
+++ b/xpcom/threads/nsProcessCommon.cpp
@@ -0,0 +1,566 @@
+/* -*- 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/ArrayUtils.h"
+
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsMemory.h"
+#include "nsProcess.h"
+#include "prio.h"
+#include "prenv.h"
+#include "nsCRT.h"
+#include "nsThreadUtils.h"
+#include "nsIObserverService.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/Services.h"
+#include "GeckoProfiler.h"
+
+#include <stdlib.h>
+
+#if defined(PROCESSMODEL_WINAPI)
+# include "nsString.h"
+# include "nsLiteralString.h"
+# include "nsReadableUtils.h"
+# include "mozilla/AssembleCmdLine.h"
+# include "mozilla/UniquePtrExtensions.h"
+#else
+# ifdef XP_MACOSX
+# include <crt_externs.h>
+# include <spawn.h>
+# endif
+# ifdef XP_UNIX
+# ifndef XP_MACOSX
+# include "base/process_util.h"
+# endif
+# include <sys/wait.h>
+# include <sys/errno.h>
+# endif
+# include <sys/types.h>
+# include <signal.h>
+#endif
+
+using namespace mozilla;
+
+//-------------------------------------------------------------------//
+// nsIProcess implementation
+//-------------------------------------------------------------------//
+NS_IMPL_ISUPPORTS(nsProcess, nsIProcess, nsIObserver)
+
+// Constructor
+nsProcess::nsProcess()
+ : mThread(nullptr),
+ mLock("nsProcess.mLock"),
+ mShutdown(false),
+ mBlocking(false),
+ mStartHidden(false),
+ mNoShell(false),
+ mPid(-1),
+ mExitValue(-1)
+#if !defined(XP_UNIX)
+ ,
+ mProcess(nullptr)
+#endif
+{
+}
+
+// Destructor
+nsProcess::~nsProcess() = default;
+
+NS_IMETHODIMP
+nsProcess::Init(nsIFile* aExecutable) {
+ 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);
+
+ return rv;
+}
+
+void nsProcess::Monitor(void* aArg) {
+ RefPtr<nsProcess> process = dont_AddRef(static_cast<nsProcess*>(aArg));
+
+#ifdef MOZ_GECKO_PROFILER
+ Maybe<AutoProfilerRegisterThread> registerThread;
+ if (!process->mBlocking) {
+ registerThread.emplace("RunProcess");
+ }
+#endif
+ if (!process->mBlocking) {
+ NS_SetCurrentThreadName("RunProcess");
+ }
+
+#if defined(PROCESSMODEL_WINAPI)
+ DWORD dwRetVal;
+ unsigned long 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 = nullptr;
+ process->mExitValue = exitCode;
+ if (process->mShutdown) {
+ return;
+ }
+ }
+#else
+# ifdef XP_UNIX
+ 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;
+ }
+# endif
+
+ // Lock in case Kill or GetExitCode are called during this
+ {
+ MutexAutoLock lock(process->mLock);
+# if !defined(XP_UNIX)
+ process->mProcess = nullptr;
+# endif
+ 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 {
+ NS_DispatchToMainThread(NewRunnableMethod(
+ "nsProcess::ProcessComplete", process, &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;
+ }
+
+ const char* topic;
+ if (mExitValue != 0) {
+ topic = "process-failed";
+ } else {
+ topic = "process-finished";
+ }
+
+ mPid = -1;
+ nsCOMPtr<nsIObserver> observer = mObserver.GetValue();
+ mObserver = nullptr;
+
+ if (observer) {
+ observer->Observe(NS_ISUPPORTS_CAST(nsIProcess*, this), topic, nullptr);
+ }
+}
+
+// XXXldb |aArgs| has the wrong const-ness
+NS_IMETHODIMP
+nsProcess::Run(bool aBlocking, const char** aArgs, uint32_t aCount) {
+ return CopyArgsAndRunProcess(aBlocking, aArgs, aCount, nullptr, false);
+}
+
+// XXXldb |aArgs| has the wrong const-ness
+NS_IMETHODIMP
+nsProcess::RunAsync(const char** aArgs, uint32_t aCount, nsIObserver* aObserver,
+ bool aHoldWeak) {
+ return CopyArgsAndRunProcess(false, aArgs, aCount, aObserver, aHoldWeak);
+}
+
+nsresult nsProcess::CopyArgsAndRunProcess(bool aBlocking, const char** aArgs,
+ uint32_t aCount,
+ nsIObserver* aObserver,
+ bool aHoldWeak) {
+ // Add one to the aCount for the program name and one for null termination.
+ char** my_argv = nullptr;
+ my_argv = (char**)moz_xmalloc(sizeof(char*) * (aCount + 2));
+
+ my_argv[0] = ToNewUTF8String(mTargetPath);
+
+ for (uint32_t i = 0; i < aCount; ++i) {
+ my_argv[i + 1] = const_cast<char*>(aArgs[i]);
+ }
+
+ my_argv[aCount + 1] = nullptr;
+
+ nsresult rv = RunProcess(aBlocking, my_argv, aObserver, aHoldWeak, false);
+
+ free(my_argv[0]);
+ free(my_argv);
+ return rv;
+}
+
+// XXXldb |aArgs| has the wrong const-ness
+NS_IMETHODIMP
+nsProcess::Runw(bool aBlocking, const char16_t** aArgs, uint32_t aCount) {
+ return CopyArgsAndRunProcessw(aBlocking, aArgs, aCount, nullptr, false);
+}
+
+// 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, aArgs, aCount, aObserver, aHoldWeak);
+}
+
+nsresult nsProcess::CopyArgsAndRunProcessw(bool aBlocking,
+ const char16_t** aArgs,
+ uint32_t aCount,
+ nsIObserver* aObserver,
+ bool aHoldWeak) {
+ // Add one to the aCount for the program name and one for null termination.
+ char** my_argv = nullptr;
+ my_argv = (char**)moz_xmalloc(sizeof(char*) * (aCount + 2));
+
+ my_argv[0] = ToNewUTF8String(mTargetPath);
+
+ for (uint32_t i = 0; i < aCount; i++) {
+ my_argv[i + 1] = ToNewUTF8String(nsDependentString(aArgs[i]));
+ }
+
+ my_argv[aCount + 1] = nullptr;
+
+ nsresult rv = RunProcess(aBlocking, my_argv, aObserver, aHoldWeak, true);
+
+ for (uint32_t i = 0; i <= aCount; ++i) {
+ free(my_argv[i]);
+ }
+ free(my_argv);
+ return rv;
+}
+
+nsresult nsProcess::RunProcess(bool aBlocking, char** aMyArgv,
+ nsIObserver* aObserver, bool aHoldWeak,
+ bool aArgsUTF8) {
+ NS_WARNING_ASSERTION(!XRE_IsContentProcess(),
+ "No launching of new processes in the content process");
+
+ if (NS_WARN_IF(!mExecutable)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ if (NS_WARN_IF(mThread)) {
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+
+ if (aObserver) {
+ if (aHoldWeak) {
+ nsresult rv = NS_OK;
+ mObserver = do_GetWeakReference(aObserver, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ mObserver = aObserver;
+ }
+ }
+
+ mExitValue = -1;
+ mPid = -1;
+
+#if defined(PROCESSMODEL_WINAPI)
+ BOOL retVal;
+ UniqueFreePtr<wchar_t> cmdLine;
+
+ // |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] || mNoShell) {
+ // Pass the executable path as argv[0] to the launched program when calling
+ // CreateProcess().
+ char** argv = mNoShell ? aMyArgv : aMyArgv + 1;
+
+ wchar_t* assembledCmdLine = nullptr;
+ if (assembleCmdLine(argv, &assembledCmdLine,
+ aArgsUTF8 ? CP_UTF8 : CP_ACP) == -1) {
+ return NS_ERROR_FILE_EXECUTION_FAILED;
+ }
+ cmdLine.reset(assembledCmdLine);
+ }
+
+ // The program name in aMyArgv[0] is always UTF-8
+ NS_ConvertUTF8toUTF16 wideFile(aMyArgv[0]);
+
+ if (mNoShell) {
+ STARTUPINFO startupInfo;
+ ZeroMemory(&startupInfo, sizeof(startupInfo));
+ startupInfo.cb = sizeof(startupInfo);
+ startupInfo.dwFlags = STARTF_USESHOWWINDOW;
+ startupInfo.wShowWindow = mStartHidden ? SW_HIDE : SW_SHOWNORMAL;
+
+ PROCESS_INFORMATION processInfo;
+ retVal = CreateProcess(/* lpApplicationName = */ wideFile.get(),
+ /* lpCommandLine */ cmdLine.get(),
+ /* lpProcessAttributes = */ NULL,
+ /* lpThreadAttributes = */ NULL,
+ /* bInheritHandles = */ FALSE,
+ /* dwCreationFlags = */ 0,
+ /* lpEnvironment = */ NULL,
+ /* lpCurrentDirectory = */ NULL,
+ /* lpStartupInfo = */ &startupInfo,
+ /* lpProcessInformation */ &processInfo);
+
+ if (!retVal) {
+ return NS_ERROR_FILE_EXECUTION_FAILED;
+ }
+
+ CloseHandle(processInfo.hThread);
+
+ mProcess = processInfo.hProcess;
+ } else {
+ SHELLEXECUTEINFOW sinfo;
+ memset(&sinfo, 0, sizeof(SHELLEXECUTEINFOW));
+ sinfo.cbSize = sizeof(SHELLEXECUTEINFOW);
+ sinfo.hwnd = nullptr;
+ sinfo.lpFile = wideFile.get();
+ sinfo.nShow = mStartHidden ? SW_HIDE : SW_SHOWNORMAL;
+
+ /* 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.
+ */
+ sinfo.fMask =
+ SEE_MASK_FLAG_DDEWAIT | SEE_MASK_NO_CONSOLE | SEE_MASK_NOCLOSEPROCESS;
+
+ if (cmdLine) {
+ sinfo.lpParameters = cmdLine.get();
+ }
+
+ retVal = ShellExecuteExW(&sinfo);
+ if (!retVal) {
+ return NS_ERROR_FILE_EXECUTION_FAILED;
+ }
+
+ mProcess = sinfo.hProcess;
+ }
+
+ mPid = GetProcessId(mProcess);
+#elif defined(XP_MACOSX)
+ // Note: |aMyArgv| is already null-terminated as required by posix_spawnp.
+ pid_t newPid = 0;
+ int result = posix_spawnp(&newPid, aMyArgv[0], nullptr, nullptr, aMyArgv,
+ *_NSGetEnviron());
+ mPid = static_cast<int32_t>(newPid);
+
+ if (result != 0) {
+ return NS_ERROR_FAILURE;
+ }
+#elif defined(XP_UNIX)
+ base::LaunchOptions options;
+ std::vector<std::string> argvVec;
+ for (char** arg = aMyArgv; *arg != nullptr; ++arg) {
+ argvVec.push_back(*arg);
+ }
+ pid_t newPid;
+ if (base::LaunchApp(argvVec, options, &newPid)) {
+ static_assert(sizeof(pid_t) <= sizeof(int32_t),
+ "mPid is large enough to hold a pid");
+ mPid = static_cast<int32_t>(newPid);
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+#else
+ 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 = 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;
+ }
+
+ // 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;
+}
+
+NS_IMETHODIMP
+nsProcess::GetIsRunning(bool* aIsRunning) {
+ if (mThread) {
+ *aIsRunning = true;
+ } else {
+ *aIsRunning = false;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProcess::GetStartHidden(bool* aStartHidden) {
+ *aStartHidden = mStartHidden;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProcess::SetStartHidden(bool aStartHidden) {
+ mStartHidden = aStartHidden;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProcess::GetNoShell(bool* aNoShell) {
+ *aNoShell = mNoShell;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProcess::SetNoShell(bool aNoShell) {
+ mNoShell = aNoShell;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProcess::GetPid(uint32_t* aPid) {
+ 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;
+ }
+
+ {
+ MutexAutoLock lock(mLock);
+#if defined(PROCESSMODEL_WINAPI)
+ if (TerminateProcess(mProcess, 0) == 0) {
+ return NS_ERROR_FAILURE;
+ }
+#elif defined(XP_UNIX)
+ if (kill(mPid, SIGKILL) != 0) {
+ return NS_ERROR_FAILURE;
+ }
+#else
+ if (!mProcess || (PR_KillProcess(mProcess) != PR_SUCCESS)) {
+ return NS_ERROR_FAILURE;
+ }
+#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) {
+ MutexAutoLock lock(mLock);
+
+ *aExitValue = mExitValue;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+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;
+ }
+
+ mObserver = nullptr;
+
+ MutexAutoLock lock(mLock);
+ mShutdown = true;
+
+ return NS_OK;
+}
diff --git a/xpcom/threads/nsProxyRelease.cpp b/xpcom/threads/nsProxyRelease.cpp
new file mode 100644
index 0000000000..b53fe4c022
--- /dev/null
+++ b/xpcom/threads/nsProxyRelease.cpp
@@ -0,0 +1,30 @@
+/* -*- 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 "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+
+namespace detail {
+
+/* static */ void ProxyReleaseChooser<true>::ProxyReleaseISupports(
+ const char* aName, nsIEventTarget* aTarget, nsISupports* aDoomed,
+ bool aAlwaysProxy) {
+ ::detail::ProxyRelease<nsISupports>(aName, aTarget, dont_AddRef(aDoomed),
+ aAlwaysProxy);
+}
+
+} // namespace detail
+
+extern "C" {
+
+// This function uses C linkage because it's exposed to Rust to support the
+// `ThreadPtrHolder` wrapper in the `moz_task` crate.
+void NS_ProxyReleaseISupports(const char* aName, nsIEventTarget* aTarget,
+ nsISupports* aDoomed, bool aAlwaysProxy) {
+ NS_ProxyRelease(aName, aTarget, dont_AddRef(aDoomed), aAlwaysProxy);
+}
+
+} // extern "C"
diff --git a/xpcom/threads/nsProxyRelease.h b/xpcom/threads/nsProxyRelease.h
new file mode 100644
index 0000000000..0c132d362b
--- /dev/null
+++ b/xpcom/threads/nsProxyRelease.h
@@ -0,0 +1,384 @@
+/* -*- 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 nsProxyRelease_h__
+#define nsProxyRelease_h__
+
+#include <utility>
+
+#include "MainThreadUtils.h"
+#include "mozilla/Likely.h"
+#include "mozilla/Unused.h"
+#include "nsCOMPtr.h"
+#include "nsIEventTarget.h"
+#include "nsISerialEventTarget.h"
+#include "nsIThread.h"
+#include "nsPrintfCString.h"
+#include "nsThreadUtils.h"
+
+#ifdef XPCOM_GLUE_AVOID_NSPR
+# error NS_ProxyRelease implementation depends on NSPR.
+#endif
+
+class nsIRunnable;
+
+namespace detail {
+
+template <typename T>
+class ProxyReleaseEvent : public mozilla::CancelableRunnable {
+ public:
+ ProxyReleaseEvent(const char* aName, already_AddRefed<T> aDoomed)
+ : CancelableRunnable(aName), mDoomed(aDoomed.take()) {}
+
+ NS_IMETHOD Run() override {
+ NS_IF_RELEASE(mDoomed);
+ return NS_OK;
+ }
+
+ nsresult Cancel() override { return Run(); }
+
+#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+ NS_IMETHOD GetName(nsACString& aName) override {
+ if (mName) {
+ aName.Append(nsPrintfCString("ProxyReleaseEvent for %s", mName));
+ } else {
+ aName.AssignLiteral("ProxyReleaseEvent");
+ }
+ return NS_OK;
+ }
+#endif
+
+ private:
+ T* MOZ_OWNING_REF mDoomed;
+};
+
+template <typename T>
+void ProxyRelease(const char* aName, nsIEventTarget* aTarget,
+ already_AddRefed<T> aDoomed, bool aAlwaysProxy) {
+ // Auto-managing release of the pointer.
+ RefPtr<T> doomed = aDoomed;
+ nsresult rv;
+
+ if (!doomed || !aTarget) {
+ return;
+ }
+
+ if (!aAlwaysProxy) {
+ bool onCurrentThread = false;
+ rv = aTarget->IsOnCurrentThread(&onCurrentThread);
+ if (NS_SUCCEEDED(rv) && onCurrentThread) {
+ return;
+ }
+ }
+
+ nsCOMPtr<nsIRunnable> ev = new ProxyReleaseEvent<T>(aName, doomed.forget());
+
+ rv = aTarget->Dispatch(ev, NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to post proxy release event, leaking!");
+ // It is better to leak the aDoomed object than risk crashing as
+ // a result of deleting it on the wrong thread.
+ }
+}
+
+template <bool nsISupportsBased>
+struct ProxyReleaseChooser {
+ template <typename T>
+ static void ProxyRelease(const char* aName, nsIEventTarget* aTarget,
+ already_AddRefed<T> aDoomed, bool aAlwaysProxy) {
+ ::detail::ProxyRelease(aName, aTarget, std::move(aDoomed), aAlwaysProxy);
+ }
+};
+
+template <>
+struct ProxyReleaseChooser<true> {
+ // We need an intermediate step for handling classes with ambiguous
+ // inheritance to nsISupports.
+ template <typename T>
+ static void ProxyRelease(const char* aName, nsIEventTarget* aTarget,
+ already_AddRefed<T> aDoomed, bool aAlwaysProxy) {
+ ProxyReleaseISupports(aName, aTarget, ToSupports(aDoomed.take()),
+ aAlwaysProxy);
+ }
+
+ static void ProxyReleaseISupports(const char* aName, nsIEventTarget* aTarget,
+ nsISupports* aDoomed, bool aAlwaysProxy);
+};
+
+} // namespace detail
+
+/**
+ * Ensures that the delete of a smart pointer occurs on the target thread.
+ *
+ * @param aName
+ * the labelling name of the runnable involved in the releasing.
+ * @param aTarget
+ * the target thread where the doomed object should be released.
+ * @param aDoomed
+ * the doomed object; the object to be released on the target thread.
+ * @param aAlwaysProxy
+ * normally, if NS_ProxyRelease is called on the target thread, then the
+ * doomed object will be released directly. However, if this parameter is
+ * true, then an event will always be posted to the target thread for
+ * asynchronous release.
+ */
+template <class T>
+inline NS_HIDDEN_(void)
+ NS_ProxyRelease(const char* aName, nsIEventTarget* aTarget,
+ already_AddRefed<T> aDoomed, bool aAlwaysProxy = false) {
+ ::detail::ProxyReleaseChooser<
+ std::is_base_of<nsISupports, T>::value>::ProxyRelease(aName, aTarget,
+ std::move(aDoomed),
+ aAlwaysProxy);
+}
+
+/**
+ * Ensures that the delete of a smart pointer occurs on the main thread.
+ *
+ * @param aName
+ * the labelling name of the runnable involved in the releasing
+ * @param aDoomed
+ * the doomed object; the object to be released on the main thread.
+ * @param aAlwaysProxy
+ * normally, if NS_ReleaseOnMainThread is called on the main
+ * thread, then the doomed object will be released directly. However, if
+ * this parameter is true, then an event will always be posted to the
+ * main thread for asynchronous release.
+ */
+template <class T>
+inline NS_HIDDEN_(void)
+ NS_ReleaseOnMainThread(const char* aName, already_AddRefed<T> aDoomed,
+ bool aAlwaysProxy = false) {
+ RefPtr<T> doomed = aDoomed;
+ if (!doomed) {
+ return; // Nothing to do.
+ }
+
+ // NS_ProxyRelease treats a null event target as "the current thread". So a
+ // handle on the main thread is only necessary when we're not already on the
+ // main thread or the release must happen asynchronously.
+ nsCOMPtr<nsIEventTarget> target;
+ if (!NS_IsMainThread() || aAlwaysProxy) {
+ target = mozilla::GetMainThreadSerialEventTarget();
+
+ if (!target) {
+ MOZ_ASSERT_UNREACHABLE("Could not get main thread; leaking an object!");
+ mozilla::Unused << doomed.forget().take();
+ return;
+ }
+ }
+
+ NS_ProxyRelease(aName, target, doomed.forget(), aAlwaysProxy);
+}
+
+template <class T>
+inline NS_HIDDEN_(void) NS_ReleaseOnMainThread(already_AddRefed<T> aDoomed,
+ bool aAlwaysProxy = false) {
+ NS_ReleaseOnMainThread("NS_ReleaseOnMainThread", std::move(aDoomed),
+ aAlwaysProxy);
+}
+
+/**
+ * Class to safely handle main-thread-only pointers off the main thread.
+ *
+ * Classes like XPCWrappedJS are main-thread-only, which means that it is
+ * forbidden to call methods on instances of these classes off the main thread.
+ * For various reasons (see bug 771074), this restriction applies to
+ * AddRef/Release as well.
+ *
+ * This presents a problem for consumers that wish to hold a callback alive
+ * on non-main-thread code. A common example of this is the proxy callback
+ * pattern, where non-main-thread code holds a strong-reference to the callback
+ * object, and dispatches new Runnables (also with a strong reference) to the
+ * main thread in order to execute the callback. This involves several AddRef
+ * and Release calls on the other thread, which is verboten.
+ *
+ * The basic idea of this class is to introduce a layer of indirection.
+ * nsMainThreadPtrHolder is a threadsafe reference-counted class that internally
+ * maintains one strong reference to the main-thread-only object. It must be
+ * instantiated on the main thread (so that the AddRef of the underlying object
+ * happens on the main thread), but consumers may subsequently pass references
+ * to the holder anywhere they please. These references are meant to be opaque
+ * when accessed off-main-thread (assertions enforce this).
+ *
+ * The semantics of RefPtr<nsMainThreadPtrHolder<T>> would be cumbersome, so we
+ * also introduce nsMainThreadPtrHandle<T>, which is conceptually identical to
+ * the above (though it includes various convenience methods). The basic pattern
+ * is as follows.
+ *
+ * // On the main thread:
+ * nsCOMPtr<nsIFooCallback> callback = ...;
+ * nsMainThreadPtrHandle<nsIFooCallback> callbackHandle =
+ * new nsMainThreadPtrHolder<nsIFooCallback>(callback);
+ * // Pass callbackHandle to structs/classes that might be accessed on other
+ * // threads.
+ *
+ * All structs and classes that might be accessed on other threads should store
+ * an nsMainThreadPtrHandle<T> rather than an nsCOMPtr<T>.
+ */
+template <class T>
+class MOZ_IS_SMARTPTR_TO_REFCOUNTED nsMainThreadPtrHolder final {
+ public:
+ // We can only acquire a pointer on the main thread. We want to fail fast for
+ // threading bugs, so by default we assert if our pointer is used or acquired
+ // off-main-thread. But some consumers need to use the same pointer for
+ // multiple classes, some of which are main-thread-only and some of which
+ // aren't. So we allow them to explicitly disable this strict checking.
+ nsMainThreadPtrHolder(const char* aName, T* aPtr, bool aStrict = true,
+ nsIEventTarget* aMainThreadEventTarget = nullptr)
+ : mRawPtr(aPtr),
+ mStrict(aStrict),
+ mMainThreadEventTarget(aMainThreadEventTarget)
+#ifndef RELEASE_OR_BETA
+ ,
+ mName(aName)
+#endif
+ {
+ // We can only AddRef our pointer on the main thread, which means that the
+ // holder must be constructed on the main thread.
+ MOZ_ASSERT(!mStrict || NS_IsMainThread());
+ NS_IF_ADDREF(mRawPtr);
+ }
+ nsMainThreadPtrHolder(const char* aName, already_AddRefed<T> aPtr,
+ bool aStrict = true,
+ nsIEventTarget* aMainThreadEventTarget = nullptr)
+ : mRawPtr(aPtr.take()),
+ mStrict(aStrict),
+ mMainThreadEventTarget(aMainThreadEventTarget)
+#ifndef RELEASE_OR_BETA
+ ,
+ mName(aName)
+#endif
+ {
+ // Since we don't need to AddRef the pointer, this constructor is safe to
+ // call on any thread.
+ }
+
+ // Copy constructor and operator= deleted. Once constructed, the holder is
+ // immutable.
+ T& operator=(nsMainThreadPtrHolder& aOther) = delete;
+ nsMainThreadPtrHolder(const nsMainThreadPtrHolder& aOther) = delete;
+
+ private:
+ // We can be released on any thread.
+ ~nsMainThreadPtrHolder() {
+ if (NS_IsMainThread()) {
+ NS_IF_RELEASE(mRawPtr);
+ } else if (mRawPtr) {
+ if (!mMainThreadEventTarget) {
+ mMainThreadEventTarget = do_GetMainThread();
+ }
+ MOZ_ASSERT(mMainThreadEventTarget);
+ NS_ProxyRelease(
+#ifdef RELEASE_OR_BETA
+ nullptr,
+#else
+ mName,
+#endif
+ mMainThreadEventTarget, dont_AddRef(mRawPtr));
+ }
+ }
+
+ public:
+ T* get() const {
+ // Nobody should be touching the raw pointer off-main-thread.
+ if (mStrict && MOZ_UNLIKELY(!NS_IsMainThread())) {
+ NS_ERROR("Can't dereference nsMainThreadPtrHolder off main thread");
+ MOZ_CRASH();
+ }
+ return mRawPtr;
+ }
+
+ bool operator==(const nsMainThreadPtrHolder<T>& aOther) const {
+ return mRawPtr == aOther.mRawPtr;
+ }
+ bool operator!() const { return !mRawPtr; }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsMainThreadPtrHolder<T>)
+
+ private:
+ // Our wrapped pointer.
+ T* mRawPtr = nullptr;
+
+ // Whether to strictly enforce thread invariants in this class.
+ bool mStrict = true;
+
+ nsCOMPtr<nsIEventTarget> mMainThreadEventTarget;
+
+#ifndef RELEASE_OR_BETA
+ const char* mName = nullptr;
+#endif
+};
+
+template <class T>
+class MOZ_IS_SMARTPTR_TO_REFCOUNTED nsMainThreadPtrHandle {
+ public:
+ nsMainThreadPtrHandle() : mPtr(nullptr) {}
+ MOZ_IMPLICIT nsMainThreadPtrHandle(decltype(nullptr)) : mPtr(nullptr) {}
+ explicit nsMainThreadPtrHandle(nsMainThreadPtrHolder<T>* aHolder)
+ : mPtr(aHolder) {}
+ explicit nsMainThreadPtrHandle(
+ already_AddRefed<nsMainThreadPtrHolder<T>> aHolder)
+ : mPtr(aHolder) {}
+ nsMainThreadPtrHandle(const nsMainThreadPtrHandle& aOther) = default;
+ nsMainThreadPtrHandle(nsMainThreadPtrHandle&& aOther) = default;
+ nsMainThreadPtrHandle& operator=(const nsMainThreadPtrHandle& aOther) =
+ default;
+ nsMainThreadPtrHandle& operator=(nsMainThreadPtrHandle&& aOther) = default;
+ nsMainThreadPtrHandle& operator=(nsMainThreadPtrHolder<T>* aHolder) {
+ mPtr = aHolder;
+ return *this;
+ }
+
+ // These all call through to nsMainThreadPtrHolder, and thus implicitly
+ // assert that we're on the main thread (if strict). Off-main-thread consumers
+ // must treat these handles as opaque.
+ T* get() const {
+ if (mPtr) {
+ return mPtr.get()->get();
+ }
+ return nullptr;
+ }
+
+ operator T*() const { return get(); }
+ T* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN { return get(); }
+
+ // These are safe to call on other threads with appropriate external locking.
+ bool operator==(const nsMainThreadPtrHandle<T>& aOther) const {
+ if (!mPtr || !aOther.mPtr) {
+ return mPtr == aOther.mPtr;
+ }
+ return *mPtr == *aOther.mPtr;
+ }
+ bool operator!=(const nsMainThreadPtrHandle<T>& aOther) const {
+ return !operator==(aOther);
+ }
+ bool operator==(decltype(nullptr)) const { return mPtr == nullptr; }
+ bool operator!=(decltype(nullptr)) const { return mPtr != nullptr; }
+ bool operator!() const { return !mPtr || !*mPtr; }
+
+ private:
+ RefPtr<nsMainThreadPtrHolder<T>> mPtr;
+};
+
+class nsCycleCollectionTraversalCallback;
+template <typename T>
+void CycleCollectionNoteChild(nsCycleCollectionTraversalCallback& aCallback,
+ T* aChild, const char* aName, uint32_t aFlags);
+
+template <typename T>
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback,
+ nsMainThreadPtrHandle<T>& aField, const char* aName, uint32_t aFlags = 0) {
+ CycleCollectionNoteChild(aCallback, aField.get(), aName, aFlags);
+}
+
+template <typename T>
+inline void ImplCycleCollectionUnlink(nsMainThreadPtrHandle<T>& aField) {
+ aField = nullptr;
+}
+
+#endif
diff --git a/xpcom/threads/nsThread.cpp b/xpcom/threads/nsThread.cpp
new file mode 100644
index 0000000000..cbae8e42d3
--- /dev/null
+++ b/xpcom/threads/nsThread.cpp
@@ -0,0 +1,1557 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsThread.h"
+
+#include "base/message_loop.h"
+#include "base/platform_thread.h"
+
+// Chromium's logging can sometimes leak through...
+#ifdef LOG
+# undef LOG
+#endif
+
+#include "mozilla/ReentrantMonitor.h"
+#include "nsMemoryPressure.h"
+#include "nsThreadManager.h"
+#include "nsIClassInfoImpl.h"
+#include "nsCOMPtr.h"
+#include "nsQueryObject.h"
+#include "pratom.h"
+#include "mozilla/BackgroundHangMonitor.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/Logging.h"
+#include "nsIObserverService.h"
+#include "mozilla/IOInterposer.h"
+#include "mozilla/ipc/MessageChannel.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/Services.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/StaticPrefs_threads.h"
+#include "mozilla/TaskController.h"
+#include "nsXPCOMPrivate.h"
+#include "mozilla/ChaosMode.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "nsThreadSyncDispatch.h"
+#include "nsServiceManagerUtils.h"
+#include "GeckoProfiler.h"
+#include "InputEventStatistics.h"
+#include "ThreadEventQueue.h"
+#include "ThreadEventTarget.h"
+#include "ThreadDelay.h"
+
+#include <limits>
+
+#ifdef XP_LINUX
+# ifdef __GLIBC__
+# include <gnu/libc-version.h>
+# endif
+# include <sys/mman.h>
+# include <sys/time.h>
+# include <sys/resource.h>
+# include <sched.h>
+# include <stdio.h>
+#endif
+
+#ifdef XP_WIN
+# include "mozilla/DynamicallyLinkedFunctionPtr.h"
+
+# include <winbase.h>
+
+using GetCurrentThreadStackLimitsFn = void(WINAPI*)(PULONG_PTR LowLimit,
+ PULONG_PTR HighLimit);
+#endif
+
+#define HAVE_UALARM \
+ _BSD_SOURCE || \
+ (_XOPEN_SOURCE >= 500 || _XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED) && \
+ !(_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700)
+
+#if defined(XP_LINUX) && !defined(ANDROID) && defined(_GNU_SOURCE)
+# define HAVE_SCHED_SETAFFINITY
+#endif
+
+#ifdef XP_MACOSX
+# include <mach/mach.h>
+# include <mach/thread_policy.h>
+#endif
+
+#ifdef MOZ_CANARY
+# include <unistd.h>
+# include <execinfo.h>
+# include <signal.h>
+# include <fcntl.h>
+# include "nsXULAppAPI.h"
+#endif
+
+#ifdef MOZ_TASK_TRACER
+# include "GeckoTaskTracer.h"
+# include "TracedTaskCommon.h"
+using namespace mozilla::tasktracer;
+#endif
+
+using namespace mozilla;
+
+extern void InitThreadLocalVariables();
+
+static LazyLogModule sThreadLog("nsThread");
+#ifdef LOG
+# undef LOG
+#endif
+#define LOG(args) MOZ_LOG(sThreadLog, mozilla::LogLevel::Debug, args)
+
+NS_DECL_CI_INTERFACE_GETTER(nsThread)
+
+Array<char, nsThread::kRunnableNameBufSize> nsThread::sMainThreadRunnableName;
+
+uint32_t nsThread::sActiveThreads;
+uint32_t nsThread::sMaxActiveThreads;
+
+#ifdef EARLY_BETA_OR_EARLIER
+const uint32_t kTelemetryWakeupCountLimit = 100;
+#endif
+
+//-----------------------------------------------------------------------------
+// Because we do not have our own nsIFactory, we have to implement nsIClassInfo
+// somewhat manually.
+
+class nsThreadClassInfo : public nsIClassInfo {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED // no mRefCnt
+ NS_DECL_NSICLASSINFO
+
+ nsThreadClassInfo() = default;
+};
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsThreadClassInfo::AddRef() { return 2; }
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsThreadClassInfo::Release() { return 1; }
+NS_IMPL_QUERY_INTERFACE(nsThreadClassInfo, nsIClassInfo)
+
+NS_IMETHODIMP
+nsThreadClassInfo::GetInterfaces(nsTArray<nsIID>& aArray) {
+ return NS_CI_INTERFACE_GETTER_NAME(nsThread)(aArray);
+}
+
+NS_IMETHODIMP
+nsThreadClassInfo::GetScriptableHelper(nsIXPCScriptable** aResult) {
+ *aResult = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadClassInfo::GetContractID(nsACString& aResult) {
+ aResult.SetIsVoid(true);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadClassInfo::GetClassDescription(nsACString& aResult) {
+ aResult.SetIsVoid(true);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadClassInfo::GetClassID(nsCID** aResult) {
+ *aResult = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadClassInfo::GetFlags(uint32_t* aResult) {
+ *aResult = THREADSAFE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadClassInfo::GetClassIDNoAlloc(nsCID* aResult) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(nsThread)
+NS_IMPL_RELEASE(nsThread)
+NS_INTERFACE_MAP_BEGIN(nsThread)
+ NS_INTERFACE_MAP_ENTRY(nsIThread)
+ NS_INTERFACE_MAP_ENTRY(nsIThreadInternal)
+ NS_INTERFACE_MAP_ENTRY(nsIEventTarget)
+ NS_INTERFACE_MAP_ENTRY(nsISerialEventTarget)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
+ NS_INTERFACE_MAP_ENTRY(nsIDirectTaskDispatcher)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIThread)
+ if (aIID.Equals(NS_GET_IID(nsIClassInfo))) {
+ static nsThreadClassInfo sThreadClassInfo;
+ foundInterface = static_cast<nsIClassInfo*>(&sThreadClassInfo);
+ } else
+NS_INTERFACE_MAP_END
+NS_IMPL_CI_INTERFACE_GETTER(nsThread, nsIThread, nsIThreadInternal,
+ nsIEventTarget, nsISerialEventTarget,
+ nsISupportsPriority)
+
+//-----------------------------------------------------------------------------
+
+class nsThreadStartupEvent final : public Runnable {
+ public:
+ nsThreadStartupEvent()
+ : Runnable("nsThreadStartupEvent"),
+ mMon("nsThreadStartupEvent.mMon"),
+ mInitialized(false) {}
+
+ // This method does not return until the thread startup object is in the
+ // completion state.
+ void Wait() {
+ ReentrantMonitorAutoEnter mon(mMon);
+ while (!mInitialized) {
+ mon.Wait();
+ }
+ }
+
+ private:
+ ~nsThreadStartupEvent() = default;
+
+ NS_IMETHOD Run() override {
+ ReentrantMonitorAutoEnter mon(mMon);
+ mInitialized = true;
+ mon.Notify();
+ return NS_OK;
+ }
+
+ ReentrantMonitor mMon;
+ bool mInitialized;
+};
+//-----------------------------------------------------------------------------
+
+bool nsThread::ShutdownContextsComp::Equals(
+ const ShutdownContexts::elem_type& a,
+ const ShutdownContexts::elem_type::Pointer b) const {
+ return a.get() == b;
+}
+
+// This event is responsible for notifying nsThread::Shutdown that it is time
+// to call PR_JoinThread. It implements nsICancelableRunnable so that it can
+// run on a DOM Worker thread (where all events must implement
+// nsICancelableRunnable.)
+class nsThreadShutdownAckEvent : public CancelableRunnable {
+ public:
+ explicit nsThreadShutdownAckEvent(NotNull<nsThreadShutdownContext*> aCtx)
+ : CancelableRunnable("nsThreadShutdownAckEvent"),
+ mShutdownContext(aCtx) {}
+ NS_IMETHOD Run() override {
+ mShutdownContext->mTerminatingThread->ShutdownComplete(mShutdownContext);
+ return NS_OK;
+ }
+ nsresult Cancel() override { return Run(); }
+
+ private:
+ virtual ~nsThreadShutdownAckEvent() = default;
+
+ NotNull<nsThreadShutdownContext*> mShutdownContext;
+};
+
+// This event is responsible for setting mShutdownContext
+class nsThreadShutdownEvent : public Runnable {
+ public:
+ nsThreadShutdownEvent(NotNull<nsThread*> aThr,
+ NotNull<nsThreadShutdownContext*> aCtx)
+ : Runnable("nsThreadShutdownEvent"),
+ mThread(aThr),
+ mShutdownContext(aCtx) {}
+ NS_IMETHOD Run() override {
+ mThread->mShutdownContext = mShutdownContext;
+ MessageLoop::current()->Quit();
+ return NS_OK;
+ }
+
+ private:
+ NotNull<RefPtr<nsThread>> mThread;
+ NotNull<nsThreadShutdownContext*> mShutdownContext;
+};
+
+//-----------------------------------------------------------------------------
+
+static void SetThreadAffinity(unsigned int cpu) {
+#ifdef HAVE_SCHED_SETAFFINITY
+ cpu_set_t cpus;
+ CPU_ZERO(&cpus);
+ CPU_SET(cpu, &cpus);
+ sched_setaffinity(0, sizeof(cpus), &cpus);
+ // Don't assert sched_setaffinity's return value because it intermittently (?)
+ // fails with EINVAL on Linux x64 try runs.
+#elif defined(XP_MACOSX)
+ // OS X does not provide APIs to pin threads to specific processors, but you
+ // can tag threads as belonging to the same "affinity set" and the OS will try
+ // to run them on the same processor. To run threads on different processors,
+ // tag them as belonging to different affinity sets. Tag 0, the default, means
+ // "no affinity" so let's pretend each CPU has its own tag `cpu+1`.
+ thread_affinity_policy_data_t policy;
+ policy.affinity_tag = cpu + 1;
+ MOZ_ALWAYS_TRUE(thread_policy_set(mach_thread_self(), THREAD_AFFINITY_POLICY,
+ &policy.affinity_tag, 1) == KERN_SUCCESS);
+#elif defined(XP_WIN)
+ MOZ_ALWAYS_TRUE(SetThreadIdealProcessor(GetCurrentThread(), cpu) !=
+ (DWORD)-1);
+#endif
+}
+
+static void SetupCurrentThreadForChaosMode() {
+ if (!ChaosMode::isActive(ChaosFeature::ThreadScheduling)) {
+ return;
+ }
+
+#ifdef XP_LINUX
+ // PR_SetThreadPriority doesn't really work since priorities >
+ // PR_PRIORITY_NORMAL can't be set by non-root users. Instead we'll just use
+ // setpriority(2) to set random 'nice values'. In regular Linux this is only
+ // a dynamic adjustment so it still doesn't really do what we want, but tools
+ // like 'rr' can be more aggressive about honoring these values.
+ // Some of these calls may fail due to trying to lower the priority
+ // (e.g. something may have already called setpriority() for this thread).
+ // This makes it hard to have non-main threads with higher priority than the
+ // main thread, but that's hard to fix. Tools like rr can choose to honor the
+ // requested values anyway.
+ // Use just 4 priorities so there's a reasonable chance of any two threads
+ // having equal priority.
+ setpriority(PRIO_PROCESS, 0, ChaosMode::randomUint32LessThan(4));
+#else
+ // We should set the affinity here but NSPR doesn't provide a way to expose
+ // it.
+ uint32_t priority = ChaosMode::randomUint32LessThan(PR_PRIORITY_LAST + 1);
+ PR_SetThreadPriority(PR_GetCurrentThread(), PRThreadPriority(priority));
+#endif
+
+ // Force half the threads to CPU 0 so they compete for CPU
+ if (ChaosMode::randomUint32LessThan(2)) {
+ SetThreadAffinity(0);
+ }
+}
+
+namespace {
+
+struct ThreadInitData {
+ nsThread* thread;
+ const nsACString& name;
+};
+
+} // namespace
+
+/* static */ mozilla::OffTheBooksMutex& nsThread::ThreadListMutex() {
+ static OffTheBooksMutex sMutex("nsThread::ThreadListMutex");
+ return sMutex;
+}
+
+/* static */ LinkedList<nsThread>& nsThread::ThreadList() {
+ static LinkedList<nsThread> sList;
+ return sList;
+}
+
+/* static */
+void nsThread::ClearThreadList() {
+ OffTheBooksMutexAutoLock mal(ThreadListMutex());
+ while (ThreadList().popFirst()) {
+ }
+}
+
+/* static */
+nsThreadEnumerator nsThread::Enumerate() { return {}; }
+
+/* static */
+uint32_t nsThread::MaxActiveThreads() {
+ OffTheBooksMutexAutoLock mal(ThreadListMutex());
+ return sMaxActiveThreads;
+}
+
+void nsThread::AddToThreadList() {
+ OffTheBooksMutexAutoLock mal(ThreadListMutex());
+ MOZ_ASSERT(!isInList());
+
+ sActiveThreads++;
+ sMaxActiveThreads = std::max(sActiveThreads, sMaxActiveThreads);
+
+ ThreadList().insertBack(this);
+}
+
+void nsThread::MaybeRemoveFromThreadList() {
+ OffTheBooksMutexAutoLock mal(ThreadListMutex());
+ if (isInList()) {
+ sActiveThreads--;
+ removeFrom(ThreadList());
+ }
+}
+
+/*static*/
+void nsThread::ThreadFunc(void* aArg) {
+ using mozilla::ipc::BackgroundChild;
+
+ ThreadInitData* initData = static_cast<ThreadInitData*>(aArg);
+ nsThread* self = initData->thread; // strong reference
+
+ MOZ_ASSERT(self->mEventTarget);
+ MOZ_ASSERT(self->mEvents);
+
+ self->mThread = PR_GetCurrentThread();
+ self->mEventTarget->SetCurrentThread();
+ SetupCurrentThreadForChaosMode();
+
+ if (!initData->name.IsEmpty()) {
+ NS_SetCurrentThreadName(initData->name.BeginReading());
+ }
+
+ self->InitCommon();
+
+ // Inform the ThreadManager
+ nsThreadManager::get().RegisterCurrentThread(*self);
+
+ mozilla::IOInterposer::RegisterCurrentThread();
+
+#ifdef MOZ_GECKO_PROFILER
+ // This must come after the call to nsThreadManager::RegisterCurrentThread(),
+ // because that call is needed to properly set up this thread as an nsThread,
+ // which profiler_register_thread() requires. See bug 1347007.
+ const bool registerWithProfiler = !initData->name.IsEmpty();
+ if (registerWithProfiler) {
+ PROFILER_REGISTER_THREAD(initData->name.BeginReading());
+ }
+#endif // MOZ_GECKO_PROFILER
+
+ // Wait for and process startup event
+ nsCOMPtr<nsIRunnable> event = self->mEvents->GetEvent(true, nullptr);
+ MOZ_ASSERT(event);
+
+ initData = nullptr; // clear before unblocking nsThread::Init
+
+ event->Run(); // unblocks nsThread::Init
+ event = nullptr;
+
+ {
+ // Scope for MessageLoop.
+ MessageLoop loop(MessageLoop::TYPE_MOZILLA_NONMAINTHREAD, self);
+
+ // Now, process incoming events...
+ loop.Run();
+
+ BackgroundChild::CloseForCurrentThread();
+
+ // NB: The main thread does not shut down here! It shuts down via
+ // nsThreadManager::Shutdown.
+
+ // Do NS_ProcessPendingEvents but with special handling to set
+ // mEventsAreDoomed atomically with the removal of the last event. The key
+ // invariant here is that we will never permit PutEvent to succeed if the
+ // event would be left in the queue after our final call to
+ // NS_ProcessPendingEvents. We also have to keep processing events as long
+ // as we have outstanding mRequestedShutdownContexts.
+ while (true) {
+ // Check and see if we're waiting on any threads.
+ self->WaitForAllAsynchronousShutdowns();
+
+ if (self->mEvents->ShutdownIfNoPendingEvents()) {
+ break;
+ }
+ NS_ProcessPendingEvents(self);
+ }
+ }
+
+ mozilla::IOInterposer::UnregisterCurrentThread();
+
+ // Inform the threadmanager that this thread is going away
+ nsThreadManager::get().UnregisterCurrentThread(*self);
+
+#ifdef MOZ_GECKO_PROFILER
+ // The thread should only unregister itself if it was registered above.
+ if (registerWithProfiler) {
+ PROFILER_UNREGISTER_THREAD();
+ }
+#endif // MOZ_GECKO_PROFILER
+
+ // Dispatch shutdown ACK
+ NotNull<nsThreadShutdownContext*> context =
+ WrapNotNull(self->mShutdownContext);
+ MOZ_ASSERT(context->mTerminatingThread == self);
+ event = do_QueryObject(new nsThreadShutdownAckEvent(context));
+ if (context->mIsMainThreadJoining) {
+ SchedulerGroup::Dispatch(TaskCategory::Other, event.forget());
+ } else {
+ context->mJoiningThread->Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+
+ // Release any observer of the thread here.
+ self->SetObserver(nullptr);
+
+#ifdef MOZ_TASK_TRACER
+ FreeTraceInfo();
+#endif
+
+ // The PRThread will be deleted in PR_JoinThread(), so clear references.
+ self->mThread = nullptr;
+ self->mEventTarget->ClearCurrentThread();
+ NS_RELEASE(self);
+}
+
+void nsThread::InitCommon() {
+ mThreadId = uint32_t(PlatformThread::CurrentId());
+
+ {
+#if defined(XP_LINUX)
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_getattr_np(pthread_self(), &attr);
+
+ size_t stackSize;
+ pthread_attr_getstack(&attr, &mStackBase, &stackSize);
+
+ // Glibc prior to 2.27 reports the stack size and base including the guard
+ // region, so we need to compensate for it to get accurate accounting.
+ // Also, this behavior difference isn't guarded by a versioned symbol, so we
+ // actually need to check the runtime glibc version, not the version we were
+ // compiled against.
+ static bool sAdjustForGuardSize = ({
+# ifdef __GLIBC__
+ unsigned major, minor;
+ sscanf(gnu_get_libc_version(), "%u.%u", &major, &minor) < 2 ||
+ major < 2 || (major == 2 && minor < 27);
+# else
+ false;
+# endif
+ });
+ if (sAdjustForGuardSize) {
+ size_t guardSize;
+ pthread_attr_getguardsize(&attr, &guardSize);
+
+ // Note: This assumes that the stack grows down, as is the case on all of
+ // our tier 1 platforms. On platforms where the stack grows up, the
+ // mStackBase adjustment is unnecessary, but doesn't cause any harm other
+ // than under-counting stack memory usage by one page.
+ mStackBase = reinterpret_cast<char*>(mStackBase) + guardSize;
+ stackSize -= guardSize;
+ }
+
+ mStackSize = stackSize;
+
+ // This is a bit of a hack.
+ //
+ // We really do want the NOHUGEPAGE flag on our thread stacks, since we
+ // don't expect any of them to need anywhere near 2MB of space. But setting
+ // it here is too late to have an effect, since the first stack page has
+ // already been faulted in existence, and NSPR doesn't give us a way to set
+ // it beforehand.
+ //
+ // What this does get us, however, is a different set of VM flags on our
+ // thread stacks compared to normal heap memory. Which makes the Linux
+ // kernel report them as separate regions, even when they are adjacent to
+ // heap memory. This allows us to accurately track the actual memory
+ // consumption of our allocated stacks.
+ madvise(mStackBase, stackSize, MADV_NOHUGEPAGE);
+
+ pthread_attr_destroy(&attr);
+#elif defined(XP_WIN)
+ static const StaticDynamicallyLinkedFunctionPtr<
+ GetCurrentThreadStackLimitsFn>
+ sGetStackLimits(L"kernel32.dll", "GetCurrentThreadStackLimits");
+
+ if (sGetStackLimits) {
+ ULONG_PTR stackBottom, stackTop;
+ sGetStackLimits(&stackBottom, &stackTop);
+ mStackBase = reinterpret_cast<void*>(stackBottom);
+ mStackSize = stackTop - stackBottom;
+ }
+#endif
+ }
+
+ InitThreadLocalVariables();
+ AddToThreadList();
+}
+
+//-----------------------------------------------------------------------------
+
+#ifdef MOZ_CANARY
+int sCanaryOutputFD = -1;
+#endif
+
+nsThread::nsThread(NotNull<SynchronizedEventQueue*> aQueue,
+ MainThreadFlag aMainThread, uint32_t aStackSize)
+ : mEvents(aQueue.get()),
+ mEventTarget(
+ new ThreadEventTarget(mEvents.get(), aMainThread == MAIN_THREAD)),
+ mShutdownContext(nullptr),
+ mScriptObserver(nullptr),
+ mStackSize(aStackSize),
+ mNestedEventLoopDepth(0),
+ mShutdownRequired(false),
+ mPriority(PRIORITY_NORMAL),
+ mIsMainThread(aMainThread == MAIN_THREAD),
+ mUseHangMonitor(aMainThread == MAIN_THREAD),
+ mIsAPoolThreadFree(nullptr),
+ mCanInvokeJS(false),
+#ifdef EARLY_BETA_OR_EARLIER
+ mLastWakeupCheckTime(TimeStamp::Now()),
+#endif
+ mPerformanceCounterState(mNestedEventLoopDepth, mIsMainThread) {
+ if (mIsMainThread) {
+ mozilla::TaskController::Get()->SetPerformanceCounterState(
+ &mPerformanceCounterState);
+ }
+}
+
+nsThread::nsThread()
+ : mEvents(nullptr),
+ mEventTarget(nullptr),
+ mShutdownContext(nullptr),
+ mScriptObserver(nullptr),
+ mStackSize(0),
+ mNestedEventLoopDepth(0),
+ mShutdownRequired(false),
+ mPriority(PRIORITY_NORMAL),
+ mIsMainThread(false),
+ mUseHangMonitor(false),
+ mCanInvokeJS(false),
+#ifdef EARLY_BETA_OR_EARLIER
+ mLastWakeupCheckTime(TimeStamp::Now()),
+#endif
+ mPerformanceCounterState(mNestedEventLoopDepth, mIsMainThread) {
+ MOZ_ASSERT(!NS_IsMainThread());
+}
+
+nsThread::~nsThread() {
+ NS_ASSERTION(mRequestedShutdownContexts.IsEmpty(),
+ "shouldn't be waiting on other threads to shutdown");
+
+ MaybeRemoveFromThreadList();
+
+#ifdef DEBUG
+ // We deliberately leak these so they can be tracked by the leak checker.
+ // If you're having nsThreadShutdownContext leaks, you can set:
+ // XPCOM_MEM_LOG_CLASSES=nsThreadShutdownContext
+ // during a test run and that will at least tell you what thread is
+ // requesting shutdown on another, which can be helpful for diagnosing
+ // the leak.
+ for (size_t i = 0; i < mRequestedShutdownContexts.Length(); ++i) {
+ Unused << mRequestedShutdownContexts[i].release();
+ }
+#endif
+}
+
+nsresult nsThread::Init(const nsACString& aName) {
+ MOZ_ASSERT(mEvents);
+ MOZ_ASSERT(mEventTarget);
+
+ // spawn thread and wait until it is fully setup
+ RefPtr<nsThreadStartupEvent> startup = new nsThreadStartupEvent();
+
+ NS_ADDREF_THIS();
+
+ mShutdownRequired = true;
+
+ ThreadInitData initData = {this, aName};
+
+ // ThreadFunc is responsible for setting mThread
+ if (!PR_CreateThread(PR_USER_THREAD, ThreadFunc, &initData,
+ PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD,
+ mStackSize)) {
+ NS_RELEASE_THIS();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // ThreadFunc will wait for this event to be run before it tries to access
+ // mThread. By delaying insertion of this event into the queue, we ensure
+ // that mThread is set properly.
+ {
+ mEvents->PutEvent(do_AddRef(startup),
+ EventQueuePriority::Normal); // retain a reference
+ }
+
+ // Wait for thread to call ThreadManager::SetupCurrentThread, which completes
+ // initialization of ThreadFunc.
+ startup->Wait();
+ return NS_OK;
+}
+
+nsresult nsThread::InitCurrentThread() {
+ mThread = PR_GetCurrentThread();
+ SetupCurrentThreadForChaosMode();
+ InitCommon();
+
+ nsThreadManager::get().RegisterCurrentThread(*this);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIEventTarget
+
+NS_IMETHODIMP
+nsThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) {
+ MOZ_ASSERT(mEventTarget);
+ NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED);
+
+ nsCOMPtr<nsIRunnable> event(aEvent);
+ return mEventTarget->Dispatch(event.forget(), aFlags);
+}
+
+NS_IMETHODIMP
+nsThread::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags) {
+ MOZ_ASSERT(mEventTarget);
+ NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED);
+
+ LOG(("THRD(%p) Dispatch [%p %x]\n", this, /* XXX aEvent */ nullptr, aFlags));
+
+ return mEventTarget->Dispatch(std::move(aEvent), aFlags);
+}
+
+NS_IMETHODIMP
+nsThread::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
+ uint32_t aDelayMs) {
+ MOZ_ASSERT(mEventTarget);
+ NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED);
+
+ return mEventTarget->DelayedDispatch(std::move(aEvent), aDelayMs);
+}
+
+NS_IMETHODIMP
+nsThread::GetRunningEventDelay(TimeDuration* aDelay, TimeStamp* aStart) {
+ if (mIsAPoolThreadFree && *mIsAPoolThreadFree) {
+ // if there are unstarted threads in the pool, a new event to the
+ // pool would not be delayed at all (beyond thread start time)
+ *aDelay = TimeDuration();
+ *aStart = TimeStamp();
+ } else {
+ *aDelay = mLastEventDelay;
+ *aStart = mLastEventStart;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThread::SetRunningEventDelay(TimeDuration aDelay, TimeStamp aStart) {
+ mLastEventDelay = aDelay;
+ mLastEventStart = aStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThread::IsOnCurrentThread(bool* aResult) {
+ if (mEventTarget) {
+ return mEventTarget->IsOnCurrentThread(aResult);
+ }
+ *aResult = PR_GetCurrentThread() == mThread;
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(bool)
+nsThread::IsOnCurrentThreadInfallible() {
+ // This method is only going to be called if `mThread` is null, which
+ // only happens when the thread has exited the event loop. Therefore, when
+ // we are called, we can never be on this thread.
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// nsIThread
+
+NS_IMETHODIMP
+nsThread::GetPRThread(PRThread** aResult) {
+ PRThread* thread = mThread; // atomic load
+ *aResult = thread;
+ return thread ? NS_OK : NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsThread::GetCanInvokeJS(bool* aResult) {
+ *aResult = mCanInvokeJS;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThread::SetCanInvokeJS(bool aCanInvokeJS) {
+ mCanInvokeJS = aCanInvokeJS;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThread::GetLastLongTaskEnd(TimeStamp* _retval) {
+ *_retval = mPerformanceCounterState.LastLongTaskEnd();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThread::GetLastLongNonIdleTaskEnd(TimeStamp* _retval) {
+ *_retval = mPerformanceCounterState.LastLongNonIdleTaskEnd();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThread::SetNameForWakeupTelemetry(const nsACString& aName) {
+#ifdef EARLY_BETA_OR_EARLIER
+ mNameForWakeupTelemetry = aName;
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThread::AsyncShutdown() {
+ LOG(("THRD(%p) async shutdown\n", this));
+
+ ShutdownInternal(/* aSync = */ false);
+ return NS_OK;
+}
+
+nsThreadShutdownContext* nsThread::ShutdownInternal(bool aSync) {
+ MOZ_ASSERT(mEvents);
+ MOZ_ASSERT(mEventTarget);
+ MOZ_ASSERT(mThread != PR_GetCurrentThread());
+ if (NS_WARN_IF(mThread == PR_GetCurrentThread())) {
+ return nullptr;
+ }
+
+ // Prevent multiple calls to this method.
+ if (!mShutdownRequired.compareExchange(true, false)) {
+ return nullptr;
+ }
+ MOZ_ASSERT(mThread);
+
+ MaybeRemoveFromThreadList();
+
+ NotNull<nsThread*> currentThread =
+ WrapNotNull(nsThreadManager::get().GetCurrentThread());
+
+ MOZ_DIAGNOSTIC_ASSERT(currentThread->EventQueue(),
+ "Shutdown() may only be called from an XPCOM thread");
+
+ // Allocate a shutdown context and store a strong ref.
+ auto context =
+ new nsThreadShutdownContext(WrapNotNull(this), currentThread, aSync);
+ Unused << *currentThread->mRequestedShutdownContexts.EmplaceBack(context);
+
+ // Set mShutdownContext and wake up the thread in case it is waiting for
+ // events to process.
+ nsCOMPtr<nsIRunnable> event =
+ new nsThreadShutdownEvent(WrapNotNull(this), WrapNotNull(context));
+ // XXXroc What if posting the event fails due to OOM?
+ mEvents->PutEvent(event.forget(), EventQueuePriority::Normal);
+
+ // We could still end up with other events being added after the shutdown
+ // task, but that's okay because we process pending events in ThreadFunc
+ // after setting mShutdownContext just before exiting.
+ return context;
+}
+
+void nsThread::ShutdownComplete(NotNull<nsThreadShutdownContext*> aContext) {
+ MOZ_ASSERT(mEvents);
+ MOZ_ASSERT(mEventTarget);
+ MOZ_ASSERT(aContext->mTerminatingThread == this);
+
+ MaybeRemoveFromThreadList();
+
+ if (aContext->mAwaitingShutdownAck) {
+ // We're in a synchronous shutdown, so tell whatever is up the stack that
+ // we're done and unwind the stack so it can call us again.
+ aContext->mAwaitingShutdownAck = false;
+ return;
+ }
+
+ // Now, it should be safe to join without fear of dead-locking.
+ PR_JoinThread(aContext->mTerminatingPRThread);
+ MOZ_ASSERT(!mThread);
+
+#ifdef DEBUG
+ nsCOMPtr<nsIThreadObserver> obs = mEvents->GetObserver();
+ MOZ_ASSERT(!obs, "Should have been cleared at shutdown!");
+#endif
+
+ // Delete aContext.
+ // aContext might not be in mRequestedShutdownContexts if it belongs to a
+ // thread that was leaked by calling nsIThreadPool::ShutdownWithTimeout.
+ aContext->mJoiningThread->mRequestedShutdownContexts.RemoveElement(
+ aContext, ShutdownContextsComp{});
+}
+
+void nsThread::WaitForAllAsynchronousShutdowns() {
+ // This is the motivating example for why SpinEventLoop has the template
+ // parameter we are providing here.
+ SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
+ [&]() { return mRequestedShutdownContexts.IsEmpty(); }, this);
+}
+
+NS_IMETHODIMP
+nsThread::Shutdown() {
+ LOG(("THRD(%p) sync shutdown\n", this));
+
+ nsThreadShutdownContext* maybeContext = ShutdownInternal(/* aSync = */ true);
+ if (!maybeContext) {
+ return NS_OK; // The thread has already shut down.
+ }
+
+ NotNull<nsThreadShutdownContext*> context = WrapNotNull(maybeContext);
+
+ // Process events on the current thread until we receive a shutdown ACK.
+ // Allows waiting; ensure no locks are held that would deadlock us!
+ SpinEventLoopUntil([&, context]() { return !context->mAwaitingShutdownAck; },
+ context->mJoiningThread);
+
+ ShutdownComplete(context);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThread::HasPendingEvents(bool* aResult) {
+ if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+
+ if (mIsMainThread && !mIsInLocalExecutionMode) {
+ *aResult = TaskController::Get()->HasMainThreadPendingTasks();
+ } else {
+ *aResult = mEvents->HasPendingEvent();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThread::HasPendingHighPriorityEvents(bool* aResult) {
+ if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+
+ // This function appears to never be called anymore.
+ *aResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThread::DispatchToQueue(already_AddRefed<nsIRunnable> aEvent,
+ EventQueuePriority aQueue) {
+ nsCOMPtr<nsIRunnable> event = aEvent;
+
+ if (NS_WARN_IF(!event)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!mEvents->PutEvent(event.forget(), aQueue)) {
+ NS_WARNING(
+ "An idle event was posted to a thread that will never run it "
+ "(rejected)");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+#ifdef MOZ_CANARY
+void canary_alarm_handler(int signum);
+
+class Canary {
+ // XXX ToDo: support nested loops
+ public:
+ Canary() {
+ if (sCanaryOutputFD > 0 && EventLatencyIsImportant()) {
+ signal(SIGALRM, canary_alarm_handler);
+ ualarm(15000, 0);
+ }
+ }
+
+ ~Canary() {
+ if (sCanaryOutputFD != 0 && EventLatencyIsImportant()) {
+ ualarm(0, 0);
+ }
+ }
+
+ static bool EventLatencyIsImportant() {
+ return NS_IsMainThread() && XRE_IsParentProcess();
+ }
+};
+
+void canary_alarm_handler(int signum) {
+ void* array[30];
+ const char msg[29] = "event took too long to run:\n";
+ // use write to be safe in the signal handler
+ write(sCanaryOutputFD, msg, sizeof(msg));
+ backtrace_symbols_fd(array, backtrace(array, 30), sCanaryOutputFD);
+}
+
+#endif
+
+#define NOTIFY_EVENT_OBSERVERS(observers_, func_, params_) \
+ do { \
+ if (!observers_.IsEmpty()) { \
+ for (nsCOMPtr<nsIThreadObserver> obs_ : observers_.ForwardRange()) { \
+ obs_->func_ params_; \
+ } \
+ } \
+ } while (0)
+
+#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+// static
+bool nsThread::GetLabeledRunnableName(nsIRunnable* aEvent, nsACString& aName,
+ EventQueuePriority aPriority) {
+ bool labeled = false;
+ if (RefPtr<SchedulerGroup::Runnable> groupRunnable = do_QueryObject(aEvent)) {
+ labeled = true;
+ MOZ_ALWAYS_TRUE(NS_SUCCEEDED(groupRunnable->GetName(aName)));
+ } else if (nsCOMPtr<nsINamed> named = do_QueryInterface(aEvent)) {
+ MOZ_ALWAYS_TRUE(NS_SUCCEEDED(named->GetName(aName)));
+ } else {
+ aName.AssignLiteral("non-nsINamed runnable");
+ }
+ if (aName.IsEmpty()) {
+ aName.AssignLiteral("anonymous runnable");
+ }
+
+ if (!labeled && aPriority > EventQueuePriority::InputHigh) {
+ aName.AppendLiteral("(unlabeled)");
+ }
+
+ return labeled;
+}
+#endif
+
+mozilla::PerformanceCounter* nsThread::GetPerformanceCounter(
+ nsIRunnable* aEvent) const {
+ return GetPerformanceCounterBase(aEvent);
+}
+
+// static
+mozilla::PerformanceCounter* nsThread::GetPerformanceCounterBase(
+ nsIRunnable* aEvent) {
+ RefPtr<SchedulerGroup::Runnable> docRunnable = do_QueryObject(aEvent);
+ if (docRunnable) {
+ mozilla::dom::DocGroup* docGroup = docRunnable->DocGroup();
+ if (docGroup) {
+ return docGroup->GetPerformanceCounter();
+ }
+ }
+ return nullptr;
+}
+
+size_t nsThread::ShallowSizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t n = 0;
+ if (mShutdownContext) {
+ n += aMallocSizeOf(mShutdownContext);
+ }
+ n += mRequestedShutdownContexts.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ return aMallocSizeOf(this) + aMallocSizeOf(mThread) + n;
+}
+
+size_t nsThread::SizeOfEventQueues(mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t n = 0;
+ if (mEventTarget) {
+ // The size of mEvents is reported by mEventTarget.
+ n += mEventTarget->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ return n;
+}
+
+size_t nsThread::SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ return ShallowSizeOfIncludingThis(aMallocSizeOf) +
+ SizeOfEventQueues(aMallocSizeOf);
+}
+
+NS_IMETHODIMP
+nsThread::ProcessNextEvent(bool aMayWait, bool* aResult) {
+ MOZ_ASSERT(mEvents);
+ NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED);
+
+ LOG(("THRD(%p) ProcessNextEvent [%u %u]\n", this, aMayWait,
+ mNestedEventLoopDepth));
+
+ if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+
+ // The toplevel event loop normally blocks waiting for the next event, but
+ // if we're trying to shut this thread down, we must exit the event loop
+ // when the event queue is empty. This only applys to the toplevel event
+ // loop! Nested event loops (e.g. during sync dispatch) are waiting for
+ // some state change and must be able to block even if something has
+ // requested shutdown of the thread. Otherwise we'll just busywait as we
+ // endlessly look for an event, fail to find one, and repeat the nested
+ // event loop since its state change hasn't happened yet.
+ bool reallyWait = aMayWait && (mNestedEventLoopDepth > 0 || !ShuttingDown());
+
+ if (mIsInLocalExecutionMode) {
+ if (nsCOMPtr<nsIRunnable> event = mEvents->GetEvent(reallyWait)) {
+ *aResult = true;
+ LogRunnable::Run log(event);
+ event->Run();
+ event = nullptr;
+ } else {
+ *aResult = false;
+ }
+ return NS_OK;
+ }
+
+ Maybe<dom::AutoNoJSAPI> noJSAPI;
+
+ if (mUseHangMonitor && reallyWait) {
+ BackgroundHangMonitor().NotifyWait();
+ }
+
+ if (mIsMainThread) {
+ DoMainThreadSpecificProcessing();
+ }
+
+ ++mNestedEventLoopDepth;
+
+ // We only want to create an AutoNoJSAPI on threads that actually do DOM
+ // stuff (including workers). Those are exactly the threads that have an
+ // mScriptObserver.
+ bool callScriptObserver = !!mScriptObserver;
+ if (callScriptObserver) {
+ noJSAPI.emplace();
+ mScriptObserver->BeforeProcessTask(reallyWait);
+ }
+
+#ifdef EARLY_BETA_OR_EARLIER
+ // Need to capture mayWaitForWakeup state before OnProcessNextEvent,
+ // since on the main thread OnProcessNextEvent ends up waiting for the new
+ // events.
+ bool mayWaitForWakeup = reallyWait && !mEvents->HasPendingEvent();
+#endif
+
+ nsCOMPtr<nsIThreadObserver> obs = mEvents->GetObserverOnThread();
+ if (obs) {
+ obs->OnProcessNextEvent(this, reallyWait);
+ }
+
+ NOTIFY_EVENT_OBSERVERS(EventQueue()->EventObservers(), OnProcessNextEvent,
+ (this, reallyWait));
+
+#ifdef MOZ_CANARY
+ Canary canary;
+#endif
+ nsresult rv = NS_OK;
+
+ {
+ // Scope for |event| to make sure that its destructor fires while
+ // mNestedEventLoopDepth has been incremented, since that destructor can
+ // also do work.
+ nsCOMPtr<nsIRunnable> event;
+ bool usingTaskController = mIsMainThread;
+ if (usingTaskController) {
+ event = TaskController::Get()->GetRunnableForMTTask(reallyWait);
+ } else {
+ event = mEvents->GetEvent(reallyWait, &mLastEventDelay);
+ }
+
+ *aResult = (event.get() != nullptr);
+
+ if (event) {
+#ifdef EARLY_BETA_OR_EARLIER
+ if (mayWaitForWakeup && mThread) {
+ ++mWakeupCount;
+ if (mWakeupCount == kTelemetryWakeupCountLimit) {
+ TimeStamp now = TimeStamp::Now();
+ double ms = (now - mLastWakeupCheckTime).ToMilliseconds();
+ if (ms < 0) {
+ ms = 0;
+ }
+ const char* name = !mNameForWakeupTelemetry.IsEmpty()
+ ? mNameForWakeupTelemetry.get()
+ : PR_GetThreadName(mThread);
+ if (!name) {
+ name = mIsMainThread ? "MainThread" : "(nameless thread)";
+ }
+ nsDependentCString key(name);
+ Telemetry::Accumulate(Telemetry::THREAD_WAKEUP, key,
+ static_cast<uint32_t>(ms));
+ mLastWakeupCheckTime = now;
+ mWakeupCount = 0;
+ }
+ }
+#endif
+
+ LOG(("THRD(%p) running [%p]\n", this, event.get()));
+
+ Maybe<LogRunnable::Run> log;
+
+ if (!usingTaskController) {
+ log.emplace(event);
+ }
+
+ // Delay event processing to encourage whoever dispatched this event
+ // to run.
+ DelayForChaosMode(ChaosFeature::TaskRunning, 1000);
+
+ mozilla::TimeStamp now = mozilla::TimeStamp::Now();
+
+ if (mUseHangMonitor) {
+ BackgroundHangMonitor().NotifyActivity();
+ }
+
+ Maybe<PerformanceCounterState::Snapshot> snapshot;
+ if (!usingTaskController) {
+ snapshot.emplace(mPerformanceCounterState.RunnableWillRun(
+ GetPerformanceCounter(event), now, false));
+ }
+
+ mLastEventStart = now;
+
+ event->Run();
+
+ if (usingTaskController) {
+ *aResult = TaskController::Get()->MTTaskRunnableProcessedTask();
+ } else {
+ mPerformanceCounterState.RunnableDidRun(std::move(snapshot.ref()));
+ }
+
+ // To cover the event's destructor code inside the LogRunnable span.
+ event = nullptr;
+ } else {
+ mLastEventDelay = TimeDuration();
+ mLastEventStart = TimeStamp();
+ if (aMayWait) {
+ MOZ_ASSERT(ShuttingDown(),
+ "This should only happen when shutting down");
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ }
+ }
+
+ DrainDirectTasks();
+
+ NOTIFY_EVENT_OBSERVERS(EventQueue()->EventObservers(), AfterProcessNextEvent,
+ (this, *aResult));
+
+ if (obs) {
+ obs->AfterProcessNextEvent(this, *aResult);
+ }
+
+ // In case some EventObserver dispatched some direct tasks; process them
+ // now.
+ DrainDirectTasks();
+
+ if (callScriptObserver) {
+ if (mScriptObserver) {
+ mScriptObserver->AfterProcessTask(mNestedEventLoopDepth);
+ }
+ noJSAPI.reset();
+ }
+
+ --mNestedEventLoopDepth;
+
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// nsISupportsPriority
+
+NS_IMETHODIMP
+nsThread::GetPriority(int32_t* aPriority) {
+ *aPriority = mPriority;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThread::SetPriority(int32_t aPriority) {
+ if (NS_WARN_IF(!mThread)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ // NSPR defines the following four thread priorities:
+ // PR_PRIORITY_LOW
+ // PR_PRIORITY_NORMAL
+ // PR_PRIORITY_HIGH
+ // PR_PRIORITY_URGENT
+ // We map the priority values defined on nsISupportsPriority to these
+ // values.
+
+ mPriority = aPriority;
+
+ PRThreadPriority pri;
+ if (mPriority <= PRIORITY_HIGHEST) {
+ pri = PR_PRIORITY_URGENT;
+ } else if (mPriority < PRIORITY_NORMAL) {
+ pri = PR_PRIORITY_HIGH;
+ } else if (mPriority > PRIORITY_NORMAL) {
+ pri = PR_PRIORITY_LOW;
+ } else {
+ pri = PR_PRIORITY_NORMAL;
+ }
+ // If chaos mode is active, retain the randomly chosen priority
+ if (!ChaosMode::isActive(ChaosFeature::ThreadScheduling)) {
+ PR_SetThreadPriority(mThread, pri);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThread::AdjustPriority(int32_t aDelta) {
+ return SetPriority(mPriority + aDelta);
+}
+
+//-----------------------------------------------------------------------------
+// nsIThreadInternal
+
+NS_IMETHODIMP
+nsThread::GetObserver(nsIThreadObserver** aObs) {
+ MOZ_ASSERT(mEvents);
+ NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED);
+
+ nsCOMPtr<nsIThreadObserver> obs = mEvents->GetObserver();
+ obs.forget(aObs);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThread::SetObserver(nsIThreadObserver* aObs) {
+ MOZ_ASSERT(mEvents);
+ NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED);
+
+ if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+
+ mEvents->SetObserver(aObs);
+ return NS_OK;
+}
+
+uint32_t nsThread::RecursionDepth() const {
+ MOZ_ASSERT(PR_GetCurrentThread() == mThread);
+ return mNestedEventLoopDepth;
+}
+
+NS_IMETHODIMP
+nsThread::AddObserver(nsIThreadObserver* aObserver) {
+ MOZ_ASSERT(mEvents);
+ NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED);
+
+ if (NS_WARN_IF(!aObserver)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+
+ EventQueue()->AddObserver(aObserver);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThread::RemoveObserver(nsIThreadObserver* aObserver) {
+ MOZ_ASSERT(mEvents);
+ NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED);
+
+ if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) {
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+
+ EventQueue()->RemoveObserver(aObserver);
+
+ return NS_OK;
+}
+
+void nsThread::SetScriptObserver(
+ mozilla::CycleCollectedJSContext* aScriptObserver) {
+ if (!aScriptObserver) {
+ mScriptObserver = nullptr;
+ return;
+ }
+
+ MOZ_ASSERT(!mScriptObserver);
+ mScriptObserver = aScriptObserver;
+}
+
+void nsThread::DoMainThreadSpecificProcessing() const {
+ MOZ_ASSERT(mIsMainThread);
+
+ ipc::CancelCPOWs();
+
+ // Fire a memory pressure notification, if one is pending.
+ if (!ShuttingDown()) {
+ MemoryPressureState mpPending = NS_GetPendingMemoryPressure();
+ if (mpPending != MemPressure_None) {
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+
+ if (os) {
+ if (mpPending == MemPressure_Stopping) {
+ os->NotifyObservers(nullptr, "memory-pressure-stop", nullptr);
+ } else {
+ os->NotifyObservers(nullptr, "memory-pressure",
+ mpPending == MemPressure_New
+ ? u"low-memory"
+ : u"low-memory-ongoing");
+ }
+ } else {
+ NS_WARNING("Can't get observer service!");
+ }
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsThread::GetEventTarget(nsIEventTarget** aEventTarget) {
+ nsCOMPtr<nsIEventTarget> target = this;
+ target.forget(aEventTarget);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIDirectTaskDispatcher
+
+NS_IMETHODIMP
+nsThread::DispatchDirectTask(already_AddRefed<nsIRunnable> aEvent) {
+ if (!IsOnCurrentThread()) {
+ return NS_ERROR_FAILURE;
+ }
+ mDirectTasks.AddTask(std::move(aEvent));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsThread::DrainDirectTasks() {
+ if (!IsOnCurrentThread()) {
+ return NS_ERROR_FAILURE;
+ }
+ mDirectTasks.DrainTasks();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsThread::HaveDirectTasks(bool* aValue) {
+ if (!IsOnCurrentThread()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aValue = mDirectTasks.HaveTasks();
+ return NS_OK;
+}
+
+nsIEventTarget* nsThread::EventTarget() { return this; }
+
+nsISerialEventTarget* nsThread::SerialEventTarget() { return this; }
+
+nsLocalExecutionRecord nsThread::EnterLocalExecution() {
+ MOZ_RELEASE_ASSERT(!mIsInLocalExecutionMode);
+ MOZ_ASSERT(IsOnCurrentThread());
+ MOZ_ASSERT(EventQueue());
+ return nsLocalExecutionRecord(*EventQueue(), mIsInLocalExecutionMode);
+}
+
+nsLocalExecutionGuard::nsLocalExecutionGuard(
+ nsLocalExecutionRecord&& aLocalExecutionRecord)
+ : mEventQueueStack(aLocalExecutionRecord.mEventQueueStack),
+ mLocalEventTarget(mEventQueueStack.PushEventQueue()),
+ mLocalExecutionFlag(aLocalExecutionRecord.mLocalExecutionFlag) {
+ MOZ_ASSERT(mLocalEventTarget);
+ MOZ_ASSERT(!mLocalExecutionFlag);
+ mLocalExecutionFlag = true;
+}
+
+nsLocalExecutionGuard::~nsLocalExecutionGuard() {
+ MOZ_ASSERT(mLocalExecutionFlag);
+ mLocalExecutionFlag = false;
+ mEventQueueStack.PopEventQueue(mLocalEventTarget);
+}
+
+namespace mozilla {
+PerformanceCounterState::Snapshot PerformanceCounterState::RunnableWillRun(
+ PerformanceCounter* aCounter, TimeStamp aNow, bool aIsIdleRunnable) {
+ if (IsNestedRunnable()) {
+ // Flush out any accumulated time that should be accounted to the
+ // current runnable before we start running a nested runnable.
+ MaybeReportAccumulatedTime(aNow);
+ }
+
+ Snapshot snapshot(mCurrentEventLoopDepth, mCurrentPerformanceCounter,
+ mCurrentRunnableIsIdleRunnable);
+
+ mCurrentEventLoopDepth = mNestedEventLoopDepth;
+ mCurrentPerformanceCounter = aCounter;
+ mCurrentRunnableIsIdleRunnable = aIsIdleRunnable;
+ mCurrentTimeSliceStart = aNow;
+
+ return snapshot;
+}
+
+void PerformanceCounterState::RunnableDidRun(Snapshot&& aSnapshot) {
+ // First thing: Restore our mCurrentEventLoopDepth so we can use
+ // IsNestedRunnable().
+ mCurrentEventLoopDepth = aSnapshot.mOldEventLoopDepth;
+
+ // We may not need the current timestamp; don't bother computing it if we
+ // don't.
+ TimeStamp now;
+ if (mCurrentPerformanceCounter || mIsMainThread || IsNestedRunnable()) {
+ now = TimeStamp::Now();
+ }
+ if (mCurrentPerformanceCounter || mIsMainThread) {
+ MaybeReportAccumulatedTime(now);
+ }
+
+ // And now restore the rest of our state.
+ mCurrentPerformanceCounter = std::move(aSnapshot.mOldPerformanceCounter);
+ mCurrentRunnableIsIdleRunnable = aSnapshot.mOldIsIdleRunnable;
+ if (IsNestedRunnable()) {
+ // Reset mCurrentTimeSliceStart to right now, so our parent runnable's
+ // next slice can be properly accounted for.
+ mCurrentTimeSliceStart = now;
+ } else {
+ // We are done at the outermost level; we are no longer in a timeslice.
+ mCurrentTimeSliceStart = TimeStamp();
+ }
+}
+
+void PerformanceCounterState::MaybeReportAccumulatedTime(TimeStamp aNow) {
+ MOZ_ASSERT(mCurrentTimeSliceStart,
+ "How did we get here if we're not in a timeslice?");
+
+ if (!mCurrentPerformanceCounter && !mIsMainThread) {
+ // No one cares about this timeslice.
+ return;
+ }
+
+ TimeDuration duration = aNow - mCurrentTimeSliceStart;
+ if (mCurrentPerformanceCounter) {
+ mCurrentPerformanceCounter->IncrementExecutionDuration(
+ duration.ToMicroseconds());
+ }
+
+ // Long tasks only matter on the main thread.
+ if (mIsMainThread && duration.ToMilliseconds() > LONGTASK_BUSY_WINDOW_MS) {
+ // Idle events (gc...) don't *really* count here
+ if (!mCurrentRunnableIsIdleRunnable) {
+ mLastLongNonIdleTaskEnd = aNow;
+ }
+ mLastLongTaskEnd = aNow;
+
+#ifdef MOZ_GECKO_PROFILER
+ if (profiler_thread_is_being_profiled()) {
+ struct LongTaskMarker {
+ static constexpr Span<const char> MarkerTypeName() {
+ return MakeStringSpan("MainThreadLongTask");
+ }
+ static void StreamJSONMarkerData(
+ baseprofiler::SpliceableJSONWriter& aWriter) {
+ aWriter.StringProperty("category", "LongTask");
+ }
+ static MarkerSchema MarkerTypeDisplay() {
+ using MS = MarkerSchema;
+ MS schema{MS::Location::markerChart, MS::Location::markerTable};
+ schema.AddKeyLabelFormat("category", "Type", MS::Format::string);
+ return schema;
+ }
+ };
+
+ profiler_add_marker(mCurrentRunnableIsIdleRunnable
+ ? ProfilerString8View("LongIdleTask")
+ : ProfilerString8View("LongTask"),
+ geckoprofiler::category::OTHER,
+ MarkerTiming::Interval(mCurrentTimeSliceStart, aNow),
+ LongTaskMarker{});
+ }
+#endif
+ }
+}
+
+} // namespace mozilla
diff --git a/xpcom/threads/nsThread.h b/xpcom/threads/nsThread.h
new file mode 100644
index 0000000000..305f092dba
--- /dev/null
+++ b/xpcom/threads/nsThread.h
@@ -0,0 +1,444 @@
+/* -*- 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 nsThread_h__
+#define nsThread_h__
+
+#include "MainThreadUtils.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/EventQueue.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/PerformanceCounter.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TaskDispatcher.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "nsIDirectTaskDispatcher.h"
+#include "nsIEventTarget.h"
+#include "nsISerialEventTarget.h"
+#include "nsISupportsPriority.h"
+#include "nsIThread.h"
+#include "nsIThreadInternal.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+class CycleCollectedJSContext;
+class SynchronizedEventQueue;
+class ThreadEventQueue;
+class ThreadEventTarget;
+
+template <typename T, size_t Length>
+class Array;
+} // namespace mozilla
+
+using mozilla::NotNull;
+
+class nsIRunnable;
+class nsLocalExecutionRecord;
+class nsThreadEnumerator;
+
+// See https://www.w3.org/TR/longtasks
+#define LONGTASK_BUSY_WINDOW_MS 50
+
+// A class for managing performance counter state.
+namespace mozilla {
+class PerformanceCounterState {
+ public:
+ explicit PerformanceCounterState(const uint32_t& aNestedEventLoopDepthRef,
+ bool aIsMainThread)
+ : mNestedEventLoopDepth(aNestedEventLoopDepthRef),
+ mIsMainThread(aIsMainThread),
+ // Does it really make sense to initialize these to "now" when we
+ // haven't run any tasks?
+ mLastLongTaskEnd(TimeStamp::Now()),
+ mLastLongNonIdleTaskEnd(mLastLongTaskEnd) {}
+
+ class Snapshot {
+ public:
+ Snapshot(uint32_t aOldEventLoopDepth, PerformanceCounter* aCounter,
+ bool aOldIsIdleRunnable)
+ : mOldEventLoopDepth(aOldEventLoopDepth),
+ mOldPerformanceCounter(aCounter),
+ mOldIsIdleRunnable(aOldIsIdleRunnable) {}
+
+ Snapshot(const Snapshot&) = default;
+ Snapshot(Snapshot&&) = default;
+
+ private:
+ friend class PerformanceCounterState;
+
+ const uint32_t mOldEventLoopDepth;
+ // Non-const so we can move out of it and avoid the extra refcounting.
+ RefPtr<PerformanceCounter> mOldPerformanceCounter;
+ const bool mOldIsIdleRunnable;
+ };
+
+ // Notification that a runnable is about to run. This captures a snapshot of
+ // our current state before we reset to prepare for the new runnable. This
+ // muast be called after mNestedEventLoopDepth has been incremented for the
+ // runnable execution. The performance counter passed in should be the one
+ // for the relevant runnable and may be null. aIsIdleRunnable should be true
+ // if and only if the runnable has idle priority.
+ Snapshot RunnableWillRun(PerformanceCounter* Counter, TimeStamp aNow,
+ bool aIsIdleRunnable);
+
+ // Notification that a runnable finished executing. This must be passed the
+ // snapshot that RunnableWillRun returned for the same runnable. This must be
+ // called before mNestedEventLoopDepth is decremented after the runnable's
+ // execution.
+ void RunnableDidRun(Snapshot&& aSnapshot);
+
+ const TimeStamp& LastLongTaskEnd() const { return mLastLongTaskEnd; }
+ const TimeStamp& LastLongNonIdleTaskEnd() const {
+ return mLastLongNonIdleTaskEnd;
+ }
+
+ private:
+ // Called to report accumulated time, as needed, when we're about to run a
+ // runnable or just finished running one.
+ void MaybeReportAccumulatedTime(TimeStamp aNow);
+
+ // Whether the runnable we are about to run, or just ran, is a nested
+ // runnable, in the sense that there is some other runnable up the stack
+ // spinning the event loop. This must be called before we change our
+ // mCurrentEventLoopDepth (when about to run a new event) or after we restore
+ // it (after we ran one).
+ bool IsNestedRunnable() const {
+ return mNestedEventLoopDepth > mCurrentEventLoopDepth;
+ }
+
+ // The event loop depth of the currently running runnable. Set to the max
+ // value of a uint32_t when there is no runnable running, so when starting to
+ // run a toplevel (not nested) runnable IsNestedRunnable() will test false.
+ uint32_t mCurrentEventLoopDepth = std::numeric_limits<uint32_t>::max();
+
+ // A reference to the nsThread's mNestedEventLoopDepth, so we can
+ // see what it is right now.
+ const uint32_t& mNestedEventLoopDepth;
+
+ // A boolean that indicates whether the currently running runnable is an idle
+ // runnable. Only has a useful value between RunnableWillRun() being called
+ // and RunnableDidRun() returning.
+ bool mCurrentRunnableIsIdleRunnable = false;
+
+ // Whether we're attached to the mainthread nsThread.
+ const bool mIsMainThread;
+
+ // The timestamp from which time to be accounted for should be measured. This
+ // can be the start of a runnable running or the end of a nested runnable
+ // running.
+ TimeStamp mCurrentTimeSliceStart;
+
+ // Information about when long tasks last ended.
+ TimeStamp mLastLongTaskEnd;
+ TimeStamp mLastLongNonIdleTaskEnd;
+
+ // The performance counter to use for accumulating the runtime of
+ // the currently running event. May be null, in which case the
+ // event's running time should not be accounted to any performance
+ // counters.
+ RefPtr<PerformanceCounter> mCurrentPerformanceCounter;
+};
+} // namespace mozilla
+
+// A native thread
+class nsThread : public nsIThreadInternal,
+ public nsISupportsPriority,
+ public nsIDirectTaskDispatcher,
+ private mozilla::LinkedListElement<nsThread> {
+ friend mozilla::LinkedList<nsThread>;
+ friend mozilla::LinkedListElement<nsThread>;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIEVENTTARGET_FULL
+ NS_DECL_NSITHREAD
+ NS_DECL_NSITHREADINTERNAL
+ NS_DECL_NSISUPPORTSPRIORITY
+ NS_DECL_NSIDIRECTTASKDISPATCHER
+
+ enum MainThreadFlag { MAIN_THREAD, NOT_MAIN_THREAD };
+
+ nsThread(NotNull<mozilla::SynchronizedEventQueue*> aQueue,
+ MainThreadFlag aMainThread, uint32_t aStackSize);
+
+ private:
+ nsThread();
+
+ public:
+ // Initialize this as a named wrapper for a new PRThread.
+ nsresult Init(const nsACString& aName);
+
+ // Initialize this as a wrapper for the current PRThread.
+ nsresult InitCurrentThread();
+
+ private:
+ // Initializes the mThreadId and stack base/size members, and adds the thread
+ // to the ThreadList().
+ void InitCommon();
+
+ public:
+ // The PRThread corresponding to this thread.
+ PRThread* GetPRThread() const { return mThread; }
+
+ const void* StackBase() const { return mStackBase; }
+ size_t StackSize() const { return mStackSize; }
+
+ uint32_t ThreadId() const { return mThreadId; }
+
+ // If this flag is true, then the nsThread was created using
+ // nsIThreadManager::NewThread.
+ bool ShutdownRequired() { return mShutdownRequired; }
+
+ // Lets GetRunningEventDelay() determine if the pool this is part
+ // of has an unstarted thread
+ void SetPoolThreadFreePtr(mozilla::Atomic<bool, mozilla::Relaxed>* aPtr) {
+ mIsAPoolThreadFree = aPtr;
+ }
+
+ void SetScriptObserver(mozilla::CycleCollectedJSContext* aScriptObserver);
+
+ uint32_t RecursionDepth() const;
+
+ void ShutdownComplete(NotNull<struct nsThreadShutdownContext*> aContext);
+
+ void WaitForAllAsynchronousShutdowns();
+
+ static const uint32_t kRunnableNameBufSize = 1000;
+ static mozilla::Array<char, kRunnableNameBufSize> sMainThreadRunnableName;
+
+ mozilla::SynchronizedEventQueue* EventQueue() { return mEvents.get(); }
+
+ bool ShuttingDown() const { return mShutdownContext != nullptr; }
+
+ static bool GetLabeledRunnableName(nsIRunnable* aEvent, nsACString& aName,
+ mozilla::EventQueuePriority aPriority);
+
+ virtual mozilla::PerformanceCounter* GetPerformanceCounter(
+ nsIRunnable* aEvent) const;
+
+ static mozilla::PerformanceCounter* GetPerformanceCounterBase(
+ nsIRunnable* aEvent);
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ // Returns the size of this object, its PRThread, and its shutdown contexts,
+ // but excluding its event queues.
+ size_t ShallowSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ size_t SizeOfEventQueues(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ static nsThreadEnumerator Enumerate();
+
+ static uint32_t MaxActiveThreads();
+
+ // When entering local execution mode a new event queue is created and used as
+ // an event source. This queue is only accessible through an
+ // nsLocalExecutionGuard constructed from the nsLocalExecutionRecord returned
+ // by this function, effectively restricting the events that get run while in
+ // local execution mode to those dispatched by the owner of the guard object.
+ //
+ // Local execution is not nestable. When the nsLocalExecutionGuard is
+ // destructed, the thread exits the local execution mode.
+ //
+ // Note that code run in local execution mode is not considered a task in the
+ // spec sense. Events from the local queue are considered part of the
+ // enclosing task and as such do not trigger profiling hooks, observer
+ // notifications, etc.
+ nsLocalExecutionRecord EnterLocalExecution();
+
+ void SetUseHangMonitor(bool aValue) {
+ MOZ_ASSERT(IsOnCurrentThread());
+ mUseHangMonitor = aValue;
+ }
+
+ private:
+ void DoMainThreadSpecificProcessing() const;
+
+ protected:
+ friend class nsThreadShutdownEvent;
+
+ friend class nsThreadEnumerator;
+
+ virtual ~nsThread();
+
+ static void ThreadFunc(void* aArg);
+
+ // Helper
+ already_AddRefed<nsIThreadObserver> GetObserver() {
+ nsIThreadObserver* obs;
+ nsThread::GetObserver(&obs);
+ return already_AddRefed<nsIThreadObserver>(obs);
+ }
+
+ struct nsThreadShutdownContext* ShutdownInternal(bool aSync);
+
+ friend class nsThreadManager;
+ friend class nsThreadPool;
+
+ static mozilla::OffTheBooksMutex& ThreadListMutex();
+ static mozilla::LinkedList<nsThread>& ThreadList();
+ static void ClearThreadList();
+
+ // The current number of active threads.
+ static uint32_t sActiveThreads;
+ // The maximum current number of active threads we've had in this session.
+ static uint32_t sMaxActiveThreads;
+
+ void AddToThreadList();
+ void MaybeRemoveFromThreadList();
+
+ // Whether or not these members have a value determines whether the nsThread
+ // is treated as a full XPCOM thread or as a thin wrapper.
+ //
+ // For full nsThreads, they will always contain valid pointers. For thin
+ // wrappers around non-XPCOM threads, they will be null, and event dispatch
+ // methods which rely on them will fail (and assert) if called.
+ RefPtr<mozilla::SynchronizedEventQueue> mEvents;
+ RefPtr<mozilla::ThreadEventTarget> mEventTarget;
+
+ // The shutdown contexts for any other threads we've asked to shut down.
+ using ShutdownContexts =
+ nsTArray<mozilla::UniquePtr<struct nsThreadShutdownContext>>;
+
+ // Helper for finding a ShutdownContext in the contexts array.
+ struct ShutdownContextsComp {
+ bool Equals(const ShutdownContexts::elem_type& a,
+ const ShutdownContexts::elem_type::Pointer b) const;
+ };
+
+ ShutdownContexts mRequestedShutdownContexts;
+ // The shutdown context for ourselves.
+ struct nsThreadShutdownContext* mShutdownContext;
+
+ mozilla::CycleCollectedJSContext* mScriptObserver;
+
+ void* mStackBase = nullptr;
+ uint32_t mStackSize;
+ uint32_t mThreadId;
+
+ uint32_t mNestedEventLoopDepth;
+
+ mozilla::Atomic<bool> mShutdownRequired;
+
+ int8_t mPriority;
+
+ const bool mIsMainThread;
+ bool mUseHangMonitor;
+ mozilla::Atomic<bool, mozilla::Relaxed>* mIsAPoolThreadFree;
+
+ // Set to true if this thread creates a JSRuntime.
+ bool mCanInvokeJS;
+
+ bool mHasTLSEntry = false;
+
+ // The time the currently running event spent in event queues, and
+ // when it started running. If no event is running, they are
+ // TimeDuration() & TimeStamp().
+ mozilla::TimeDuration mLastEventDelay;
+ mozilla::TimeStamp mLastEventStart;
+
+#ifdef EARLY_BETA_OR_EARLIER
+ nsCString mNameForWakeupTelemetry;
+ mozilla::TimeStamp mLastWakeupCheckTime;
+ uint32_t mWakeupCount = 0;
+#endif
+
+ mozilla::PerformanceCounterState mPerformanceCounterState;
+
+ bool mIsInLocalExecutionMode = false;
+
+ mozilla::SimpleTaskQueue mDirectTasks;
+};
+
+struct nsThreadShutdownContext {
+ nsThreadShutdownContext(NotNull<nsThread*> aTerminatingThread,
+ NotNull<nsThread*> aJoiningThread,
+ bool aAwaitingShutdownAck)
+ : mTerminatingThread(aTerminatingThread),
+ mTerminatingPRThread(aTerminatingThread->GetPRThread()),
+ mJoiningThread(aJoiningThread),
+ mAwaitingShutdownAck(aAwaitingShutdownAck),
+ mIsMainThreadJoining(NS_IsMainThread()) {
+ MOZ_COUNT_CTOR(nsThreadShutdownContext);
+ }
+ MOZ_COUNTED_DTOR(nsThreadShutdownContext)
+
+ // NB: This will be the last reference.
+ NotNull<RefPtr<nsThread>> mTerminatingThread;
+ PRThread* const mTerminatingPRThread;
+ NotNull<nsThread*> MOZ_UNSAFE_REF(
+ "Thread manager is holding reference to joining thread") mJoiningThread;
+ bool mAwaitingShutdownAck;
+ bool mIsMainThreadJoining;
+};
+
+// This RAII class controls the duration of the associated nsThread's local
+// execution mode and provides access to the local event target. (See
+// nsThread::EnterLocalExecution() for details.) It is constructed from an
+// nsLocalExecutionRecord, which can only be constructed by nsThread.
+class MOZ_RAII nsLocalExecutionGuard final {
+ public:
+ MOZ_IMPLICIT nsLocalExecutionGuard(
+ nsLocalExecutionRecord&& aLocalExecutionRecord);
+ nsLocalExecutionGuard(const nsLocalExecutionGuard&) = delete;
+ nsLocalExecutionGuard(nsLocalExecutionGuard&&) = delete;
+ ~nsLocalExecutionGuard();
+
+ nsCOMPtr<nsISerialEventTarget> GetEventTarget() const {
+ return mLocalEventTarget;
+ }
+
+ private:
+ mozilla::SynchronizedEventQueue& mEventQueueStack;
+ nsCOMPtr<nsISerialEventTarget> mLocalEventTarget;
+ bool& mLocalExecutionFlag;
+};
+
+class MOZ_TEMPORARY_CLASS nsLocalExecutionRecord final {
+ private:
+ friend class nsThread;
+ friend class nsLocalExecutionGuard;
+
+ nsLocalExecutionRecord(mozilla::SynchronizedEventQueue& aEventQueueStack,
+ bool& aLocalExecutionFlag)
+ : mEventQueueStack(aEventQueueStack),
+ mLocalExecutionFlag(aLocalExecutionFlag) {}
+
+ nsLocalExecutionRecord(nsLocalExecutionRecord&&) = default;
+
+ public:
+ nsLocalExecutionRecord(const nsLocalExecutionRecord&) = delete;
+
+ private:
+ mozilla::SynchronizedEventQueue& mEventQueueStack;
+ bool& mLocalExecutionFlag;
+};
+
+class MOZ_STACK_CLASS nsThreadEnumerator final {
+ public:
+ nsThreadEnumerator() = default;
+
+ auto begin() { return nsThread::ThreadList().begin(); }
+ auto end() { return nsThread::ThreadList().end(); }
+
+ private:
+ mozilla::OffTheBooksMutexAutoLock mMal{nsThread::ThreadListMutex()};
+};
+
+#if defined(XP_UNIX) && !defined(ANDROID) && !defined(DEBUG) && HAVE_UALARM && \
+ defined(_GNU_SOURCE)
+# define MOZ_CANARY
+
+extern int sCanaryOutputFD;
+#endif
+
+#endif // nsThread_h__
diff --git a/xpcom/threads/nsThreadManager.cpp b/xpcom/threads/nsThreadManager.cpp
new file mode 100644
index 0000000000..c6f3eca640
--- /dev/null
+++ b/xpcom/threads/nsThreadManager.cpp
@@ -0,0 +1,841 @@
+/* -*- 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 "nsThreadManager.h"
+#include "nsThread.h"
+#include "nsThreadPool.h"
+#include "nsThreadUtils.h"
+#include "nsIClassInfoImpl.h"
+#include "nsTArray.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/AbstractThread.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/EventQueue.h"
+#include "mozilla/InputTaskManager.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TaskQueue.h"
+#include "mozilla/ThreadEventQueue.h"
+#include "mozilla/ThreadLocal.h"
+#include "TaskController.h"
+#ifdef MOZ_CANARY
+# include <fcntl.h>
+# include <unistd.h>
+#endif
+
+#include "MainThreadIdlePeriod.h"
+#include "InputEventStatistics.h"
+
+using namespace mozilla;
+
+static MOZ_THREAD_LOCAL(bool) sTLSIsMainThread;
+
+bool NS_IsMainThreadTLSInitialized() { return sTLSIsMainThread.initialized(); }
+
+class BackgroundEventTarget final : public nsIEventTarget {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIEVENTTARGET_FULL
+
+ BackgroundEventTarget();
+
+ nsresult Init();
+
+ already_AddRefed<nsISerialEventTarget> CreateBackgroundTaskQueue(
+ const char* aName);
+
+ void BeginShutdown(nsTArray<RefPtr<ShutdownPromise>>&);
+ void FinishShutdown();
+
+ private:
+ ~BackgroundEventTarget() = default;
+
+ nsCOMPtr<nsIThreadPool> mPool;
+ nsCOMPtr<nsIThreadPool> mIOPool;
+
+ Mutex mMutex;
+ nsTArray<RefPtr<TaskQueue>> mTaskQueues;
+};
+
+NS_IMPL_ISUPPORTS(BackgroundEventTarget, nsIEventTarget)
+
+BackgroundEventTarget::BackgroundEventTarget()
+ : mMutex("BackgroundEventTarget::mMutex") {}
+
+nsresult BackgroundEventTarget::Init() {
+ nsCOMPtr<nsIThreadPool> pool(new nsThreadPool());
+ NS_ENSURE_TRUE(pool, NS_ERROR_FAILURE);
+
+ nsresult rv = pool->SetName("BackgroundThreadPool"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Use potentially more conservative stack size.
+ rv = pool->SetThreadStackSize(nsIThreadManager::kThreadPoolStackSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Thread limit of 2 makes deadlock during synchronous dispatch less likely.
+ rv = pool->SetThreadLimit(2);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = pool->SetIdleThreadLimit(1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Leave threads alive for up to 5 minutes
+ rv = pool->SetIdleThreadTimeout(300000);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Initialize the background I/O event target.
+ nsCOMPtr<nsIThreadPool> ioPool(new nsThreadPool());
+ NS_ENSURE_TRUE(pool, NS_ERROR_FAILURE);
+
+ rv = ioPool->SetName("BgIOThreadPool"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Use potentially more conservative stack size.
+ rv = ioPool->SetThreadStackSize(nsIThreadManager::kThreadPoolStackSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Thread limit of 4 makes deadlock during synchronous dispatch less likely.
+ rv = ioPool->SetThreadLimit(4);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ioPool->SetIdleThreadLimit(1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Leave threads alive for up to 5 minutes
+ rv = ioPool->SetIdleThreadTimeout(300000);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ pool.swap(mPool);
+ ioPool.swap(mIOPool);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(bool)
+BackgroundEventTarget::IsOnCurrentThreadInfallible() {
+ return mPool->IsOnCurrentThread() || mIOPool->IsOnCurrentThread();
+}
+
+NS_IMETHODIMP
+BackgroundEventTarget::IsOnCurrentThread(bool* aValue) {
+ bool value = false;
+ if (NS_SUCCEEDED(mPool->IsOnCurrentThread(&value)) && value) {
+ *aValue = value;
+ return NS_OK;
+ }
+ return mIOPool->IsOnCurrentThread(aValue);
+}
+
+NS_IMETHODIMP
+BackgroundEventTarget::Dispatch(already_AddRefed<nsIRunnable> aRunnable,
+ uint32_t aFlags) {
+ // We need to be careful here, because if an event is getting dispatched here
+ // from within TaskQueue::Runner::Run, it will be dispatched with
+ // NS_DISPATCH_AT_END, but we might not be running the event on the same
+ // pool, depending on which pool we were on and the dispatch flags. If we
+ // dispatch an event with NS_DISPATCH_AT_END to the wrong pool, the pool
+ // may not process the event in a timely fashion, which can lead to deadlock.
+ uint32_t flags = aFlags & ~NS_DISPATCH_EVENT_MAY_BLOCK;
+ bool mayBlock = bool(aFlags & NS_DISPATCH_EVENT_MAY_BLOCK);
+ nsCOMPtr<nsIThreadPool>& pool = mayBlock ? mIOPool : mPool;
+
+ // If we're already running on the pool we want to dispatch to, we can
+ // unconditionally add NS_DISPATCH_AT_END to indicate that we shouldn't spin
+ // up a new thread.
+ //
+ // Otherwise, we should remove NS_DISPATCH_AT_END so we don't run into issues
+ // like those in the above comment.
+ if (pool->IsOnCurrentThread()) {
+ flags |= NS_DISPATCH_AT_END;
+ } else {
+ flags &= ~NS_DISPATCH_AT_END;
+ }
+
+ return pool->Dispatch(std::move(aRunnable), flags);
+}
+
+NS_IMETHODIMP
+BackgroundEventTarget::DispatchFromScript(nsIRunnable* aRunnable,
+ uint32_t aFlags) {
+ nsCOMPtr<nsIRunnable> runnable(aRunnable);
+ return Dispatch(runnable.forget(), aFlags);
+}
+
+NS_IMETHODIMP
+BackgroundEventTarget::DelayedDispatch(already_AddRefed<nsIRunnable> aRunnable,
+ uint32_t) {
+ nsCOMPtr<nsIRunnable> dropRunnable(aRunnable);
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void BackgroundEventTarget::BeginShutdown(
+ nsTArray<RefPtr<ShutdownPromise>>& promises) {
+ for (auto& queue : mTaskQueues) {
+ promises.AppendElement(queue->BeginShutdown());
+ }
+}
+
+void BackgroundEventTarget::FinishShutdown() {
+ mPool->Shutdown();
+ mIOPool->Shutdown();
+}
+
+already_AddRefed<nsISerialEventTarget>
+BackgroundEventTarget::CreateBackgroundTaskQueue(const char* aName) {
+ MutexAutoLock lock(mMutex);
+
+ RefPtr<TaskQueue> queue = new TaskQueue(do_AddRef(this), aName);
+ mTaskQueues.AppendElement(queue);
+
+ return queue.forget();
+}
+
+extern "C" {
+// This uses the C language linkage because it's exposed to Rust
+// via the xpcom/rust/moz_task crate.
+bool NS_IsMainThread() { return sTLSIsMainThread.get(); }
+}
+
+void NS_SetMainThread() {
+ if (!sTLSIsMainThread.init()) {
+ MOZ_CRASH();
+ }
+ sTLSIsMainThread.set(true);
+ MOZ_ASSERT(NS_IsMainThread());
+ // We initialize the SerialEventTargetGuard's TLS here for simplicity as it
+ // needs to be initialized around the same time you would initialize
+ // sTLSIsMainThread.
+ SerialEventTargetGuard::InitTLS();
+}
+
+#ifdef DEBUG
+
+namespace mozilla {
+
+void AssertIsOnMainThread() { MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); }
+
+} // namespace mozilla
+
+#endif
+
+typedef nsTArray<NotNull<RefPtr<nsThread>>> nsThreadArray;
+
+static Atomic<bool> sShutdownComplete;
+
+//-----------------------------------------------------------------------------
+
+/* static */
+void nsThreadManager::ReleaseThread(void* aData) {
+ if (sShutdownComplete) {
+ // We've already completed shutdown and released the references to all or
+ // our TLS wrappers. Don't try to release them again.
+ return;
+ }
+
+ auto* thread = static_cast<nsThread*>(aData);
+
+ if (thread->mHasTLSEntry) {
+ thread->mHasTLSEntry = false;
+ thread->Release();
+ }
+}
+
+// statically allocated instance
+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_INTERFACE_CI(nsThreadManager, nsIThreadManager)
+NS_IMPL_CI_INTERFACE_GETTER(nsThreadManager, nsIThreadManager)
+
+namespace {
+
+// Simple observer to monitor the beginning of the shutdown.
+class ShutdownObserveHelper final : public nsIObserver,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS
+
+ static nsresult Create(ShutdownObserveHelper** aObserver) {
+ MOZ_ASSERT(aObserver);
+
+ RefPtr<ShutdownObserveHelper> observer = new ShutdownObserveHelper();
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (NS_WARN_IF(!obs)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv =
+ obs->AddObserver(observer, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = obs->AddObserver(observer, "content-child-will-shutdown", true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ observer.forget(aObserver);
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) override {
+ if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) ||
+ !strcmp(aTopic, "content-child-will-shutdown")) {
+ mShuttingDown = true;
+ return NS_OK;
+ }
+
+ return NS_OK;
+ }
+
+ bool ShuttingDown() const { return mShuttingDown; }
+
+ private:
+ explicit ShutdownObserveHelper() : mShuttingDown(false) {}
+
+ ~ShutdownObserveHelper() = default;
+
+ bool mShuttingDown;
+};
+
+NS_INTERFACE_MAP_BEGIN(ShutdownObserveHelper)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(ShutdownObserveHelper)
+NS_IMPL_RELEASE(ShutdownObserveHelper)
+
+StaticRefPtr<ShutdownObserveHelper> gShutdownObserveHelper;
+
+} // namespace
+
+//-----------------------------------------------------------------------------
+
+/*static*/ nsThreadManager& nsThreadManager::get() {
+ static nsThreadManager sInstance;
+ return sInstance;
+}
+
+/* static */
+void nsThreadManager::InitializeShutdownObserver() {
+ MOZ_ASSERT(!gShutdownObserveHelper);
+
+ RefPtr<ShutdownObserveHelper> observer;
+ nsresult rv = ShutdownObserveHelper::Create(getter_AddRefs(observer));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ gShutdownObserveHelper = observer;
+ ClearOnShutdown(&gShutdownObserveHelper);
+}
+
+nsThreadManager::nsThreadManager()
+ : mCurThreadIndex(0), mMainPRThread(nullptr), mInitialized(false) {}
+
+nsThreadManager::~nsThreadManager() = default;
+
+nsresult nsThreadManager::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, ReleaseThread) == PR_FAILURE) {
+ return NS_ERROR_FAILURE;
+ }
+
+#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
+
+ TaskController::Initialize();
+
+ // Initialize idle handling.
+ nsCOMPtr<nsIIdlePeriod> idlePeriod = new MainThreadIdlePeriod();
+ TaskController::Get()->SetIdleTaskManager(
+ new IdleTaskManager(idlePeriod.forget()));
+
+ // Create main thread queue that forwards events to TaskController and
+ // construct main thread.
+ UniquePtr<EventQueue> queue = MakeUnique<EventQueue>(true);
+
+ RefPtr<ThreadEventQueue> synchronizedQueue =
+ new ThreadEventQueue(std::move(queue), true);
+
+ mMainThread =
+ new nsThread(WrapNotNull(synchronizedQueue), nsThread::MAIN_THREAD, 0);
+
+ nsresult rv = mMainThread->InitCurrentThread();
+ if (NS_FAILED(rv)) {
+ mMainThread = nullptr;
+ return rv;
+ }
+
+ // We need to keep a pointer to the current thread, so we can satisfy
+ // GetIsMainThread calls that occur post-Shutdown.
+ mMainThread->GetPRThread(&mMainPRThread);
+
+ // Init AbstractThread.
+ AbstractThread::InitTLS();
+ AbstractThread::InitMainThread();
+
+ // Initialize the background event target.
+ RefPtr<BackgroundEventTarget> target(new BackgroundEventTarget());
+
+ rv = target->Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mBackgroundEventTarget = std::move(target);
+
+ mInitialized = true;
+
+ return NS_OK;
+}
+
+void nsThreadManager::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread(), "shutdown not called from main thread");
+
+ // Prevent further access to the thread manager (no more new threads!)
+ //
+ // 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;
+
+ // Empty the main thread event queue before we begin shutting down threads.
+ NS_ProcessPendingEvents(mMainThread);
+
+ typedef typename ShutdownPromise::AllPromiseType AllPromise;
+ typename AllPromise::ResolveOrRejectValue val;
+ using ResolveValueT = typename AllPromise::ResolveValueType;
+ using RejectValueT = typename AllPromise::RejectValueType;
+
+ nsTArray<RefPtr<ShutdownPromise>> promises;
+ mBackgroundEventTarget->BeginShutdown(promises);
+
+ RefPtr<AllPromise> complete = ShutdownPromise::All(mMainThread, promises);
+
+ bool taskQueuesShutdown = false;
+
+ complete->Then(
+ mMainThread, __func__,
+ [&](const ResolveValueT& aResolveValue) {
+ mBackgroundEventTarget->FinishShutdown();
+ taskQueuesShutdown = true;
+ },
+ [&](RejectValueT aRejectValue) {
+ mBackgroundEventTarget->FinishShutdown();
+ taskQueuesShutdown = true;
+ });
+
+ // Wait for task queues to shutdown, so we don't shut down the underlying
+ // threads of the background event target in the block below, thereby
+ // preventing the task queues from emptying, preventing the shutdown promises
+ // from resolving, and prevent anything checking `taskQueuesShutdown` from
+ // working.
+ ::SpinEventLoopUntil([&]() { return taskQueuesShutdown; }, mMainThread);
+
+ {
+ // We gather the threads from the hashtable into a list, so that we avoid
+ // holding the enumerator lock while calling nsIThread::Shutdown.
+ nsTArray<RefPtr<nsThread>> threadsToShutdown;
+ for (auto* thread : nsThread::Enumerate()) {
+ if (thread->ShutdownRequired()) {
+ threadsToShutdown.AppendElement(thread);
+ }
+ }
+
+ // It's tempting to walk the list of threads here and tell them each to stop
+ // 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 (auto& thread : threadsToShutdown) {
+ thread->Shutdown();
+ }
+ }
+
+ // NB: It's possible that there are events in the queue that want to *start*
+ // an asynchronous shutdown. But we have already shutdown the threads above,
+ // so there's no need to worry about them. We only have to wait for all
+ // in-flight asynchronous thread shutdowns to complete.
+ mMainThread->WaitForAllAsynchronousShutdowns();
+
+ // In case there are any more events somehow...
+ NS_ProcessPendingEvents(mMainThread);
+
+ // There are no more background threads at this point.
+
+ // Normally thread shutdown clears the observer for the thread, but since the
+ // main thread is special we do it manually here after we're sure all events
+ // have been processed.
+ mMainThread->SetObserver(nullptr);
+
+ mBackgroundEventTarget = nullptr;
+
+ // Release main thread object.
+ mMainThread = nullptr;
+
+ // Remove the TLS entry for the main thread.
+ PR_SetThreadPrivate(mCurThreadIndex, nullptr);
+
+ {
+ // Cleanup the last references to any threads which haven't shut down yet.
+ nsTArray<RefPtr<nsThread>> threads;
+ for (auto* thread : nsThread::Enumerate()) {
+ if (thread->mHasTLSEntry) {
+ threads.AppendElement(dont_AddRef(thread));
+ thread->mHasTLSEntry = false;
+ }
+ }
+ }
+
+ // xpcshell tests sometimes leak the main thread. They don't enable leak
+ // checking, so that doesn't cause the test to fail, but leaving the entry in
+ // the thread list triggers an assertion, which does.
+ nsThread::ClearThreadList();
+
+ sShutdownComplete = true;
+}
+
+void nsThreadManager::RegisterCurrentThread(nsThread& aThread) {
+ MOZ_ASSERT(aThread.GetPRThread() == PR_GetCurrentThread(), "bad aThread");
+
+ aThread.AddRef(); // for TLS entry
+ aThread.mHasTLSEntry = true;
+ PR_SetThreadPrivate(mCurThreadIndex, &aThread);
+}
+
+void nsThreadManager::UnregisterCurrentThread(nsThread& aThread) {
+ MOZ_ASSERT(aThread.GetPRThread() == PR_GetCurrentThread(), "bad aThread");
+
+ PR_SetThreadPrivate(mCurThreadIndex, nullptr);
+ // Ref-count balanced via ReleaseThread
+}
+
+nsThread* nsThreadManager::CreateCurrentThread(
+ SynchronizedEventQueue* aQueue, nsThread::MainThreadFlag aMainThread) {
+ // Make sure we don't have an nsThread yet.
+ MOZ_ASSERT(!PR_GetThreadPrivate(mCurThreadIndex));
+
+ if (!mInitialized) {
+ return nullptr;
+ }
+
+ RefPtr<nsThread> thread = new nsThread(WrapNotNull(aQueue), aMainThread, 0);
+ if (!thread || NS_FAILED(thread->InitCurrentThread())) {
+ return nullptr;
+ }
+
+ return thread.get(); // reference held in TLS
+}
+
+nsresult nsThreadManager::DispatchToBackgroundThread(nsIRunnable* aEvent,
+ uint32_t aDispatchFlags) {
+ if (!mInitialized) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIEventTarget> backgroundTarget(mBackgroundEventTarget);
+ return backgroundTarget->Dispatch(aEvent, aDispatchFlags);
+}
+
+already_AddRefed<nsISerialEventTarget>
+nsThreadManager::CreateBackgroundTaskQueue(const char* aName) {
+ if (!mInitialized) {
+ return nullptr;
+ }
+
+ return mBackgroundEventTarget->CreateBackgroundTaskQueue(aName);
+}
+
+nsThread* nsThreadManager::GetCurrentThread() {
+ // read thread local storage
+ void* data = PR_GetThreadPrivate(mCurThreadIndex);
+ if (data) {
+ return static_cast<nsThread*>(data);
+ }
+
+ if (!mInitialized) {
+ return nullptr;
+ }
+
+ // OK, that's fine. We'll dynamically create one :-)
+ //
+ // We assume that if we're implicitly creating a thread here that it doesn't
+ // want an event queue. Any thread which wants an event queue should
+ // explicitly create its nsThread wrapper.
+ RefPtr<nsThread> thread = new nsThread();
+ if (!thread || NS_FAILED(thread->InitCurrentThread())) {
+ return nullptr;
+ }
+
+ return thread.get(); // reference held in TLS
+}
+
+bool nsThreadManager::IsNSThread() const {
+ if (!mInitialized) {
+ return false;
+ }
+ if (auto* thread = (nsThread*)PR_GetThreadPrivate(mCurThreadIndex)) {
+ return thread->EventQueue();
+ }
+ return false;
+}
+
+NS_IMETHODIMP
+nsThreadManager::NewThread(uint32_t aCreationFlags, uint32_t aStackSize,
+ nsIThread** aResult) {
+ return NewNamedThread(""_ns, aStackSize, aResult);
+}
+
+NS_IMETHODIMP
+nsThreadManager::NewNamedThread(const nsACString& aName, uint32_t aStackSize,
+ nsIThread** aResult) {
+ // Note: can be called from arbitrary threads
+
+ // No new threads during Shutdown
+ if (NS_WARN_IF(!mInitialized)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ TimeStamp startTime = TimeStamp::Now();
+
+ RefPtr<ThreadEventQueue> queue =
+ new ThreadEventQueue(MakeUnique<EventQueue>());
+ RefPtr<nsThread> thr =
+ new nsThread(WrapNotNull(queue), nsThread::NOT_MAIN_THREAD, aStackSize);
+ nsresult rv =
+ thr->Init(aName); // Note: blocks until the new thread has been set up
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // 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. 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;
+ }
+
+ PROFILER_MARKER_TEXT(
+ "NewThread", OTHER,
+ MarkerOptions(MarkerStack::Capture(),
+ MarkerTiming::IntervalUntilNowFrom(startTime)),
+ aName);
+ if (!NS_IsMainThread()) {
+ PROFILER_MARKER_TEXT(
+ "NewThread (non-main thread)", OTHER,
+ MarkerOptions(MarkerStack::Capture(), MarkerThreadId::MainThread(),
+ MarkerTiming::IntervalUntilNowFrom(startTime)),
+ aName);
+ }
+
+ thr.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadManager::GetMainThread(nsIThread** aResult) {
+ // Keep this functioning during Shutdown
+ if (!mMainThread) {
+ if (!NS_IsMainThread()) {
+ NS_WARNING(
+ "Called GetMainThread but there isn't a main thread and "
+ "we're not the main thread.");
+ }
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ NS_ADDREF(*aResult = mMainThread);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadManager::GetCurrentThread(nsIThread** aResult) {
+ // Keep this functioning during Shutdown
+ if (!mMainThread) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ *aResult = GetCurrentThread();
+ if (!*aResult) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadManager::SpinEventLoopUntil(nsINestedEventLoopCondition* aCondition) {
+ return SpinEventLoopUntilInternal(aCondition, false);
+}
+
+NS_IMETHODIMP
+nsThreadManager::SpinEventLoopUntilOrShutdown(
+ nsINestedEventLoopCondition* aCondition) {
+ return SpinEventLoopUntilInternal(aCondition, true);
+}
+
+nsresult nsThreadManager::SpinEventLoopUntilInternal(
+ nsINestedEventLoopCondition* aCondition, bool aCheckingShutdown) {
+ nsCOMPtr<nsINestedEventLoopCondition> condition(aCondition);
+ nsresult rv = NS_OK;
+
+ // Nothing to do if already shutting down. Note that gShutdownObserveHelper is
+ // nullified on shutdown.
+ if (aCheckingShutdown &&
+ (!gShutdownObserveHelper || gShutdownObserveHelper->ShuttingDown())) {
+ return NS_OK;
+ }
+
+ if (!mozilla::SpinEventLoopUntil([&]() -> bool {
+ // Shutting down is started.
+ if (aCheckingShutdown && (!gShutdownObserveHelper ||
+ gShutdownObserveHelper->ShuttingDown())) {
+ return true;
+ }
+
+ bool isDone = false;
+ rv = condition->IsDone(&isDone);
+ // JS failure should be unusual, but we need to stop and propagate
+ // the error back to the caller.
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+
+ return isDone;
+ })) {
+ // We stopped early for some reason, which is unexpected.
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // If we exited when the condition told us to, we need to return whether
+ // the condition encountered failure when executing.
+ return rv;
+}
+
+NS_IMETHODIMP
+nsThreadManager::SpinEventLoopUntilEmpty() {
+ nsIThread* thread = NS_GetCurrentThread();
+
+ while (NS_HasPendingEvents(thread)) {
+ (void)NS_ProcessNextEvent(thread, false);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadManager::GetMainThreadEventTarget(nsIEventTarget** aTarget) {
+ nsCOMPtr<nsIEventTarget> target = GetMainThreadSerialEventTarget();
+ target.forget(aTarget);
+ return NS_OK;
+}
+
+uint32_t nsThreadManager::GetHighestNumberOfThreads() {
+ return nsThread::MaxActiveThreads();
+}
+
+NS_IMETHODIMP
+nsThreadManager::DispatchToMainThread(nsIRunnable* aEvent, uint32_t aPriority,
+ uint8_t aArgc) {
+ // Note: C++ callers should instead use NS_DispatchToMainThread.
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Keep this functioning during Shutdown
+ if (NS_WARN_IF(!mMainThread)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ // If aPriority wasn't explicitly passed, that means it should be treated as
+ // PRIORITY_NORMAL.
+ if (aArgc > 0 && aPriority != nsIRunnablePriority::PRIORITY_NORMAL) {
+ nsCOMPtr<nsIRunnable> event(aEvent);
+ return mMainThread->DispatchFromScript(
+ new PrioritizableRunnable(event.forget(), aPriority), 0);
+ }
+ return mMainThread->DispatchFromScript(aEvent, 0);
+}
+
+void nsThreadManager::EnableMainThreadEventPrioritization() {
+ MOZ_ASSERT(NS_IsMainThread());
+ InputEventStatistics::Get().SetEnable(true);
+ InputTaskManager::Get()->EnableInputEventPrioritization();
+}
+
+void nsThreadManager::FlushInputEventPrioritization() {
+ MOZ_ASSERT(NS_IsMainThread());
+ InputTaskManager::Get()->FlushInputEventPrioritization();
+}
+
+void nsThreadManager::SuspendInputEventPrioritization() {
+ MOZ_ASSERT(NS_IsMainThread());
+ InputTaskManager::Get()->SuspendInputEventPrioritization();
+}
+
+void nsThreadManager::ResumeInputEventPrioritization() {
+ MOZ_ASSERT(NS_IsMainThread());
+ InputTaskManager::Get()->ResumeInputEventPrioritization();
+}
+
+// static
+bool nsThreadManager::MainThreadHasPendingHighPriorityEvents() {
+ MOZ_ASSERT(NS_IsMainThread());
+ bool retVal = false;
+ if (get().mMainThread) {
+ get().mMainThread->HasPendingHighPriorityEvents(&retVal);
+ }
+ return retVal;
+}
+
+NS_IMETHODIMP
+nsThreadManager::IdleDispatchToMainThread(nsIRunnable* aEvent,
+ uint32_t aTimeout) {
+ // Note: C++ callers should instead use NS_DispatchToThreadQueue or
+ // NS_DispatchToCurrentThreadQueue.
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIRunnable> event(aEvent);
+ if (aTimeout) {
+ return NS_DispatchToThreadQueue(event.forget(), aTimeout, mMainThread,
+ EventQueuePriority::Idle);
+ }
+
+ return NS_DispatchToThreadQueue(event.forget(), mMainThread,
+ EventQueuePriority::Idle);
+}
diff --git a/xpcom/threads/nsThreadManager.h b/xpcom/threads/nsThreadManager.h
new file mode 100644
index 0000000000..fa223702aa
--- /dev/null
+++ b/xpcom/threads/nsThreadManager.h
@@ -0,0 +1,111 @@
+/* -*- 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 nsThreadManager_h__
+#define nsThreadManager_h__
+
+#include "nsIThreadManager.h"
+#include "nsThread.h"
+
+class nsIRunnable;
+class nsIEventTarget;
+class nsISerialEventTarget;
+class nsIThread;
+
+namespace mozilla {
+class IdleTaskManager;
+class SynchronizedEventQueue;
+} // namespace mozilla
+
+class BackgroundEventTarget;
+
+class nsThreadManager : public nsIThreadManager {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITHREADMANAGER
+
+ static nsThreadManager& get();
+
+ static void InitializeShutdownObserver();
+
+ nsresult Init();
+
+ // Shutdown all threads. This function should only be called on the main
+ // thread of the application process.
+ void Shutdown();
+
+ // 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& 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& aThread);
+
+ // Returns the current thread. Returns null if OOM or if ThreadManager isn't
+ // initialized. Creates the nsThread if one does not exist yet.
+ nsThread* GetCurrentThread();
+
+ // Returns true iff the currently running thread has an nsThread associated
+ // with it (ie; whether this is a thread that we can dispatch runnables to).
+ bool IsNSThread() const;
+
+ // CreateCurrentThread sets up an nsThread for the current thread. It uses the
+ // event queue and main thread flags passed in. It should only be called once
+ // for the current thread. After it returns, GetCurrentThread() will return
+ // the thread that was created. GetCurrentThread() will also create a thread
+ // (lazily), but it doesn't allow the queue or main-thread attributes to be
+ // specified.
+ nsThread* CreateCurrentThread(mozilla::SynchronizedEventQueue* aQueue,
+ nsThread::MainThreadFlag aMainThread);
+
+ nsresult DispatchToBackgroundThread(nsIRunnable* aEvent,
+ uint32_t aDispatchFlags);
+
+ already_AddRefed<nsISerialEventTarget> CreateBackgroundTaskQueue(
+ const char* aName);
+
+ // Returns the maximal number of threads that have been in existence
+ // simultaneously during the execution of the thread manager.
+ uint32_t GetHighestNumberOfThreads();
+
+ ~nsThreadManager();
+
+ void EnableMainThreadEventPrioritization();
+ void FlushInputEventPrioritization();
+ void SuspendInputEventPrioritization();
+ void ResumeInputEventPrioritization();
+
+ static bool MainThreadHasPendingHighPriorityEvents();
+
+ nsIThread* GetMainThreadWeak() { return mMainThread; }
+
+ private:
+ nsThreadManager();
+
+ nsresult SpinEventLoopUntilInternal(nsINestedEventLoopCondition* aCondition,
+ bool aCheckingShutdown);
+
+ static void ReleaseThread(void* aData);
+
+ unsigned mCurThreadIndex; // thread-local-storage index
+ RefPtr<mozilla::IdleTaskManager> mIdleTaskManager;
+ RefPtr<nsThread> mMainThread;
+ PRThread* mMainPRThread;
+ mozilla::Atomic<bool, mozilla::SequentiallyConsistent> mInitialized;
+
+ // Shared event target used for background runnables.
+ RefPtr<BackgroundEventTarget> mBackgroundEventTarget;
+};
+
+#define NS_THREADMANAGER_CID \
+ { /* 7a4204c6-e45a-4c37-8ebb-6709a22c917c */ \
+ 0x7a4204c6, 0xe45a, 0x4c37, { \
+ 0x8e, 0xbb, 0x67, 0x09, 0xa2, 0x2c, 0x91, 0x7c \
+ } \
+ }
+
+#endif // nsThreadManager_h__
diff --git a/xpcom/threads/nsThreadPool.cpp b/xpcom/threads/nsThreadPool.cpp
new file mode 100644
index 0000000000..0d7aca27ec
--- /dev/null
+++ b/xpcom/threads/nsThreadPool.cpp
@@ -0,0 +1,627 @@
+/* -*- 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 "nsCOMArray.h"
+#include "ThreadDelay.h"
+#include "nsThreadPool.h"
+#include "nsThreadManager.h"
+#include "nsThread.h"
+#include "nsMemory.h"
+#include "prinrval.h"
+#include "mozilla/Logging.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "nsThreadSyncDispatch.h"
+
+#include <mutex>
+
+using namespace mozilla;
+
+static LazyLogModule sThreadPoolLog("nsThreadPool");
+#ifdef LOG
+# undef LOG
+#endif
+#define LOG(args) MOZ_LOG(sThreadPoolLog, mozilla::LogLevel::Debug, args)
+
+static MOZ_THREAD_LOCAL(nsThreadPool*) gCurrentThreadPool;
+
+// DESIGN:
+// o Allocate anonymous threads.
+// o Use nsThreadPool::Run as the main routine for each thread.
+// o Each thread waits on the event queue's monitor, checking for
+// pending events and rescheduling itself as an idle thread.
+
+#define DEFAULT_THREAD_LIMIT 4
+#define DEFAULT_IDLE_THREAD_LIMIT 1
+#define DEFAULT_IDLE_THREAD_TIMEOUT PR_SecondsToInterval(60)
+
+NS_IMPL_ISUPPORTS_INHERITED(nsThreadPool, Runnable, nsIThreadPool,
+ nsIEventTarget)
+
+nsThreadPool::nsThreadPool()
+ : Runnable("nsThreadPool"),
+ mMutex("[nsThreadPool.mMutex]"),
+ mEventsAvailable(mMutex, "[nsThreadPool.mEventsAvailable]"),
+ mThreadLimit(DEFAULT_THREAD_LIMIT),
+ mIdleThreadLimit(DEFAULT_IDLE_THREAD_LIMIT),
+ mIdleThreadTimeout(DEFAULT_IDLE_THREAD_TIMEOUT),
+ mIdleCount(0),
+ mStackSize(nsIThreadManager::DEFAULT_STACK_SIZE),
+ mShutdown(false),
+ mRegressiveMaxIdleTime(false),
+ mIsAPoolThreadFree(true) {
+ static std::once_flag flag;
+ std::call_once(flag, [] { gCurrentThreadPool.infallibleInit(); });
+
+ LOG(("THRD-P(%p) constructor!!!\n", this));
+}
+
+nsThreadPool::~nsThreadPool() {
+ // 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* aEvent) {
+ nsCOMPtr<nsIRunnable> event(aEvent);
+ return PutEvent(event.forget(), 0);
+}
+
+nsresult nsThreadPool::PutEvent(already_AddRefed<nsIRunnable> aEvent,
+ uint32_t aFlags) {
+ // Avoid spawning a new thread while holding the event queue lock...
+
+ bool spawnThread = false;
+ uint32_t stackSize = 0;
+ {
+ MutexAutoLock lock(mMutex);
+
+ 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");
+
+ // Make sure we have a thread to service this event.
+ if (mThreads.Count() < (int32_t)mThreadLimit &&
+ !(aFlags & NS_DISPATCH_AT_END) &&
+ // Spawn a new thread if we don't have enough idle threads to serve
+ // pending events immediately.
+ mEvents.Count(lock) >= mIdleCount) {
+ spawnThread = true;
+ }
+
+ nsCOMPtr<nsIRunnable> event(aEvent);
+ LogRunnable::LogDispatch(event);
+ mEvents.PutEvent(event.forget(), EventQueuePriority::Normal, lock);
+ mEventsAvailable.Notify();
+ stackSize = mStackSize;
+ }
+
+ auto delay = MakeScopeExit([&]() {
+ // Delay to encourage the receiving task to run before we do work.
+ DelayForChaosMode(ChaosFeature::TaskDispatching, 1000);
+ });
+
+ LOG(("THRD-P(%p) put [spawn=%d]\n", this, spawnThread));
+ if (!spawnThread) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIThread> thread;
+ nsresult rv = NS_NewNamedThread(mThreadNaming.GetNextThreadName(mName),
+ getter_AddRefs(thread), nullptr, stackSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ bool killThread = false;
+ {
+ MutexAutoLock lock(mMutex);
+ if (mShutdown) {
+ killThread = true;
+ } else if (mThreads.Count() < (int32_t)mThreadLimit) {
+ mThreads.AppendObject(thread);
+ if (mThreads.Count() >= (int32_t)mThreadLimit) {
+ mIsAPoolThreadFree = false;
+ }
+ } else {
+ // Someone else may have also been starting a thread
+ killThread = true; // okay, we don't need this thread anymore
+ }
+ }
+ LOG(("THRD-P(%p) put [%p kill=%d]\n", this, thread.get(), killThread));
+ if (killThread) {
+ // We never dispatched any events to the thread, so we can shut it down
+ // asynchronously without worrying about anything.
+ ShutdownThread(thread);
+ } else {
+ thread->Dispatch(this, NS_DISPATCH_NORMAL);
+ }
+
+ return NS_OK;
+}
+
+void nsThreadPool::ShutdownThread(nsIThread* aThread) {
+ LOG(("THRD-P(%p) shutdown async [%p]\n", this, aThread));
+
+ // This is either called by a threadpool thread that is out of work, or
+ // a thread that attempted to create a threadpool thread and raced in
+ // such a way that the newly created thread is no longer necessary.
+ // In the first case, we must go to another thread to shut aThread down
+ // (because it is the current thread). In the second case, we cannot
+ // synchronously shut down the current thread (because then Dispatch() would
+ // spin the event loop, and that could blow up the world), and asynchronous
+ // shutdown requires this thread have an event loop (and it may not, see bug
+ // 10204784). The simplest way to cover all cases is to asynchronously
+ // shutdown aThread from the main thread.
+ SchedulerGroup::Dispatch(
+ TaskCategory::Other,
+ NewRunnableMethod("nsIThread::AsyncShutdown", aThread,
+ &nsIThread::AsyncShutdown));
+}
+
+// This event 'runs' for the lifetime of the worker thread. The actual
+// eventqueue is mEvents, and is shared by all the worker threads. This
+// means that the set of threads together define the delay seen by a new
+// event sent to the pool.
+//
+// To model the delay experienced by the pool, we can have each thread in
+// the pool report 0 if it's idle OR if the pool is below the threadlimit;
+// or otherwise the current event's queuing delay plus current running
+// time.
+//
+// To reconstruct the delays for the pool, the profiler can look at all the
+// threads that are part of a pool (pools have defined naming patterns that
+// can be user to connect them). If all threads have delays at time X,
+// that means that all threads saturated at that point and any event
+// dispatched to the pool would get a delay.
+//
+// The delay experienced by an event dispatched when all pool threads are
+// busy is based on the calculations shown in platform.cpp. Run that
+// algorithm for each thread in the pool, and the delay at time X is the
+// longest value for time X of any of the threads, OR the time from X until
+// any one of the threads reports 0 (i.e. it's not busy), whichever is
+// shorter.
+
+// In order to record this when the profiler samples threads in the pool,
+// each thread must (effectively) override GetRunnningEventDelay, by
+// resetting the mLastEventDelay/Start values in the nsThread when we start
+// to run an event (or when we run out of events to run). Note that handling
+// the shutdown of a thread may be a little tricky.
+
+NS_IMETHODIMP
+nsThreadPool::Run() {
+ LOG(("THRD-P(%p) enter %s\n", this, mName.BeginReading()));
+
+ nsCOMPtr<nsIThread> current;
+ nsThreadManager::get().GetCurrentThread(getter_AddRefs(current));
+
+ bool shutdownThreadOnExit = false;
+ bool exitThread = false;
+ bool wasIdle = false;
+ TimeStamp idleSince;
+
+ // This thread is an nsThread created below with NS_NewNamedThread()
+ static_cast<nsThread*>(current.get())
+ ->SetPoolThreadFreePtr(&mIsAPoolThreadFree);
+
+ nsCOMPtr<nsIThreadPoolListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ listener = mListener;
+ }
+
+ if (listener) {
+ listener->OnThreadCreated();
+ }
+
+ MOZ_ASSERT(!gCurrentThreadPool.get());
+ gCurrentThreadPool.set(this);
+
+ do {
+ nsCOMPtr<nsIRunnable> event;
+ TimeDuration delay;
+ {
+ MutexAutoLock lock(mMutex);
+
+ event = mEvents.GetEvent(lock, &delay);
+ if (!event) {
+ TimeStamp now = TimeStamp::Now();
+ uint32_t idleTimeoutDivider =
+ (mIdleCount && mRegressiveMaxIdleTime) ? mIdleCount : 1;
+ TimeDuration timeout = TimeDuration::FromMilliseconds(
+ static_cast<double>(mIdleThreadTimeout) / idleTimeoutDivider);
+
+ // If we are shutting down, then don't keep any idle threads
+ if (mShutdown) {
+ exitThread = true;
+ } else {
+ if (wasIdle) {
+ // if too many idle threads or idle for too long, then bail.
+ if (mIdleCount > mIdleThreadLimit ||
+ (mIdleThreadTimeout != UINT32_MAX &&
+ (now - idleSince) >= timeout)) {
+ exitThread = true;
+ }
+ } else {
+ // if would be too many idle threads...
+ if (mIdleCount == mIdleThreadLimit) {
+ exitThread = true;
+ } else {
+ ++mIdleCount;
+ idleSince = now;
+ wasIdle = true;
+ }
+ }
+ }
+
+ if (exitThread) {
+ if (wasIdle) {
+ --mIdleCount;
+ }
+ shutdownThreadOnExit = mThreads.RemoveObject(current);
+
+ // keep track if there are threads available to start
+ mIsAPoolThreadFree = (mThreads.Count() < (int32_t)mThreadLimit);
+ } else {
+ current->SetRunningEventDelay(TimeDuration(), TimeStamp());
+
+ AUTO_PROFILER_LABEL("nsThreadPool::Run::Wait", IDLE);
+
+ TimeDuration delta = timeout - (now - idleSince);
+ LOG(("THRD-P(%p) %s waiting [%f]\n", this, mName.BeginReading(),
+ delta.ToMilliseconds()));
+ mEventsAvailable.Wait(delta);
+ LOG(("THRD-P(%p) done waiting\n", this));
+ }
+ } else if (wasIdle) {
+ wasIdle = false;
+ --mIdleCount;
+ }
+ }
+ if (event) {
+ LOG(("THRD-P(%p) %s running [%p]\n", this, mName.BeginReading(),
+ event.get()));
+
+ // Delay event processing to encourage whoever dispatched this event
+ // to run.
+ DelayForChaosMode(ChaosFeature::TaskRunning, 1000);
+
+ // We'll handle the case of unstarted threads available
+ // when we sample.
+ current->SetRunningEventDelay(delay, TimeStamp::Now());
+
+ LogRunnable::Run log(event);
+ event->Run();
+ // To cover the event's destructor code in the LogRunnable span
+ event = nullptr;
+ }
+ } while (!exitThread);
+
+ if (listener) {
+ listener->OnThreadShuttingDown();
+ }
+
+ MOZ_ASSERT(gCurrentThreadPool.get() == this);
+ gCurrentThreadPool.set(nullptr);
+
+ if (shutdownThreadOnExit) {
+ ShutdownThread(current);
+ }
+
+ LOG(("THRD-P(%p) leave\n", this));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadPool::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) {
+ nsCOMPtr<nsIRunnable> event(aEvent);
+ return Dispatch(event.forget(), aFlags);
+}
+
+NS_IMETHODIMP
+nsThreadPool::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags) {
+ LOG(("THRD-P(%p) dispatch [%p %x]\n", this, /* XXX aEvent*/ nullptr, aFlags));
+
+ if (NS_WARN_IF(mShutdown)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (aFlags & DISPATCH_SYNC) {
+ nsCOMPtr<nsIThread> thread;
+ nsThreadManager::get().GetCurrentThread(getter_AddRefs(thread));
+ if (NS_WARN_IF(!thread)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<nsThreadSyncDispatch> wrapper =
+ new nsThreadSyncDispatch(thread.forget(), std::move(aEvent));
+ PutEvent(wrapper);
+
+ SpinEventLoopUntil(
+ [&, wrapper]() -> bool { return !wrapper->IsPending(); });
+ } else {
+ NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL || aFlags == NS_DISPATCH_AT_END,
+ "unexpected dispatch flags");
+ PutEvent(std::move(aEvent), aFlags);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadPool::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP_(bool)
+nsThreadPool::IsOnCurrentThreadInfallible() {
+ return gCurrentThreadPool.get() == this;
+}
+
+NS_IMETHODIMP
+nsThreadPool::IsOnCurrentThread(bool* aResult) {
+ MutexAutoLock lock(mMutex);
+ if (NS_WARN_IF(mShutdown)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aResult = IsOnCurrentThreadInfallible();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadPool::Shutdown() {
+ nsCOMArray<nsIThread> threads;
+ nsCOMPtr<nsIThreadPoolListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ mShutdown = true;
+ mEventsAvailable.NotifyAll();
+
+ threads.AppendObjects(mThreads);
+ mThreads.Clear();
+
+ // Swap in a null listener so that we release the listener at the end of
+ // this method. The listener will be kept alive as long as the other threads
+ // that were created when it was set.
+ mListener.swap(listener);
+ }
+
+ // 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) {
+ threads[i]->Shutdown();
+ }
+
+ return NS_OK;
+}
+
+template <typename Pred>
+static void SpinMTEventLoopUntil(Pred&& aPredicate, nsIThread* aThread,
+ TimeDuration aTimeout) {
+ MOZ_ASSERT(NS_IsMainThread(), "Must be run on the main thread");
+
+ // From a latency perspective, spinning the event loop is like leaving script
+ // and returning to the event loop. Tell the watchdog we stopped running
+ // script (until we return).
+ mozilla::Maybe<xpc::AutoScriptActivity> asa;
+ asa.emplace(false);
+
+ TimeStamp deadline = TimeStamp::Now() + aTimeout;
+ while (!aPredicate() && TimeStamp::Now() < deadline) {
+ if (!NS_ProcessNextEvent(aThread, false)) {
+ PR_Sleep(PR_MillisecondsToInterval(1));
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsThreadPool::ShutdownWithTimeout(int32_t aTimeoutMs) {
+ if (!NS_IsMainThread()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMArray<nsIThread> threads;
+ nsCOMPtr<nsIThreadPoolListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ mShutdown = true;
+ mEventsAvailable.NotifyAll();
+
+ threads.AppendObjects(mThreads);
+ mThreads.Clear();
+
+ // Swap in a null listener so that we release the listener at the end of
+ // this method. The listener will be kept alive as long as the other threads
+ // that were created when it was set.
+ mListener.swap(listener);
+ }
+
+ // IMPORTANT! Never dereference these pointers, as the objects may go away at
+ // any time. We just use the pointers values for comparison, to check if the
+ // thread has been shut down or not.
+ nsTArray<nsThreadShutdownContext*> contexts;
+
+ // 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) {
+ // Shutdown async
+ nsThreadShutdownContext* maybeContext =
+ static_cast<nsThread*>(threads[i])->ShutdownInternal(false);
+ contexts.AppendElement(maybeContext);
+ }
+
+ NotNull<nsThread*> currentThread =
+ WrapNotNull(nsThreadManager::get().GetCurrentThread());
+
+ // We spin the event loop until all of the threads in the thread pool
+ // have shut down, or the timeout expires.
+ SpinMTEventLoopUntil(
+ [&]() {
+ for (nsIThread* thread : threads) {
+ if (static_cast<nsThread*>(thread)->mThread) {
+ return false;
+ }
+ }
+ return true;
+ },
+ currentThread, TimeDuration::FromMilliseconds(aTimeoutMs));
+
+ // For any threads that have not shutdown yet, we need to remove them from
+ // mRequestedShutdownContexts so the thread manager does not wait for them
+ // at shutdown.
+ static const nsThread::ShutdownContextsComp comparator{};
+ for (int32_t i = 0; i < threads.Count(); ++i) {
+ nsThread* thread = static_cast<nsThread*>(threads[i]);
+ // If mThread is not null on the thread it means that it hasn't shutdown
+ // context[i] corresponds to thread[i]
+ if (thread->mThread && contexts[i]) {
+ auto index = currentThread->mRequestedShutdownContexts.IndexOf(
+ contexts[i], 0, comparator);
+ if (index != nsThread::ShutdownContexts::NoIndex) {
+ // We must leak the shutdown context just in case the leaked thread
+ // does get unstuck and completes before the main thread is done.
+ Unused << currentThread->mRequestedShutdownContexts[index].release();
+ currentThread->mRequestedShutdownContexts.RemoveElementAt(index);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadPool::GetThreadLimit(uint32_t* aValue) {
+ *aValue = mThreadLimit;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadPool::SetThreadLimit(uint32_t aValue) {
+ MutexAutoLock lock(mMutex);
+ LOG(("THRD-P(%p) thread limit [%u]\n", this, aValue));
+ mThreadLimit = aValue;
+ if (mIdleThreadLimit > mThreadLimit) {
+ mIdleThreadLimit = mThreadLimit;
+ }
+
+ if (static_cast<uint32_t>(mThreads.Count()) > mThreadLimit) {
+ mEventsAvailable
+ .NotifyAll(); // wake up threads so they observe this change
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadPool::GetIdleThreadLimit(uint32_t* aValue) {
+ *aValue = mIdleThreadLimit;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadPool::SetIdleThreadLimit(uint32_t aValue) {
+ MutexAutoLock lock(mMutex);
+ LOG(("THRD-P(%p) idle thread limit [%u]\n", this, aValue));
+ mIdleThreadLimit = aValue;
+ if (mIdleThreadLimit > mThreadLimit) {
+ mIdleThreadLimit = mThreadLimit;
+ }
+
+ // Do we need to kill some idle threads?
+ if (mIdleCount > mIdleThreadLimit) {
+ mEventsAvailable
+ .NotifyAll(); // wake up threads so they observe this change
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadPool::GetIdleThreadTimeout(uint32_t* aValue) {
+ *aValue = mIdleThreadTimeout;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadPool::SetIdleThreadTimeout(uint32_t aValue) {
+ MutexAutoLock lock(mMutex);
+ uint32_t oldTimeout = mIdleThreadTimeout;
+ mIdleThreadTimeout = aValue;
+
+ // Do we need to notify any idle threads that their sleep time has shortened?
+ if (mIdleThreadTimeout < oldTimeout && mIdleCount > 0) {
+ mEventsAvailable
+ .NotifyAll(); // wake up threads so they observe this change
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadPool::GetIdleThreadTimeoutRegressive(bool* aValue) {
+ *aValue = mRegressiveMaxIdleTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadPool::SetIdleThreadTimeoutRegressive(bool aValue) {
+ MutexAutoLock lock(mMutex);
+ bool oldRegressive = mRegressiveMaxIdleTime;
+ mRegressiveMaxIdleTime = aValue;
+
+ // Would setting regressive timeout effect idle threads?
+ if (mRegressiveMaxIdleTime > oldRegressive && mIdleCount > 1) {
+ mEventsAvailable
+ .NotifyAll(); // wake up threads so they observe this change
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadPool::GetThreadStackSize(uint32_t* aValue) {
+ MutexAutoLock lock(mMutex);
+ *aValue = mStackSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadPool::SetThreadStackSize(uint32_t aValue) {
+ MutexAutoLock lock(mMutex);
+ mStackSize = aValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadPool::GetListener(nsIThreadPoolListener** aListener) {
+ MutexAutoLock lock(mMutex);
+ NS_IF_ADDREF(*aListener = mListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadPool::SetListener(nsIThreadPoolListener* aListener) {
+ nsCOMPtr<nsIThreadPoolListener> swappedListener(aListener);
+ {
+ MutexAutoLock lock(mMutex);
+ mListener.swap(swappedListener);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThreadPool::SetName(const nsACString& aName) {
+ {
+ MutexAutoLock lock(mMutex);
+ if (mThreads.Count()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ mName = aName;
+ return NS_OK;
+}
diff --git a/xpcom/threads/nsThreadPool.h b/xpcom/threads/nsThreadPool.h
new file mode 100644
index 0000000000..7bdafde57c
--- /dev/null
+++ b/xpcom/threads/nsThreadPool.h
@@ -0,0 +1,63 @@
+/* -*- 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 nsThreadPool_h__
+#define nsThreadPool_h__
+
+#include "nsIThreadPool.h"
+#include "nsIRunnable.h"
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/CondVar.h"
+#include "mozilla/EventQueue.h"
+#include "mozilla/Mutex.h"
+
+class nsIThread;
+
+class nsThreadPool final : public mozilla::Runnable, public nsIThreadPool {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIEVENTTARGET_FULL
+ NS_DECL_NSITHREADPOOL
+ NS_DECL_NSIRUNNABLE
+
+ nsThreadPool();
+
+ private:
+ ~nsThreadPool();
+
+ void ShutdownThread(nsIThread* aThread);
+ nsresult PutEvent(nsIRunnable* aEvent);
+ nsresult PutEvent(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags);
+
+ nsCOMArray<nsIThread> mThreads;
+ mozilla::Mutex mMutex;
+ mozilla::CondVar mEventsAvailable;
+ mozilla::EventQueue mEvents;
+ uint32_t mThreadLimit;
+ uint32_t mIdleThreadLimit;
+ uint32_t mIdleThreadTimeout;
+ uint32_t mIdleCount;
+ uint32_t mStackSize;
+ nsCOMPtr<nsIThreadPoolListener> mListener;
+ bool mShutdown;
+ bool mRegressiveMaxIdleTime;
+ mozilla::Atomic<bool, mozilla::Relaxed> mIsAPoolThreadFree;
+ nsCString mName;
+ nsThreadPoolNaming mThreadNaming;
+};
+
+#define NS_THREADPOOL_CID \
+ { /* 547ec2a8-315e-4ec4-888e-6e4264fe90eb */ \
+ 0x547ec2a8, 0x315e, 0x4ec4, { \
+ 0x88, 0x8e, 0x6e, 0x42, 0x64, 0xfe, 0x90, 0xeb \
+ } \
+ }
+
+#endif // nsThreadPool_h__
diff --git a/xpcom/threads/nsThreadSyncDispatch.h b/xpcom/threads/nsThreadSyncDispatch.h
new file mode 100644
index 0000000000..7e0e121234
--- /dev/null
+++ b/xpcom/threads/nsThreadSyncDispatch.h
@@ -0,0 +1,59 @@
+/* -*- 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 nsThreadSyncDispatch_h_
+#define nsThreadSyncDispatch_h_
+
+#include "mozilla/Atomics.h"
+#include "mozilla/DebugOnly.h"
+
+#include "nsThreadUtils.h"
+#include "LeakRefPtr.h"
+
+class nsThreadSyncDispatch : public mozilla::Runnable {
+ public:
+ nsThreadSyncDispatch(already_AddRefed<nsIEventTarget> aOrigin,
+ already_AddRefed<nsIRunnable>&& aTask)
+ : Runnable("nsThreadSyncDispatch"),
+ mOrigin(aOrigin),
+ mSyncTask(std::move(aTask)),
+ mIsPending(true) {}
+
+ bool IsPending() {
+ // This is an atomic acquire on the origin thread.
+ return mIsPending;
+ }
+
+ private:
+ NS_IMETHOD Run() override {
+ if (nsCOMPtr<nsIRunnable> task = mSyncTask.take()) {
+ MOZ_ASSERT(!mSyncTask);
+
+ mozilla::DebugOnly<nsresult> result = task->Run();
+ MOZ_ASSERT(NS_SUCCEEDED(result), "task in sync dispatch should not fail");
+
+ // We must release the task here to ensure that when the original
+ // thread is unblocked, this task has been released.
+ task = nullptr;
+
+ // This is an atomic release on the target thread.
+ mIsPending = false;
+
+ // unblock the origin thread
+ mOrigin->Dispatch(this, NS_DISPATCH_NORMAL);
+ }
+
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIEventTarget> mOrigin;
+ // The task is leaked by default when Run() is not called, because
+ // otherwise we may release it in an incorrect thread.
+ mozilla::LeakRefPtr<nsIRunnable> mSyncTask;
+ mozilla::Atomic<bool, mozilla::ReleaseAcquire> mIsPending;
+};
+
+#endif // nsThreadSyncDispatch_h_
diff --git a/xpcom/threads/nsThreadUtils.cpp b/xpcom/threads/nsThreadUtils.cpp
new file mode 100644
index 0000000000..bbca11b6b3
--- /dev/null
+++ b/xpcom/threads/nsThreadUtils.cpp
@@ -0,0 +1,878 @@
+/* -*- 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 "nsThreadUtils.h"
+
+#include "chrome/common/ipc_message.h" // for IPC::Message
+#include "LeakRefPtr.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Likely.h"
+#include "mozilla/TimeStamp.h"
+#include "nsComponentManagerUtils.h"
+#include "nsExceptionHandler.h"
+#include "nsITimer.h"
+#include "nsTimerImpl.h"
+#include "prsystem.h"
+
+#ifdef MOZILLA_INTERNAL_API
+# include "nsThreadManager.h"
+#else
+# include "nsIThreadManager.h"
+# include "nsServiceManagerUtils.h"
+# include "nsXPCOMCIDInternal.h"
+#endif
+
+#ifdef XP_WIN
+# include <windows.h>
+#elif defined(XP_MACOSX)
+# include <sys/resource.h>
+#endif
+
+#if defined(ANDROID)
+# include <sys/prctl.h>
+#endif
+
+static mozilla::LazyLogModule sEventDispatchAndRunLog("events");
+#ifdef LOG1
+# undef LOG1
+#endif
+#define LOG1(args) \
+ MOZ_LOG(sEventDispatchAndRunLog, mozilla::LogLevel::Error, args)
+#define LOG1_ENABLED() \
+ MOZ_LOG_TEST(sEventDispatchAndRunLog, mozilla::LogLevel::Error)
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(TailDispatchingTarget, nsIEventTarget, nsISerialEventTarget)
+
+#ifndef XPCOM_GLUE_AVOID_NSPR
+
+NS_IMPL_ISUPPORTS(IdlePeriod, nsIIdlePeriod)
+
+NS_IMETHODIMP
+IdlePeriod::GetIdlePeriodHint(TimeStamp* aIdleDeadline) {
+ *aIdleDeadline = TimeStamp();
+ return NS_OK;
+}
+
+// NS_IMPL_NAMED_* relies on the mName field, which is not present on
+// release or beta. Instead, fall back to using "Runnable" for all
+// runnables.
+# ifndef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+NS_IMPL_ISUPPORTS(Runnable, nsIRunnable)
+# else
+NS_IMPL_NAMED_ADDREF(Runnable, mName)
+NS_IMPL_NAMED_RELEASE(Runnable, mName)
+NS_IMPL_QUERY_INTERFACE(Runnable, nsIRunnable, nsINamed)
+# endif
+
+NS_IMETHODIMP
+Runnable::Run() {
+ // Do nothing
+ return NS_OK;
+}
+
+# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+NS_IMETHODIMP
+Runnable::GetName(nsACString& aName) {
+ if (mName) {
+ aName.AssignASCII(mName);
+ } else {
+ aName.Truncate();
+ }
+ return NS_OK;
+}
+# endif
+
+NS_IMPL_ISUPPORTS_INHERITED(DiscardableRunnable, Runnable,
+ nsIDiscardableRunnable)
+
+NS_IMPL_ISUPPORTS_INHERITED(CancelableRunnable, DiscardableRunnable,
+ nsICancelableRunnable)
+
+void CancelableRunnable::OnDiscard() {
+ // Tasks that implement Cancel() can be safely cleaned up if it turns out
+ // that the task will not run.
+ (void)NS_WARN_IF(NS_FAILED(Cancel()));
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(IdleRunnable, DiscardableRunnable, nsIIdleRunnable)
+
+NS_IMPL_ISUPPORTS_INHERITED(CancelableIdleRunnable, CancelableRunnable,
+ nsIIdleRunnable)
+
+NS_IMPL_ISUPPORTS_INHERITED(PrioritizableRunnable, Runnable,
+ nsIRunnablePriority)
+
+PrioritizableRunnable::PrioritizableRunnable(
+ already_AddRefed<nsIRunnable>&& aRunnable, uint32_t aPriority)
+ // Real runnable name is managed by overridding the GetName function.
+ : Runnable("PrioritizableRunnable"),
+ mRunnable(std::move(aRunnable)),
+ mPriority(aPriority) {
+# if DEBUG
+ nsCOMPtr<nsIRunnablePriority> runnablePrio = do_QueryInterface(mRunnable);
+ MOZ_ASSERT(!runnablePrio);
+# endif
+}
+
+# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+NS_IMETHODIMP
+PrioritizableRunnable::GetName(nsACString& aName) {
+ // Try to get a name from the underlying runnable.
+ nsCOMPtr<nsINamed> named = do_QueryInterface(mRunnable);
+ if (named) {
+ named->GetName(aName);
+ }
+ return NS_OK;
+}
+# endif
+
+NS_IMETHODIMP
+PrioritizableRunnable::Run() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ return mRunnable->Run();
+}
+
+NS_IMETHODIMP
+PrioritizableRunnable::GetPriority(uint32_t* aPriority) {
+ *aPriority = mPriority;
+ return NS_OK;
+}
+
+already_AddRefed<nsIRunnable> mozilla::CreateMediumHighRunnable(
+ already_AddRefed<nsIRunnable>&& aRunnable) {
+ nsCOMPtr<nsIRunnable> runnable = new PrioritizableRunnable(
+ std::move(aRunnable), nsIRunnablePriority::PRIORITY_MEDIUMHIGH);
+ return runnable.forget();
+}
+
+#endif // XPCOM_GLUE_AVOID_NSPR
+
+//-----------------------------------------------------------------------------
+
+nsresult NS_NewNamedThread(const nsACString& aName, nsIThread** aResult,
+ nsIRunnable* aInitialEvent, uint32_t aStackSize) {
+ nsCOMPtr<nsIRunnable> event = aInitialEvent;
+ return NS_NewNamedThread(aName, aResult, event.forget(), aStackSize);
+}
+
+nsresult NS_NewNamedThread(const nsACString& aName, nsIThread** aResult,
+ already_AddRefed<nsIRunnable> aInitialEvent,
+ uint32_t aStackSize) {
+ nsCOMPtr<nsIRunnable> event = std::move(aInitialEvent);
+ nsCOMPtr<nsIThread> thread;
+#ifdef MOZILLA_INTERNAL_API
+ nsresult rv = nsThreadManager::get().nsThreadManager::NewNamedThread(
+ aName, aStackSize, getter_AddRefs(thread));
+#else
+ nsresult rv;
+ nsCOMPtr<nsIThreadManager> mgr =
+ do_GetService(NS_THREADMANAGER_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mgr->NewNamedThread(aName, aStackSize, getter_AddRefs(thread));
+#endif
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (event) {
+ rv = thread->Dispatch(event.forget(), NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ *aResult = nullptr;
+ thread.swap(*aResult);
+ return NS_OK;
+}
+
+nsresult NS_GetCurrentThread(nsIThread** aResult) {
+#ifdef MOZILLA_INTERNAL_API
+ return nsThreadManager::get().nsThreadManager::GetCurrentThread(aResult);
+#else
+ nsresult rv;
+ nsCOMPtr<nsIThreadManager> mgr =
+ do_GetService(NS_THREADMANAGER_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return mgr->GetCurrentThread(aResult);
+#endif
+}
+
+nsresult NS_GetMainThread(nsIThread** aResult) {
+#ifdef MOZILLA_INTERNAL_API
+ return nsThreadManager::get().nsThreadManager::GetMainThread(aResult);
+#else
+ nsresult rv;
+ nsCOMPtr<nsIThreadManager> mgr =
+ do_GetService(NS_THREADMANAGER_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return mgr->GetMainThread(aResult);
+#endif
+}
+
+nsresult NS_DispatchToCurrentThread(already_AddRefed<nsIRunnable>&& aEvent) {
+ nsresult rv;
+ nsCOMPtr<nsIRunnable> event(aEvent);
+#ifdef MOZILLA_INTERNAL_API
+ nsIEventTarget* thread = GetCurrentEventTarget();
+ if (!thread) {
+ return NS_ERROR_UNEXPECTED;
+ }
+#else
+ nsCOMPtr<nsIThread> thread;
+ rv = NS_GetCurrentThread(getter_AddRefs(thread));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+#endif
+ // To keep us from leaking the runnable if dispatch method fails,
+ // we grab the reference on failures and release it.
+ nsIRunnable* temp = event.get();
+ rv = thread->Dispatch(event.forget(), NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // Dispatch() leaked the reference to the event, but due to caller's
+ // assumptions, we shouldn't leak here. And given we are on the same
+ // thread as the dispatch target, it's mostly safe to do it here.
+ NS_RELEASE(temp);
+ }
+ return rv;
+}
+
+// It is common to call NS_DispatchToCurrentThread with a newly
+// allocated runnable with a refcount of zero. To keep us from leaking
+// the runnable if the dispatch method fails, we take a death grip.
+nsresult NS_DispatchToCurrentThread(nsIRunnable* aEvent) {
+ nsCOMPtr<nsIRunnable> event(aEvent);
+ return NS_DispatchToCurrentThread(event.forget());
+}
+
+nsresult NS_DispatchToMainThread(already_AddRefed<nsIRunnable>&& aEvent,
+ uint32_t aDispatchFlags) {
+ LeakRefPtr<nsIRunnable> event(std::move(aEvent));
+ nsCOMPtr<nsIThread> thread;
+ nsresult rv = NS_GetMainThread(getter_AddRefs(thread));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ NS_ASSERTION(false,
+ "Failed NS_DispatchToMainThread() in shutdown; leaking");
+ // NOTE: if you stop leaking here, adjust Promise::MaybeReportRejected(),
+ // which assumes a leak here, or split into leaks and no-leaks versions
+ return rv;
+ }
+ return thread->Dispatch(event.take(), aDispatchFlags);
+}
+
+// In the case of failure with a newly allocated runnable with a
+// refcount of zero, we intentionally leak the runnable, because it is
+// likely that the runnable is being dispatched to the main thread
+// because it owns main thread only objects, so it is not safe to
+// release them here.
+nsresult NS_DispatchToMainThread(nsIRunnable* aEvent, uint32_t aDispatchFlags) {
+ nsCOMPtr<nsIRunnable> event(aEvent);
+ return NS_DispatchToMainThread(event.forget(), aDispatchFlags);
+}
+
+nsresult NS_DelayedDispatchToCurrentThread(
+ already_AddRefed<nsIRunnable>&& aEvent, uint32_t aDelayMs) {
+ nsCOMPtr<nsIRunnable> event(aEvent);
+#ifdef MOZILLA_INTERNAL_API
+ nsIEventTarget* thread = GetCurrentEventTarget();
+ if (!thread) {
+ return NS_ERROR_UNEXPECTED;
+ }
+#else
+ nsresult rv;
+ nsCOMPtr<nsIThread> thread;
+ rv = NS_GetCurrentThread(getter_AddRefs(thread));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+#endif
+
+ return thread->DelayedDispatch(event.forget(), aDelayMs);
+}
+
+nsresult NS_DispatchToThreadQueue(already_AddRefed<nsIRunnable>&& aEvent,
+ nsIThread* aThread,
+ EventQueuePriority aQueue) {
+ nsresult rv;
+ nsCOMPtr<nsIRunnable> event(aEvent);
+ NS_ENSURE_TRUE(event, NS_ERROR_INVALID_ARG);
+ if (!aThread) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ // To keep us from leaking the runnable if dispatch method fails,
+ // we grab the reference on failures and release it.
+ nsIRunnable* temp = event.get();
+ rv = aThread->DispatchToQueue(event.forget(), aQueue);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // Dispatch() leaked the reference to the event, but due to caller's
+ // assumptions, we shouldn't leak here. And given we are on the same
+ // thread as the dispatch target, it's mostly safe to do it here.
+ NS_RELEASE(temp);
+ }
+
+ return rv;
+}
+
+nsresult NS_DispatchToCurrentThreadQueue(already_AddRefed<nsIRunnable>&& aEvent,
+ EventQueuePriority aQueue) {
+ return NS_DispatchToThreadQueue(std::move(aEvent), NS_GetCurrentThread(),
+ aQueue);
+}
+
+extern nsresult NS_DispatchToMainThreadQueue(
+ already_AddRefed<nsIRunnable>&& aEvent, EventQueuePriority aQueue) {
+ nsCOMPtr<nsIThread> mainThread;
+ nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread));
+ if (NS_SUCCEEDED(rv)) {
+ return NS_DispatchToThreadQueue(std::move(aEvent), mainThread, aQueue);
+ }
+ return rv;
+}
+
+class IdleRunnableWrapper final : public Runnable,
+ public nsIDiscardableRunnable,
+ public nsIIdleRunnable {
+ public:
+ explicit IdleRunnableWrapper(already_AddRefed<nsIRunnable>&& aEvent)
+ : Runnable("IdleRunnableWrapper"),
+ mRunnable(std::move(aEvent)),
+ mDiscardable(do_QueryInterface(mRunnable)) {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_IMETHOD Run() override {
+ if (!mRunnable) {
+ return NS_OK;
+ }
+ CancelTimer();
+ // Don't clear mDiscardable because that would cause QueryInterface to
+ // change behavior during the lifetime of an instance.
+ nsCOMPtr<nsIRunnable> runnable = std::move(mRunnable);
+ return runnable->Run();
+ }
+
+ // nsIDiscardableRunnable
+ void OnDiscard() override {
+ if (!mRunnable) {
+ // Run() was already called from TimedOut().
+ return;
+ }
+ mDiscardable->OnDiscard();
+ mRunnable = nullptr;
+ }
+
+ static void TimedOut(nsITimer* aTimer, void* aClosure) {
+ RefPtr<IdleRunnableWrapper> runnable =
+ static_cast<IdleRunnableWrapper*>(aClosure);
+ LogRunnable::Run log(runnable);
+ runnable->Run();
+ runnable = nullptr;
+ }
+
+ void SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) override {
+ MOZ_ASSERT(aTarget);
+ MOZ_ASSERT(!mTimer);
+ NS_NewTimerWithFuncCallback(getter_AddRefs(mTimer), TimedOut, this, aDelay,
+ nsITimer::TYPE_ONE_SHOT,
+ "IdleRunnableWrapper::SetTimer", aTarget);
+ }
+
+#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+ NS_IMETHOD GetName(nsACString& aName) override {
+ aName.AssignLiteral("IdleRunnableWrapper");
+ if (nsCOMPtr<nsINamed> named = do_QueryInterface(mRunnable)) {
+ nsAutoCString name;
+ named->GetName(name);
+ if (!name.IsEmpty()) {
+ aName.AppendLiteral(" for ");
+ aName.Append(name);
+ }
+ }
+ return NS_OK;
+ }
+#endif
+
+ private:
+ ~IdleRunnableWrapper() { CancelTimer(); }
+
+ void CancelTimer() {
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+ }
+
+ nsCOMPtr<nsITimer> mTimer;
+ nsCOMPtr<nsIRunnable> mRunnable;
+ nsCOMPtr<nsIDiscardableRunnable> mDiscardable;
+};
+
+NS_IMPL_ADDREF_INHERITED(IdleRunnableWrapper, Runnable)
+NS_IMPL_RELEASE_INHERITED(IdleRunnableWrapper, Runnable)
+
+NS_INTERFACE_MAP_BEGIN(IdleRunnableWrapper)
+ NS_INTERFACE_MAP_ENTRY(nsIIdleRunnable)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIDiscardableRunnable, mDiscardable)
+NS_INTERFACE_MAP_END_INHERITING(Runnable)
+
+extern nsresult NS_DispatchToThreadQueue(already_AddRefed<nsIRunnable>&& aEvent,
+ uint32_t aTimeout, nsIThread* aThread,
+ EventQueuePriority aQueue) {
+ nsCOMPtr<nsIRunnable> event(std::move(aEvent));
+ NS_ENSURE_TRUE(event, NS_ERROR_INVALID_ARG);
+ MOZ_ASSERT(aQueue == EventQueuePriority::Idle ||
+ aQueue == EventQueuePriority::DeferredTimers);
+
+ // XXX Using current thread for now as the nsIEventTarget.
+ nsIEventTarget* target = mozilla::GetCurrentEventTarget();
+ if (!target) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIIdleRunnable> idleEvent = do_QueryInterface(event);
+
+ if (!idleEvent) {
+ idleEvent = new IdleRunnableWrapper(event.forget());
+ event = do_QueryInterface(idleEvent);
+ MOZ_DIAGNOSTIC_ASSERT(event);
+ }
+ idleEvent->SetTimer(aTimeout, target);
+
+ nsresult rv = NS_DispatchToThreadQueue(event.forget(), aThread, aQueue);
+ if (NS_SUCCEEDED(rv)) {
+ // This is intended to bind with the "DISP" log made from inside
+ // NS_DispatchToThreadQueue for the `event`. There is no possibly to inject
+ // another "DISP" for a different event on this thread.
+ LOG1(("TIMEOUT %u", aTimeout));
+ }
+
+ return rv;
+}
+
+extern nsresult NS_DispatchToCurrentThreadQueue(
+ already_AddRefed<nsIRunnable>&& aEvent, uint32_t aTimeout,
+ EventQueuePriority aQueue) {
+ return NS_DispatchToThreadQueue(std::move(aEvent), aTimeout,
+ NS_GetCurrentThread(), aQueue);
+}
+
+#ifndef XPCOM_GLUE_AVOID_NSPR
+nsresult NS_ProcessPendingEvents(nsIThread* aThread, PRIntervalTime aTimeout) {
+ nsresult rv = NS_OK;
+
+# ifdef MOZILLA_INTERNAL_API
+ if (!aThread) {
+ aThread = NS_GetCurrentThread();
+ if (NS_WARN_IF(!aThread)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+# else
+ nsCOMPtr<nsIThread> current;
+ if (!aThread) {
+ rv = NS_GetCurrentThread(getter_AddRefs(current));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ aThread = current.get();
+ }
+# endif
+
+ PRIntervalTime start = PR_IntervalNow();
+ for (;;) {
+ bool processedEvent;
+ rv = aThread->ProcessNextEvent(false, &processedEvent);
+ if (NS_FAILED(rv) || !processedEvent) {
+ break;
+ }
+ if (PR_IntervalNow() - start > aTimeout) {
+ break;
+ }
+ }
+ return rv;
+}
+#endif // XPCOM_GLUE_AVOID_NSPR
+
+inline bool hasPendingEvents(nsIThread* aThread) {
+ bool val;
+ return NS_SUCCEEDED(aThread->HasPendingEvents(&val)) && val;
+}
+
+bool NS_HasPendingEvents(nsIThread* aThread) {
+ if (!aThread) {
+#ifndef MOZILLA_INTERNAL_API
+ nsCOMPtr<nsIThread> current;
+ NS_GetCurrentThread(getter_AddRefs(current));
+ return hasPendingEvents(current);
+#else
+ aThread = NS_GetCurrentThread();
+ if (NS_WARN_IF(!aThread)) {
+ return false;
+ }
+#endif
+ }
+ return hasPendingEvents(aThread);
+}
+
+bool NS_ProcessNextEvent(nsIThread* aThread, bool aMayWait) {
+#ifdef MOZILLA_INTERNAL_API
+ if (!aThread) {
+ aThread = NS_GetCurrentThread();
+ if (NS_WARN_IF(!aThread)) {
+ return false;
+ }
+ }
+#else
+ nsCOMPtr<nsIThread> current;
+ if (!aThread) {
+ NS_GetCurrentThread(getter_AddRefs(current));
+ if (NS_WARN_IF(!current)) {
+ return false;
+ }
+ aThread = current.get();
+ }
+#endif
+ bool val;
+ return NS_SUCCEEDED(aThread->ProcessNextEvent(aMayWait, &val)) && val;
+}
+
+void NS_SetCurrentThreadName(const char* aName) {
+#if defined(ANDROID)
+ // Workaround for Bug 1541216 - PR_SetCurrentThreadName() Fails to set the
+ // thread name on Android.
+ prctl(PR_SET_NAME, reinterpret_cast<unsigned long>(aName));
+#else
+ PR_SetCurrentThreadName(aName);
+#endif
+ CrashReporter::SetCurrentThreadName(aName);
+}
+
+#ifdef MOZILLA_INTERNAL_API
+nsIThread* NS_GetCurrentThread() {
+ return nsThreadManager::get().GetCurrentThread();
+}
+
+nsIThread* NS_GetCurrentThreadNoCreate() {
+ if (nsThreadManager::get().IsNSThread()) {
+ return NS_GetCurrentThread();
+ }
+ return nullptr;
+}
+#endif
+
+// nsThreadPoolNaming
+nsCString nsThreadPoolNaming::GetNextThreadName(const nsACString& aPoolName) {
+ nsCString name(aPoolName);
+ name.AppendLiteral(" #");
+ name.AppendInt(++mCounter, 10); // The counter is declared as atomic
+ return name;
+}
+
+nsresult NS_DispatchBackgroundTask(already_AddRefed<nsIRunnable> aEvent,
+ uint32_t aDispatchFlags) {
+ nsCOMPtr<nsIRunnable> event(aEvent);
+ return nsThreadManager::get().DispatchToBackgroundThread(event,
+ aDispatchFlags);
+}
+
+// nsAutoLowPriorityIO
+nsAutoLowPriorityIO::nsAutoLowPriorityIO() {
+#if defined(XP_WIN)
+ lowIOPrioritySet =
+ SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_BEGIN);
+#elif defined(XP_MACOSX)
+ oldPriority = getiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD);
+ lowIOPrioritySet =
+ oldPriority != -1 &&
+ setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD, IOPOL_THROTTLE) != -1;
+#else
+ lowIOPrioritySet = false;
+#endif
+}
+
+nsAutoLowPriorityIO::~nsAutoLowPriorityIO() {
+#if defined(XP_WIN)
+ if (MOZ_LIKELY(lowIOPrioritySet)) {
+ // On Windows the old thread priority is automatically restored
+ SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_END);
+ }
+#elif defined(XP_MACOSX)
+ if (MOZ_LIKELY(lowIOPrioritySet)) {
+ setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD, oldPriority);
+ }
+#endif
+}
+
+namespace mozilla {
+
+nsIEventTarget* GetCurrentEventTarget() {
+ nsCOMPtr<nsIThread> thread;
+ nsresult rv = NS_GetCurrentThread(getter_AddRefs(thread));
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ return thread->EventTarget();
+}
+
+nsIEventTarget* GetMainThreadEventTarget() {
+ return GetMainThreadSerialEventTarget();
+}
+
+nsISerialEventTarget* GetCurrentSerialEventTarget() {
+ if (nsISerialEventTarget* current =
+ SerialEventTargetGuard::GetCurrentSerialEventTarget()) {
+ return current;
+ }
+
+ nsCOMPtr<nsIThread> thread;
+ nsresult rv = NS_GetCurrentThread(getter_AddRefs(thread));
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ return thread->SerialEventTarget();
+}
+
+nsISerialEventTarget* GetMainThreadSerialEventTarget() {
+ return static_cast<nsThread*>(nsThreadManager::get().GetMainThreadWeak());
+}
+
+size_t GetNumberOfProcessors() {
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+ static const PRInt32 procs = PR_GetNumberOfProcessors();
+#else
+ PRInt32 procs = PR_GetNumberOfProcessors();
+#endif
+ MOZ_ASSERT(procs > 0);
+ return static_cast<size_t>(procs);
+}
+
+template <typename T>
+void LogTaskBase<T>::LogDispatch(T* aEvent) {
+ LOG1(("DISP %p", aEvent));
+}
+template <typename T>
+void LogTaskBase<T>::LogDispatch(T* aEvent, void* aContext) {
+ LOG1(("DISP %p (%p)", aEvent, aContext));
+}
+
+template <>
+void LogTaskBase<IPC::Message>::LogDispatchWithPid(IPC::Message* aEvent,
+ int32_t aPid) {
+ if (aEvent->seqno() && aPid > 0) {
+ LOG1(("SEND %p %d %d", aEvent, aEvent->seqno(), aPid));
+ }
+}
+
+template <typename T>
+LogTaskBase<T>::Run::Run(T* aEvent, bool aWillRunAgain)
+ : mWillRunAgain(aWillRunAgain) {
+ // Logging address of this RAII so that we can use it to identify the DONE log
+ // while not keeping any ref to the event that could be invalid at the dtor
+ // time.
+ LOG1(("EXEC %p %p", aEvent, this));
+}
+template <typename T>
+LogTaskBase<T>::Run::Run(T* aEvent, void* aContext, bool aWillRunAgain)
+ : mWillRunAgain(aWillRunAgain) {
+ LOG1(("EXEC %p (%p) %p", aEvent, aContext, this));
+}
+
+template <>
+LogTaskBase<nsIRunnable>::Run::Run(nsIRunnable* aEvent, bool aWillRunAgain)
+ : mWillRunAgain(aWillRunAgain) {
+ if (!LOG1_ENABLED()) {
+ return;
+ }
+
+ nsCOMPtr<nsINamed> named(do_QueryInterface(aEvent));
+ if (!named) {
+ LOG1(("EXEC %p %p", aEvent, this));
+ return;
+ }
+
+ nsAutoCString name;
+ named->GetName(name);
+ LOG1(("EXEC %p %p [%s]", aEvent, this, name.BeginReading()));
+}
+
+template <>
+LogTaskBase<Task>::Run::Run(Task* aTask, bool aWillRunAgain)
+ : mWillRunAgain(aWillRunAgain) {
+ if (!LOG1_ENABLED()) {
+ return;
+ }
+
+ nsAutoCString name;
+ if (!aTask->GetName(name)) {
+ LOG1(("EXEC %p %p", aTask, this));
+ return;
+ }
+
+ LOG1(("EXEC %p %p [%s]", aTask, this, name.BeginReading()));
+}
+
+template <>
+LogTaskBase<IPC::Message>::Run::Run(IPC::Message* aMessage, bool aWillRunAgain)
+ : mWillRunAgain(aWillRunAgain) {
+ LOG1(("RECV %p %p %d [%s]", aMessage, this, aMessage->seqno(),
+ aMessage->name()));
+}
+
+template <>
+LogTaskBase<nsTimerImpl>::Run::Run(nsTimerImpl* aEvent, bool aWillRunAgain)
+ : mWillRunAgain(aWillRunAgain) {
+ // The name of the timer will be logged when running it on the target thread.
+ // Logging it here (on the `Timer` thread) would be redundant.
+ LOG1(("EXEC %p %p [nsTimerImpl]", aEvent, this));
+}
+
+template <typename T>
+LogTaskBase<T>::Run::~Run() {
+ LOG1((mWillRunAgain ? "INTERRUPTED %p" : "DONE %p", this));
+}
+
+template class LogTaskBase<nsIRunnable>;
+template class LogTaskBase<MicroTaskRunnable>;
+template class LogTaskBase<IPC::Message>;
+template class LogTaskBase<nsTimerImpl>;
+template class LogTaskBase<Task>;
+template class LogTaskBase<PresShell>;
+template class LogTaskBase<dom::FrameRequestCallback>;
+
+MOZ_THREAD_LOCAL(nsISerialEventTarget*)
+SerialEventTargetGuard::sCurrentThreadTLS;
+void SerialEventTargetGuard::InitTLS() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!sCurrentThreadTLS.init()) {
+ MOZ_CRASH();
+ }
+}
+
+DelayedRunnable::DelayedRunnable(already_AddRefed<nsIEventTarget> aTarget,
+ already_AddRefed<nsIRunnable> aRunnable,
+ uint32_t aDelay)
+ : mozilla::Runnable("DelayedRunnable"),
+ mTarget(aTarget),
+ mWrappedRunnable(aRunnable),
+ mDelayedFrom(TimeStamp::NowLoRes()),
+ mDelay(aDelay) {}
+
+nsresult DelayedRunnable::Init() {
+ return NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, mDelay,
+ nsITimer::TYPE_ONE_SHOT, mTarget);
+}
+
+NS_IMETHODIMP DelayedRunnable::Run() {
+ MOZ_ASSERT(mTimer, "DelayedRunnable without Init?");
+
+ // Already ran?
+ if (!mWrappedRunnable) {
+ return NS_OK;
+ }
+
+ // Are we too early?
+ if ((mozilla::TimeStamp::NowLoRes() - mDelayedFrom).ToMilliseconds() <
+ mDelay) {
+ return NS_OK; // Let the nsITimer run us.
+ }
+
+ mTimer->Cancel();
+ return DoRun();
+}
+
+NS_IMETHODIMP DelayedRunnable::Notify(nsITimer* aTimer) {
+ // If we already ran, the timer should have been canceled.
+ MOZ_ASSERT(mWrappedRunnable);
+ MOZ_ASSERT(aTimer == mTimer);
+
+ return DoRun();
+}
+
+nsresult DelayedRunnable::DoRun() {
+ nsCOMPtr<nsIRunnable> r = std::move(mWrappedRunnable);
+ return r->Run();
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(DelayedRunnable, Runnable, nsITimerCallback)
+
+} // namespace mozilla
+
+bool nsIEventTarget::IsOnCurrentThread() {
+ if (mThread) {
+ return mThread == PR_GetCurrentThread();
+ }
+ return IsOnCurrentThreadInfallible();
+}
+
+extern "C" {
+// These functions use the C language linkage because they're exposed to Rust
+// via the xpcom/rust/moz_task crate, which wraps them in safe Rust functions
+// that enable Rust code to get/create threads and dispatch runnables on them.
+
+nsresult NS_GetCurrentThreadEventTarget(nsIEventTarget** aResult) {
+ nsCOMPtr<nsIEventTarget> target = mozilla::GetCurrentEventTarget();
+ if (!target) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ target.forget(aResult);
+ return NS_OK;
+}
+
+nsresult NS_GetMainThreadEventTarget(nsIEventTarget** aResult) {
+ nsCOMPtr<nsIEventTarget> target = mozilla::GetMainThreadEventTarget();
+ if (!target) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ target.forget(aResult);
+ return NS_OK;
+}
+
+// NS_NewNamedThread's aStackSize parameter has the default argument
+// nsIThreadManager::DEFAULT_STACK_SIZE, but we can't omit default arguments
+// when calling a C++ function from Rust, and we can't access
+// nsIThreadManager::DEFAULT_STACK_SIZE in Rust to pass it explicitly,
+// since it is defined in a %{C++ ... %} block within nsIThreadManager.idl.
+// So we indirect through this function.
+nsresult NS_NewNamedThreadWithDefaultStackSize(const nsACString& aName,
+ nsIThread** aResult,
+ nsIRunnable* aEvent) {
+ return NS_NewNamedThread(aName, aResult, aEvent);
+}
+
+bool NS_IsCurrentThread(nsIEventTarget* aThread) {
+ return aThread->IsOnCurrentThread();
+}
+
+nsresult NS_DispatchBackgroundTask(nsIRunnable* aEvent,
+ uint32_t aDispatchFlags) {
+ return nsThreadManager::get().DispatchToBackgroundThread(aEvent,
+ aDispatchFlags);
+}
+
+nsresult NS_CreateBackgroundTaskQueue(const char* aName,
+ nsISerialEventTarget** aTarget) {
+ nsCOMPtr<nsISerialEventTarget> target =
+ nsThreadManager::get().CreateBackgroundTaskQueue(aName);
+ if (!target) {
+ return NS_ERROR_FAILURE;
+ }
+
+ target.forget(aTarget);
+ return NS_OK;
+}
+
+} // extern "C"
diff --git a/xpcom/threads/nsThreadUtils.h b/xpcom/threads/nsThreadUtils.h
new file mode 100644
index 0000000000..a8d64de83e
--- /dev/null
+++ b/xpcom/threads/nsThreadUtils.h
@@ -0,0 +1,1968 @@
+/* -*- 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 nsThreadUtils_h__
+#define nsThreadUtils_h__
+
+#include <type_traits>
+#include <utility>
+
+#include "MainThreadUtils.h"
+#include "mozilla/EventQueue.h"
+#include "mozilla/AbstractThread.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Likely.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/ThreadLocal.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Tuple.h"
+#include "nsCOMPtr.h"
+#include "nsICancelableRunnable.h"
+#include "nsIDiscardableRunnable.h"
+#include "nsIIdlePeriod.h"
+#include "nsIIdleRunnable.h"
+#include "nsINamed.h"
+#include "nsIRunnable.h"
+#include "nsIThreadManager.h"
+#include "nsITimer.h"
+#include "nsString.h"
+#include "prinrval.h"
+#include "prthread.h"
+
+class MessageLoop;
+class nsIThread;
+
+//-----------------------------------------------------------------------------
+// These methods are alternatives to the methods on nsIThreadManager, provided
+// for convenience.
+
+/**
+ * Create a new thread, and optionally provide an initial event for the thread.
+ *
+ * @param aName
+ * The name of the thread.
+ * @param aResult
+ * The resulting nsIThread object.
+ * @param aInitialEvent
+ * The initial event to run on this thread. This parameter may be null.
+ * @param aStackSize
+ * The size in bytes to reserve for the thread's stack.
+ *
+ * @returns NS_ERROR_INVALID_ARG
+ * Indicates that the given name is not unique.
+ */
+
+extern nsresult NS_NewNamedThread(
+ const nsACString& aName, nsIThread** aResult,
+ nsIRunnable* aInitialEvent = nullptr,
+ uint32_t aStackSize = nsIThreadManager::DEFAULT_STACK_SIZE);
+
+extern nsresult NS_NewNamedThread(
+ const nsACString& aName, nsIThread** aResult,
+ already_AddRefed<nsIRunnable> aInitialEvent,
+ uint32_t aStackSize = nsIThreadManager::DEFAULT_STACK_SIZE);
+
+template <size_t LEN>
+inline nsresult NS_NewNamedThread(
+ const char (&aName)[LEN], nsIThread** aResult,
+ already_AddRefed<nsIRunnable> aInitialEvent,
+ uint32_t aStackSize = nsIThreadManager::DEFAULT_STACK_SIZE) {
+ static_assert(LEN <= 16, "Thread name must be no more than 16 characters");
+ return NS_NewNamedThread(nsDependentCString(aName, LEN - 1), aResult,
+ std::move(aInitialEvent), aStackSize);
+}
+
+template <size_t LEN>
+inline nsresult NS_NewNamedThread(
+ const char (&aName)[LEN], nsIThread** aResult,
+ nsIRunnable* aInitialEvent = nullptr,
+ uint32_t aStackSize = nsIThreadManager::DEFAULT_STACK_SIZE) {
+ nsCOMPtr<nsIRunnable> event = aInitialEvent;
+ static_assert(LEN <= 16, "Thread name must be no more than 16 characters");
+ return NS_NewNamedThread(nsDependentCString(aName, LEN - 1), aResult,
+ event.forget(), aStackSize);
+}
+
+/**
+ * Get a reference to the current thread, creating it if it does not exist yet.
+ *
+ * @param aResult
+ * The resulting nsIThread object.
+ */
+extern nsresult NS_GetCurrentThread(nsIThread** aResult);
+
+/**
+ * Dispatch the given event to the current thread.
+ *
+ * @param aEvent
+ * The event to dispatch.
+ *
+ * @returns NS_ERROR_INVALID_ARG
+ * If event is null.
+ */
+extern nsresult NS_DispatchToCurrentThread(nsIRunnable* aEvent);
+extern nsresult NS_DispatchToCurrentThread(
+ already_AddRefed<nsIRunnable>&& aEvent);
+
+/**
+ * Dispatch the given event to the main thread.
+ *
+ * @param aEvent
+ * The event to dispatch.
+ * @param aDispatchFlags
+ * The flags to pass to the main thread's dispatch method.
+ *
+ * @returns NS_ERROR_INVALID_ARG
+ * If event is null.
+ */
+extern nsresult NS_DispatchToMainThread(
+ nsIRunnable* aEvent, uint32_t aDispatchFlags = NS_DISPATCH_NORMAL);
+extern nsresult NS_DispatchToMainThread(
+ already_AddRefed<nsIRunnable>&& aEvent,
+ uint32_t aDispatchFlags = NS_DISPATCH_NORMAL);
+
+extern nsresult NS_DelayedDispatchToCurrentThread(
+ already_AddRefed<nsIRunnable>&& aEvent, uint32_t aDelayMs);
+
+/**
+ * Dispatch the given event to the specified queue of the current thread.
+ *
+ * @param aEvent The event to dispatch.
+ * @param aQueue The event queue for the thread to use
+ *
+ * @returns NS_ERROR_INVALID_ARG
+ * If event is null.
+ * @returns NS_ERROR_UNEXPECTED
+ * If the thread is shutting down.
+ */
+extern nsresult NS_DispatchToCurrentThreadQueue(
+ already_AddRefed<nsIRunnable>&& aEvent, mozilla::EventQueuePriority aQueue);
+
+/**
+ * Dispatch the given event to the specified queue of the main thread.
+ *
+ * @param aEvent The event to dispatch.
+ * @param aQueue The event queue for the thread to use
+ *
+ * @returns NS_ERROR_INVALID_ARG
+ * If event is null.
+ * @returns NS_ERROR_UNEXPECTED
+ * If the thread is shutting down.
+ */
+extern nsresult NS_DispatchToMainThreadQueue(
+ already_AddRefed<nsIRunnable>&& aEvent, mozilla::EventQueuePriority aQueue);
+
+/**
+ * Dispatch the given event to an idle queue of the current thread.
+ *
+ * @param aEvent The event to dispatch. If the event implements
+ * nsIIdleRunnable, it will receive a call on
+ * nsIIdleRunnable::SetTimer when dispatched, with the value of
+ * aTimeout.
+ *
+ * @param aTimeout The time in milliseconds until the event should be
+ * moved from an idle queue to the regular queue, if it hasn't been
+ * executed. If aEvent is also an nsIIdleRunnable, it is expected
+ * that it should handle the timeout itself, after a call to
+ * nsIIdleRunnable::SetTimer.
+ *
+ * @param aQueue
+ * The event queue for the thread to use. Must be an idle queue
+ * (Idle or DeferredTimers)
+ *
+ * @returns NS_ERROR_INVALID_ARG
+ * If event is null.
+ * @returns NS_ERROR_UNEXPECTED
+ * If the thread is shutting down.
+ */
+extern nsresult NS_DispatchToCurrentThreadQueue(
+ already_AddRefed<nsIRunnable>&& aEvent, uint32_t aTimeout,
+ mozilla::EventQueuePriority aQueue);
+
+/**
+ * Dispatch the given event to a queue of a thread.
+ *
+ * @param aEvent The event to dispatch.
+ * @param aThread The target thread for the dispatch.
+ * @param aQueue The event queue for the thread to use.
+ *
+ * @returns NS_ERROR_INVALID_ARG
+ * If event is null.
+ * @returns NS_ERROR_UNEXPECTED
+ * If the thread is shutting down.
+ */
+extern nsresult NS_DispatchToThreadQueue(already_AddRefed<nsIRunnable>&& aEvent,
+ nsIThread* aThread,
+ mozilla::EventQueuePriority aQueue);
+
+/**
+ * Dispatch the given event to an idle queue of a thread.
+ *
+ * @param aEvent The event to dispatch. If the event implements
+ * nsIIdleRunnable, it will receive a call on
+ * nsIIdleRunnable::SetTimer when dispatched, with the value of
+ * aTimeout.
+ *
+ * @param aTimeout The time in milliseconds until the event should be
+ * moved from an idle queue to the regular queue, if it hasn't been
+ * executed. If aEvent is also an nsIIdleRunnable, it is expected
+ * that it should handle the timeout itself, after a call to
+ * nsIIdleRunnable::SetTimer.
+ *
+ * @param aThread The target thread for the dispatch.
+ *
+ * @param aQueue
+ * The event queue for the thread to use. Must be an idle queue
+ * (Idle or DeferredTimers)
+ *
+ * @returns NS_ERROR_INVALID_ARG
+ * If event is null.
+ * @returns NS_ERROR_UNEXPECTED
+ * If the thread is shutting down.
+ */
+extern nsresult NS_DispatchToThreadQueue(already_AddRefed<nsIRunnable>&& aEvent,
+ uint32_t aTimeout, nsIThread* aThread,
+ mozilla::EventQueuePriority aQueue);
+
+#ifndef XPCOM_GLUE_AVOID_NSPR
+/**
+ * Process all pending events for the given thread before returning. This
+ * method simply calls ProcessNextEvent on the thread while HasPendingEvents
+ * continues to return true and the time spent in NS_ProcessPendingEvents
+ * does not exceed the given timeout value.
+ *
+ * @param aThread
+ * The thread object for which to process pending events. If null, then
+ * events will be processed for the current thread.
+ * @param aTimeout
+ * The maximum number of milliseconds to spend processing pending events.
+ * Events are not pre-empted to honor this timeout. Rather, the timeout
+ * value is simply used to determine whether or not to process another event.
+ * Pass PR_INTERVAL_NO_TIMEOUT to specify no timeout.
+ */
+extern nsresult NS_ProcessPendingEvents(
+ nsIThread* aThread, PRIntervalTime aTimeout = PR_INTERVAL_NO_TIMEOUT);
+#endif
+
+/**
+ * Shortcut for nsIThread::HasPendingEvents.
+ *
+ * It is an error to call this function when the given thread is not the
+ * current thread. This function will return false if called from some
+ * other thread.
+ *
+ * @param aThread
+ * The current thread or null.
+ *
+ * @returns
+ * A boolean value that if "true" indicates that there are pending events
+ * in the current thread's event queue.
+ */
+extern bool NS_HasPendingEvents(nsIThread* aThread = nullptr);
+
+/**
+ * Shortcut for nsIThread::ProcessNextEvent.
+ *
+ * It is an error to call this function when the given thread is not the
+ * current thread. This function will simply return false if called
+ * from some other thread.
+ *
+ * @param aThread
+ * The current thread or null.
+ * @param aMayWait
+ * A boolean parameter that if "true" indicates that the method may block
+ * the calling thread to wait for a pending event.
+ *
+ * @returns
+ * A boolean value that if "true" indicates that an event from the current
+ * thread's event queue was processed.
+ */
+extern bool NS_ProcessNextEvent(nsIThread* aThread = nullptr,
+ bool aMayWait = true);
+
+/**
+ * Returns true if we're in the compositor thread.
+ *
+ * We declare this here because the headers required to invoke
+ * CompositorThreadHolder::IsInCompositorThread() also pull in a bunch of system
+ * headers that #define various tokens in a way that can break the build.
+ */
+extern bool NS_IsInCompositorThread();
+
+extern bool NS_IsInCanvasThreadOrWorker();
+
+extern bool NS_IsInVRThread();
+
+//-----------------------------------------------------------------------------
+// Helpers that work with nsCOMPtr:
+
+inline already_AddRefed<nsIThread> do_GetCurrentThread() {
+ nsIThread* thread = nullptr;
+ NS_GetCurrentThread(&thread);
+ return already_AddRefed<nsIThread>(thread);
+}
+
+inline already_AddRefed<nsIThread> do_GetMainThread() {
+ nsIThread* thread = nullptr;
+ NS_GetMainThread(&thread);
+ return already_AddRefed<nsIThread>(thread);
+}
+
+//-----------------------------------------------------------------------------
+
+#ifdef MOZILLA_INTERNAL_API
+// Fast access to the current thread. Will create an nsIThread if one does not
+// exist already! Do not release the returned pointer! If you want to use this
+// pointer from some other thread, then you will need to AddRef it. Otherwise,
+// you should only consider this pointer valid from code running on the current
+// thread.
+extern nsIThread* NS_GetCurrentThread();
+
+// Exactly the same as NS_GetCurrentThread, except it will not create an
+// nsThread if one does not exist yet. This is useful in cases where you have
+// code that runs on threads that may or may not not be driven by an nsThread
+// event loop, and wish to avoid inadvertently creating a superfluous nsThread.
+extern nsIThread* NS_GetCurrentThreadNoCreate();
+
+/**
+ * Set the name of the current thread. Prefer this function over
+ * PR_SetCurrentThreadName() if possible. The name will also be included in the
+ * crash report.
+ *
+ * @param aName
+ * Name of the thread. A C language null-terminated string.
+ */
+extern void NS_SetCurrentThreadName(const char* aName);
+#endif
+
+//-----------------------------------------------------------------------------
+
+#ifndef XPCOM_GLUE_AVOID_NSPR
+
+namespace mozilla {
+
+// This class is designed to be subclassed.
+class IdlePeriod : public nsIIdlePeriod {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIDLEPERIOD
+
+ IdlePeriod() = default;
+
+ protected:
+ virtual ~IdlePeriod() = default;
+
+ private:
+ IdlePeriod(const IdlePeriod&) = delete;
+ IdlePeriod& operator=(const IdlePeriod&) = delete;
+ IdlePeriod& operator=(const IdlePeriod&&) = delete;
+};
+
+// Cancelable runnable methods implement nsICancelableRunnable, and
+// Idle and IdleWithTimer also nsIIdleRunnable.
+enum class RunnableKind { Standard, Cancelable, Idle, IdleWithTimer };
+
+// Implementing nsINamed on Runnable bloats vtables for the hundreds of
+// Runnable subclasses that we have, so we want to avoid that overhead
+// when we're not using nsINamed for anything.
+# ifndef RELEASE_OR_BETA
+# define MOZ_COLLECTING_RUNNABLE_TELEMETRY
+# endif
+
+// This class is designed to be subclassed.
+class Runnable : public nsIRunnable
+# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+ ,
+ public nsINamed
+# endif
+{
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+ NS_DECL_NSINAMED
+# endif
+
+ Runnable() = delete;
+
+# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+ explicit Runnable(const char* aName) : mName(aName) {}
+# else
+ explicit Runnable(const char* aName) {}
+# endif
+
+ protected:
+ virtual ~Runnable() = default;
+
+# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+ const char* mName = nullptr;
+# endif
+
+ private:
+ Runnable(const Runnable&) = delete;
+ Runnable& operator=(const Runnable&) = delete;
+ Runnable& operator=(const Runnable&&) = delete;
+};
+
+// This is a base class for tasks that might not be run, such as those that may
+// be dispatched to workers.
+// The owner of an event target will call either Run() or OnDiscard()
+// exactly once.
+// Derived classes should override Run(). An OnDiscard() override may
+// provide cleanup when Run() will not be called.
+class DiscardableRunnable : public Runnable, public nsIDiscardableRunnable {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ // nsIDiscardableRunnable
+ void OnDiscard() override {}
+
+ DiscardableRunnable() = delete;
+ explicit DiscardableRunnable(const char* aName) : Runnable(aName) {}
+
+ protected:
+ virtual ~DiscardableRunnable() = default;
+
+ private:
+ DiscardableRunnable(const DiscardableRunnable&) = delete;
+ DiscardableRunnable& operator=(const DiscardableRunnable&) = delete;
+ DiscardableRunnable& operator=(const DiscardableRunnable&&) = delete;
+};
+
+// This class is designed to be subclassed.
+// Derived classes should override Run() and Cancel() to provide that
+// calling Run() after Cancel() is a no-op.
+class CancelableRunnable : public DiscardableRunnable,
+ public nsICancelableRunnable {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ // nsIDiscardableRunnable
+ void OnDiscard() override;
+ // nsICancelableRunnable
+ virtual nsresult Cancel() override = 0;
+
+ CancelableRunnable() = delete;
+ explicit CancelableRunnable(const char* aName) : DiscardableRunnable(aName) {}
+
+ protected:
+ virtual ~CancelableRunnable() = default;
+
+ private:
+ CancelableRunnable(const CancelableRunnable&) = delete;
+ CancelableRunnable& operator=(const CancelableRunnable&) = delete;
+ CancelableRunnable& operator=(const CancelableRunnable&&) = delete;
+};
+
+// This class is designed to be subclassed.
+class IdleRunnable : public DiscardableRunnable, public nsIIdleRunnable {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ IdleRunnable() : DiscardableRunnable("IdleRunnable") {}
+ explicit IdleRunnable(const char* aName) : DiscardableRunnable(aName) {}
+
+ protected:
+ virtual ~IdleRunnable() = default;
+
+ private:
+ IdleRunnable(const IdleRunnable&) = delete;
+ IdleRunnable& operator=(const IdleRunnable&) = delete;
+ IdleRunnable& operator=(const IdleRunnable&&) = delete;
+};
+
+// This class is designed to be subclassed.
+class CancelableIdleRunnable : public CancelableRunnable,
+ public nsIIdleRunnable {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ CancelableIdleRunnable() : CancelableRunnable("CancelableIdleRunnable") {}
+ explicit CancelableIdleRunnable(const char* aName)
+ : CancelableRunnable(aName) {}
+
+ protected:
+ virtual ~CancelableIdleRunnable() = default;
+
+ private:
+ CancelableIdleRunnable(const CancelableIdleRunnable&) = delete;
+ CancelableIdleRunnable& operator=(const CancelableIdleRunnable&) = delete;
+ CancelableIdleRunnable& operator=(const CancelableIdleRunnable&&) = delete;
+};
+
+// This class is designed to be a wrapper of a real runnable to support event
+// prioritizable.
+class PrioritizableRunnable : public Runnable, public nsIRunnablePriority {
+ public:
+ PrioritizableRunnable(already_AddRefed<nsIRunnable>&& aRunnable,
+ uint32_t aPriority);
+
+# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+ NS_IMETHOD GetName(nsACString& aName) override;
+# endif
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSIRUNNABLEPRIORITY
+
+ protected:
+ virtual ~PrioritizableRunnable() = default;
+ ;
+ nsCOMPtr<nsIRunnable> mRunnable;
+ uint32_t mPriority;
+};
+
+extern already_AddRefed<nsIRunnable> CreateMediumHighRunnable(
+ already_AddRefed<nsIRunnable>&& aRunnable);
+
+namespace detail {
+
+// An event that can be used to call a C++11 functions or function objects,
+// including lambdas. The function must have no required arguments, and must
+// return void.
+template <typename StoredFunction>
+class RunnableFunction : public Runnable {
+ public:
+ template <typename F>
+ explicit RunnableFunction(const char* aName, F&& aFunction)
+ : Runnable(aName), mFunction(std::forward<F>(aFunction)) {}
+
+ NS_IMETHOD Run() override {
+ static_assert(std::is_void_v<decltype(mFunction())>,
+ "The lambda must return void!");
+ mFunction();
+ return NS_OK;
+ }
+
+ private:
+ StoredFunction mFunction;
+};
+
+// Type alias for NS_NewRunnableFunction
+template <typename Function>
+using RunnableFunctionImpl =
+ // Make sure we store a non-reference in nsRunnableFunction.
+ typename detail::RunnableFunction<std::remove_reference_t<Function>>;
+} // namespace detail
+
+namespace detail {
+
+template <typename CVRemoved>
+struct IsRefcountedSmartPointerHelper : std::false_type {};
+
+template <typename Pointee>
+struct IsRefcountedSmartPointerHelper<RefPtr<Pointee>> : std::true_type {};
+
+template <typename Pointee>
+struct IsRefcountedSmartPointerHelper<nsCOMPtr<Pointee>> : std::true_type {};
+
+} // namespace detail
+
+template <typename T>
+struct IsRefcountedSmartPointer
+ : detail::IsRefcountedSmartPointerHelper<std::remove_cv_t<T>> {};
+
+namespace detail {
+
+template <typename T, typename CVRemoved>
+struct RemoveSmartPointerHelper {
+ typedef T Type;
+};
+
+template <typename T, typename Pointee>
+struct RemoveSmartPointerHelper<T, RefPtr<Pointee>> {
+ typedef Pointee Type;
+};
+
+template <typename T, typename Pointee>
+struct RemoveSmartPointerHelper<T, nsCOMPtr<Pointee>> {
+ typedef Pointee Type;
+};
+
+} // namespace detail
+
+template <typename T>
+struct RemoveSmartPointer
+ : detail::RemoveSmartPointerHelper<T, std::remove_cv_t<T>> {};
+
+namespace detail {
+
+template <typename T, typename CVRemoved>
+struct RemoveRawOrSmartPointerHelper {
+ typedef T Type;
+};
+
+template <typename T, typename Pointee>
+struct RemoveRawOrSmartPointerHelper<T, Pointee*> {
+ typedef Pointee Type;
+};
+
+template <typename T, typename Pointee>
+struct RemoveRawOrSmartPointerHelper<T, RefPtr<Pointee>> {
+ typedef Pointee Type;
+};
+
+template <typename T, typename Pointee>
+struct RemoveRawOrSmartPointerHelper<T, nsCOMPtr<Pointee>> {
+ typedef Pointee Type;
+};
+
+} // namespace detail
+
+template <typename T>
+struct RemoveRawOrSmartPointer
+ : detail::RemoveRawOrSmartPointerHelper<T, std::remove_cv_t<T>> {};
+
+} // namespace mozilla
+
+inline nsISupports* ToSupports(mozilla::Runnable* p) {
+ return static_cast<nsIRunnable*>(p);
+}
+
+template <typename Function>
+already_AddRefed<mozilla::Runnable> NS_NewRunnableFunction(
+ const char* aName, Function&& aFunction) {
+ // We store a non-reference in RunnableFunction, but still forward aFunction
+ // to move if possible.
+ return do_AddRef(new mozilla::detail::RunnableFunctionImpl<Function>(
+ aName, std::forward<Function>(aFunction)));
+}
+
+// Creates a new object implementing nsIRunnable and nsICancelableRunnable,
+// which runs a given function on Run and clears the stored function object on a
+// call to `Cancel` (and thus destroys all objects it holds).
+template <typename Function>
+already_AddRefed<mozilla::CancelableRunnable> NS_NewCancelableRunnableFunction(
+ const char* aName, Function&& aFunc) {
+ class FuncCancelableRunnable final : public mozilla::CancelableRunnable {
+ public:
+ static_assert(std::is_void_v<decltype(
+ std::declval<std::remove_reference_t<Function>>()())>);
+
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(FuncCancelableRunnable,
+ CancelableRunnable)
+
+ explicit FuncCancelableRunnable(const char* aName, Function&& aFunc)
+ : CancelableRunnable{aName},
+ mFunc{mozilla::Some(std::forward<Function>(aFunc))} {}
+
+ NS_IMETHOD Run() override {
+ if (mFunc) {
+ (*mFunc)();
+ }
+
+ return NS_OK;
+ }
+
+ nsresult Cancel() override {
+ mFunc.reset();
+ return NS_OK;
+ }
+
+ private:
+ ~FuncCancelableRunnable() = default;
+
+ mozilla::Maybe<std::remove_reference_t<Function>> mFunc;
+ };
+
+ return mozilla::MakeAndAddRef<FuncCancelableRunnable>(
+ aName, std::forward<Function>(aFunc));
+}
+
+namespace mozilla {
+namespace detail {
+
+template <RunnableKind Kind>
+class TimerBehaviour {
+ public:
+ nsITimer* GetTimer() { return nullptr; }
+ void CancelTimer() {}
+
+ protected:
+ ~TimerBehaviour() = default;
+};
+
+template <>
+class TimerBehaviour<RunnableKind::IdleWithTimer> {
+ public:
+ nsITimer* GetTimer() {
+ if (!mTimer) {
+ mTimer = NS_NewTimer();
+ }
+
+ return mTimer;
+ }
+
+ void CancelTimer() {
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+ }
+
+ protected:
+ ~TimerBehaviour() { CancelTimer(); }
+
+ private:
+ nsCOMPtr<nsITimer> mTimer;
+};
+
+} // namespace detail
+} // namespace mozilla
+
+// An event that can be used to call a method on a class. The class type must
+// support reference counting. This event supports Revoke for use
+// with nsRevocableEventPtr.
+template <class ClassType, typename ReturnType = void, bool Owning = true,
+ mozilla::RunnableKind Kind = mozilla::RunnableKind::Standard>
+class nsRunnableMethod
+ : public std::conditional_t<
+ Kind == mozilla::RunnableKind::Standard, mozilla::Runnable,
+ std::conditional_t<Kind == mozilla::RunnableKind::Cancelable,
+ mozilla::CancelableRunnable,
+ mozilla::CancelableIdleRunnable>>,
+ protected mozilla::detail::TimerBehaviour<Kind> {
+ using BaseType = std::conditional_t<
+ Kind == mozilla::RunnableKind::Standard, mozilla::Runnable,
+ std::conditional_t<Kind == mozilla::RunnableKind::Cancelable,
+ mozilla::CancelableRunnable,
+ mozilla::CancelableIdleRunnable>>;
+
+ public:
+ nsRunnableMethod(const char* aName) : BaseType(aName) {}
+
+ virtual void Revoke() = 0;
+
+ // These ReturnTypeEnforcer classes disallow return types that
+ // we know are not safe. The default ReturnTypeEnforcer compiles just fine but
+ // already_AddRefed will not.
+ template <typename OtherReturnType>
+ class ReturnTypeEnforcer {
+ public:
+ typedef int ReturnTypeIsSafe;
+ };
+
+ template <class T>
+ class ReturnTypeEnforcer<already_AddRefed<T>> {
+ // No ReturnTypeIsSafe makes this illegal!
+ };
+
+ // Make sure this return type is safe.
+ typedef typename ReturnTypeEnforcer<ReturnType>::ReturnTypeIsSafe check;
+};
+
+template <class ClassType, bool Owning>
+struct nsRunnableMethodReceiver {
+ RefPtr<ClassType> mObj;
+ explicit nsRunnableMethodReceiver(ClassType* aObj) : mObj(aObj) {}
+ explicit nsRunnableMethodReceiver(RefPtr<ClassType>&& aObj)
+ : mObj(std::move(aObj)) {}
+ ~nsRunnableMethodReceiver() { Revoke(); }
+ ClassType* Get() const { return mObj.get(); }
+ void Revoke() { mObj = nullptr; }
+};
+
+template <class ClassType>
+struct nsRunnableMethodReceiver<ClassType, false> {
+ ClassType* MOZ_NON_OWNING_REF mObj;
+ explicit nsRunnableMethodReceiver(ClassType* aObj) : mObj(aObj) {}
+ ClassType* Get() const { return mObj; }
+ void Revoke() { mObj = nullptr; }
+};
+
+static inline constexpr bool IsIdle(mozilla::RunnableKind aKind) {
+ return aKind == mozilla::RunnableKind::Idle ||
+ aKind == mozilla::RunnableKind::IdleWithTimer;
+}
+
+template <typename PtrType, typename Method, bool Owning,
+ mozilla::RunnableKind Kind>
+struct nsRunnableMethodTraits;
+
+template <typename PtrType, class C, typename R, bool Owning,
+ mozilla::RunnableKind Kind, typename... As>
+struct nsRunnableMethodTraits<PtrType, R (C::*)(As...), Owning, Kind> {
+ typedef typename mozilla::RemoveRawOrSmartPointer<PtrType>::Type class_type;
+ static_assert(std::is_base_of<C, class_type>::value,
+ "Stored class must inherit from method's class");
+ typedef R return_type;
+ typedef nsRunnableMethod<C, R, Owning, Kind> base_type;
+ static const bool can_cancel = Kind == mozilla::RunnableKind::Cancelable;
+};
+
+template <typename PtrType, class C, typename R, bool Owning,
+ mozilla::RunnableKind Kind, typename... As>
+struct nsRunnableMethodTraits<PtrType, R (C::*)(As...) const, Owning, Kind> {
+ typedef const typename mozilla::RemoveRawOrSmartPointer<PtrType>::Type
+ class_type;
+ static_assert(std::is_base_of<C, class_type>::value,
+ "Stored class must inherit from method's class");
+ typedef R return_type;
+ typedef nsRunnableMethod<C, R, Owning, Kind> base_type;
+ static const bool can_cancel = Kind == mozilla::RunnableKind::Cancelable;
+};
+
+# ifdef NS_HAVE_STDCALL
+template <typename PtrType, class C, typename R, bool Owning,
+ mozilla::RunnableKind Kind, typename... As>
+struct nsRunnableMethodTraits<PtrType, R (__stdcall C::*)(As...), Owning,
+ Kind> {
+ typedef typename mozilla::RemoveRawOrSmartPointer<PtrType>::Type class_type;
+ static_assert(std::is_base_of<C, class_type>::value,
+ "Stored class must inherit from method's class");
+ typedef R return_type;
+ typedef nsRunnableMethod<C, R, Owning, Kind> base_type;
+ static const bool can_cancel = Kind == mozilla::RunnableKind::Cancelable;
+};
+
+template <typename PtrType, class C, typename R, bool Owning,
+ mozilla::RunnableKind Kind>
+struct nsRunnableMethodTraits<PtrType, R (NS_STDCALL C::*)(), Owning, Kind> {
+ typedef typename mozilla::RemoveRawOrSmartPointer<PtrType>::Type class_type;
+ static_assert(std::is_base_of<C, class_type>::value,
+ "Stored class must inherit from method's class");
+ typedef R return_type;
+ typedef nsRunnableMethod<C, R, Owning, Kind> base_type;
+ static const bool can_cancel = Kind == mozilla::RunnableKind::Cancelable;
+};
+
+template <typename PtrType, class C, typename R, bool Owning,
+ mozilla::RunnableKind Kind, typename... As>
+struct nsRunnableMethodTraits<PtrType, R (__stdcall C::*)(As...) const, Owning,
+ Kind> {
+ typedef const typename mozilla::RemoveRawOrSmartPointer<PtrType>::Type
+ class_type;
+ static_assert(std::is_base_of<C, class_type>::value,
+ "Stored class must inherit from method's class");
+ typedef R return_type;
+ typedef nsRunnableMethod<C, R, Owning, Kind> base_type;
+ static const bool can_cancel = Kind == mozilla::RunnableKind::Cancelable;
+};
+
+template <typename PtrType, class C, typename R, bool Owning,
+ mozilla::RunnableKind Kind>
+struct nsRunnableMethodTraits<PtrType, R (NS_STDCALL C::*)() const, Owning,
+ Kind> {
+ typedef const typename mozilla::RemoveRawOrSmartPointer<PtrType>::Type
+ class_type;
+ static_assert(std::is_base_of<C, class_type>::value,
+ "Stored class must inherit from method's class");
+ typedef R return_type;
+ typedef nsRunnableMethod<C, R, Owning, Kind> base_type;
+ static const bool can_cancel = Kind == mozilla::RunnableKind::Cancelable;
+};
+# endif
+
+// IsParameterStorageClass<T>::value is true if T is a parameter-storage class
+// that will be recognized by NS_New[NonOwning]RunnableMethodWithArg[s] to
+// force a specific storage&passing strategy (instead of inferring one,
+// see ParameterStorage).
+// When creating a new storage class, add a specialization for it to be
+// recognized.
+template <typename T>
+struct IsParameterStorageClass : public std::false_type {};
+
+// StoreXPassByY structs used to inform nsRunnableMethodArguments how to
+// store arguments, and how to pass them to the target method.
+
+template <typename T>
+struct StoreCopyPassByValue {
+ using stored_type = std::decay_t<T>;
+ typedef stored_type passed_type;
+ stored_type m;
+ template <typename A>
+ MOZ_IMPLICIT StoreCopyPassByValue(A&& a) : m(std::forward<A>(a)) {}
+ passed_type PassAsParameter() { return m; }
+};
+template <typename S>
+struct IsParameterStorageClass<StoreCopyPassByValue<S>>
+ : public std::true_type {};
+
+template <typename T>
+struct StoreCopyPassByConstLRef {
+ using stored_type = std::decay_t<T>;
+ typedef const stored_type& passed_type;
+ stored_type m;
+ template <typename A>
+ MOZ_IMPLICIT StoreCopyPassByConstLRef(A&& a) : m(std::forward<A>(a)) {}
+ passed_type PassAsParameter() { return m; }
+};
+template <typename S>
+struct IsParameterStorageClass<StoreCopyPassByConstLRef<S>>
+ : public std::true_type {};
+
+template <typename T>
+struct StoreCopyPassByLRef {
+ using stored_type = std::decay_t<T>;
+ typedef stored_type& passed_type;
+ stored_type m;
+ template <typename A>
+ MOZ_IMPLICIT StoreCopyPassByLRef(A&& a) : m(std::forward<A>(a)) {}
+ passed_type PassAsParameter() { return m; }
+};
+template <typename S>
+struct IsParameterStorageClass<StoreCopyPassByLRef<S>> : public std::true_type {
+};
+
+template <typename T>
+struct StoreCopyPassByRRef {
+ using stored_type = std::decay_t<T>;
+ typedef stored_type&& passed_type;
+ stored_type m;
+ template <typename A>
+ MOZ_IMPLICIT StoreCopyPassByRRef(A&& a) : m(std::forward<A>(a)) {}
+ passed_type PassAsParameter() { return std::move(m); }
+};
+template <typename S>
+struct IsParameterStorageClass<StoreCopyPassByRRef<S>> : public std::true_type {
+};
+
+template <typename T>
+struct StoreRefPassByLRef {
+ typedef T& stored_type;
+ typedef T& passed_type;
+ stored_type m;
+ template <typename A>
+ MOZ_IMPLICIT StoreRefPassByLRef(A& a) : m(a) {}
+ passed_type PassAsParameter() { return m; }
+};
+template <typename S>
+struct IsParameterStorageClass<StoreRefPassByLRef<S>> : public std::true_type {
+};
+
+template <typename T>
+struct StoreConstRefPassByConstLRef {
+ typedef const T& stored_type;
+ typedef const T& passed_type;
+ stored_type m;
+ template <typename A>
+ MOZ_IMPLICIT StoreConstRefPassByConstLRef(const A& a) : m(a) {}
+ passed_type PassAsParameter() { return m; }
+};
+template <typename S>
+struct IsParameterStorageClass<StoreConstRefPassByConstLRef<S>>
+ : public std::true_type {};
+
+template <typename T>
+struct StoreRefPtrPassByPtr {
+ typedef RefPtr<T> stored_type;
+ typedef T* passed_type;
+ stored_type m;
+ template <typename A>
+ MOZ_IMPLICIT StoreRefPtrPassByPtr(A&& a) : m(std::forward<A>(a)) {}
+ passed_type PassAsParameter() { return m.get(); }
+};
+template <typename S>
+struct IsParameterStorageClass<StoreRefPtrPassByPtr<S>>
+ : public std::true_type {};
+
+template <typename T>
+struct StorePtrPassByPtr {
+ typedef T* stored_type;
+ typedef T* passed_type;
+ stored_type m;
+ template <typename A>
+ MOZ_IMPLICIT StorePtrPassByPtr(A a) : m(a) {}
+ passed_type PassAsParameter() { return m; }
+};
+template <typename S>
+struct IsParameterStorageClass<StorePtrPassByPtr<S>> : public std::true_type {};
+
+template <typename T>
+struct StoreConstPtrPassByConstPtr {
+ typedef const T* stored_type;
+ typedef const T* passed_type;
+ stored_type m;
+ template <typename A>
+ MOZ_IMPLICIT StoreConstPtrPassByConstPtr(A a) : m(a) {}
+ passed_type PassAsParameter() { return m; }
+};
+template <typename S>
+struct IsParameterStorageClass<StoreConstPtrPassByConstPtr<S>>
+ : public std::true_type {};
+
+template <typename T>
+struct StoreCopyPassByConstPtr {
+ typedef T stored_type;
+ typedef const T* passed_type;
+ stored_type m;
+ template <typename A>
+ MOZ_IMPLICIT StoreCopyPassByConstPtr(A&& a) : m(std::forward<A>(a)) {}
+ passed_type PassAsParameter() { return &m; }
+};
+template <typename S>
+struct IsParameterStorageClass<StoreCopyPassByConstPtr<S>>
+ : public std::true_type {};
+
+template <typename T>
+struct StoreCopyPassByPtr {
+ typedef T stored_type;
+ typedef T* passed_type;
+ stored_type m;
+ template <typename A>
+ MOZ_IMPLICIT StoreCopyPassByPtr(A&& a) : m(std::forward<A>(a)) {}
+ passed_type PassAsParameter() { return &m; }
+};
+template <typename S>
+struct IsParameterStorageClass<StoreCopyPassByPtr<S>> : public std::true_type {
+};
+
+namespace detail {
+
+template <typename>
+struct SFINAE1True : std::true_type {};
+
+template <class T>
+static auto HasRefCountMethodsTest(int)
+ -> SFINAE1True<decltype(std::declval<T>().AddRef(),
+ std::declval<T>().Release())>;
+template <class>
+static auto HasRefCountMethodsTest(long) -> std::false_type;
+
+template <class T>
+struct HasRefCountMethods : decltype(HasRefCountMethodsTest<T>(0)) {};
+
+template <typename TWithoutPointer>
+struct NonnsISupportsPointerStorageClass
+ : std::conditional<
+ std::is_const_v<TWithoutPointer>,
+ StoreConstPtrPassByConstPtr<std::remove_const_t<TWithoutPointer>>,
+ StorePtrPassByPtr<TWithoutPointer>> {
+ using Type = typename NonnsISupportsPointerStorageClass::conditional::type;
+};
+
+template <typename TWithoutPointer>
+struct PointerStorageClass
+ : std::conditional<
+ HasRefCountMethods<TWithoutPointer>::value,
+ StoreRefPtrPassByPtr<TWithoutPointer>,
+ typename NonnsISupportsPointerStorageClass<TWithoutPointer>::Type> {
+ using Type = typename PointerStorageClass::conditional::type;
+};
+
+template <typename TWithoutRef>
+struct LValueReferenceStorageClass
+ : std::conditional<
+ std::is_const_v<TWithoutRef>,
+ StoreConstRefPassByConstLRef<std::remove_const_t<TWithoutRef>>,
+ StoreRefPassByLRef<TWithoutRef>> {
+ using Type = typename LValueReferenceStorageClass::conditional::type;
+};
+
+template <typename T>
+struct SmartPointerStorageClass
+ : std::conditional<
+ mozilla::IsRefcountedSmartPointer<T>::value,
+ StoreRefPtrPassByPtr<typename mozilla::RemoveSmartPointer<T>::Type>,
+ StoreCopyPassByConstLRef<T>> {
+ using Type = typename SmartPointerStorageClass::conditional::type;
+};
+
+template <typename T>
+struct NonLValueReferenceStorageClass
+ : std::conditional<std::is_rvalue_reference_v<T>,
+ StoreCopyPassByRRef<std::remove_reference_t<T>>,
+ typename SmartPointerStorageClass<T>::Type> {
+ using Type = typename NonLValueReferenceStorageClass::conditional::type;
+};
+
+template <typename T>
+struct NonPointerStorageClass
+ : std::conditional<std::is_lvalue_reference_v<T>,
+ typename LValueReferenceStorageClass<
+ std::remove_reference_t<T>>::Type,
+ typename NonLValueReferenceStorageClass<T>::Type> {
+ using Type = typename NonPointerStorageClass::conditional::type;
+};
+
+template <typename T>
+struct NonParameterStorageClass
+ : std::conditional<
+ std::is_pointer_v<T>,
+ typename PointerStorageClass<std::remove_pointer_t<T>>::Type,
+ typename NonPointerStorageClass<T>::Type> {
+ using Type = typename NonParameterStorageClass::conditional::type;
+};
+
+// Choose storage&passing strategy based on preferred storage type:
+// - If IsParameterStorageClass<T>::value is true, use as-is.
+// - RC* -> StoreRefPtrPassByPtr<RC> :Store RefPtr<RC>, pass RC*
+// ^^ RC quacks like a ref-counted type (i.e., has AddRef and Release methods)
+// - const T* -> StoreConstPtrPassByConstPtr<T> :Store const T*, pass const T*
+// - T* -> StorePtrPassByPtr<T> :Store T*, pass T*.
+// - const T& -> StoreConstRefPassByConstLRef<T>:Store const T&, pass const T&.
+// - T& -> StoreRefPassByLRef<T> :Store T&, pass T&.
+// - T&& -> StoreCopyPassByRRef<T> :Store T, pass std::move(T).
+// - RefPtr<T>, nsCOMPtr<T>
+// -> StoreRefPtrPassByPtr<T> :Store RefPtr<T>, pass T*
+// - Other T -> StoreCopyPassByConstLRef<T> :Store T, pass const T&.
+// Other available explicit options:
+// - StoreCopyPassByValue<T> :Store T, pass T.
+// - StoreCopyPassByLRef<T> :Store T, pass T& (of copy!)
+// - StoreCopyPassByConstPtr<T> :Store T, pass const T*
+// - StoreCopyPassByPtr<T> :Store T, pass T* (of copy!)
+// Or create your own class with PassAsParameter() method, optional
+// clean-up in destructor, and with associated IsParameterStorageClass<>.
+template <typename T>
+struct ParameterStorage
+ : std::conditional<IsParameterStorageClass<T>::value, T,
+ typename NonParameterStorageClass<T>::Type> {
+ using Type = typename ParameterStorage::conditional::type;
+};
+
+template <class T>
+static auto HasSetDeadlineTest(int) -> SFINAE1True<decltype(
+ std::declval<T>().SetDeadline(std::declval<mozilla::TimeStamp>()))>;
+
+template <class T>
+static auto HasSetDeadlineTest(long) -> std::false_type;
+
+template <class T>
+struct HasSetDeadline : decltype(HasSetDeadlineTest<T>(0)) {};
+
+template <class T>
+std::enable_if_t<::detail::HasSetDeadline<T>::value> SetDeadlineImpl(
+ T* aObj, mozilla::TimeStamp aTimeStamp) {
+ aObj->SetDeadline(aTimeStamp);
+}
+
+template <class T>
+std::enable_if_t<!::detail::HasSetDeadline<T>::value> SetDeadlineImpl(
+ T* aObj, mozilla::TimeStamp aTimeStamp) {}
+} /* namespace detail */
+
+namespace mozilla {
+namespace detail {
+
+// struct used to store arguments and later apply them to a method.
+template <typename... Ts>
+struct RunnableMethodArguments final {
+ Tuple<typename ::detail::ParameterStorage<Ts>::Type...> mArguments;
+ template <typename... As>
+ explicit RunnableMethodArguments(As&&... aArguments)
+ : mArguments(std::forward<As>(aArguments)...) {}
+ template <typename C, typename M, typename... Args, size_t... Indices>
+ static auto applyImpl(C* o, M m, Tuple<Args...>& args,
+ std::index_sequence<Indices...>)
+ -> decltype(((*o).*m)(Get<Indices>(args).PassAsParameter()...)) {
+ return ((*o).*m)(Get<Indices>(args).PassAsParameter()...);
+ }
+ template <class C, typename M>
+ auto apply(C* o, M m)
+ -> decltype(applyImpl(o, m, mArguments,
+ std::index_sequence_for<Ts...>{})) {
+ return applyImpl(o, m, mArguments, std::index_sequence_for<Ts...>{});
+ }
+};
+
+template <typename PtrType, typename Method, bool Owning, RunnableKind Kind,
+ typename... Storages>
+class RunnableMethodImpl final
+ : public ::nsRunnableMethodTraits<PtrType, Method, Owning,
+ Kind>::base_type {
+ typedef typename ::nsRunnableMethodTraits<PtrType, Method, Owning, Kind>
+ Traits;
+
+ typedef typename Traits::class_type ClassType;
+ typedef typename Traits::base_type BaseType;
+ ::nsRunnableMethodReceiver<ClassType, Owning> mReceiver;
+ Method mMethod;
+ RunnableMethodArguments<Storages...> mArgs;
+ using BaseType::CancelTimer;
+ using BaseType::GetTimer;
+
+ private:
+ virtual ~RunnableMethodImpl() { Revoke(); };
+ static void TimedOut(nsITimer* aTimer, void* aClosure) {
+ static_assert(IsIdle(Kind), "Don't use me!");
+ RefPtr<CancelableIdleRunnable> r =
+ static_cast<CancelableIdleRunnable*>(aClosure);
+ r->SetDeadline(TimeStamp());
+ r->Run();
+ r->Cancel();
+ }
+
+ public:
+ template <typename ForwardedPtrType, typename... Args>
+ explicit RunnableMethodImpl(const char* aName, ForwardedPtrType&& aObj,
+ Method aMethod, Args&&... aArgs)
+ : BaseType(aName),
+ mReceiver(std::forward<ForwardedPtrType>(aObj)),
+ mMethod(aMethod),
+ mArgs(std::forward<Args>(aArgs)...) {
+ static_assert(sizeof...(Storages) == sizeof...(Args),
+ "Storages and Args should have equal sizes");
+ }
+
+ NS_IMETHOD Run() {
+ CancelTimer();
+
+ if (MOZ_LIKELY(mReceiver.Get())) {
+ mArgs.apply(mReceiver.Get(), mMethod);
+ }
+
+ return NS_OK;
+ }
+
+ nsresult Cancel() {
+ static_assert(Kind >= RunnableKind::Cancelable, "Don't use me!");
+ Revoke();
+ return NS_OK;
+ }
+
+ void Revoke() {
+ CancelTimer();
+ mReceiver.Revoke();
+ }
+
+ void SetDeadline(TimeStamp aDeadline) {
+ if (MOZ_LIKELY(mReceiver.Get())) {
+ ::detail::SetDeadlineImpl(mReceiver.Get(), aDeadline);
+ }
+ }
+
+ void SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) {
+ MOZ_ASSERT(aTarget);
+
+ if (nsCOMPtr<nsITimer> timer = GetTimer()) {
+ timer->Cancel();
+ timer->SetTarget(aTarget);
+ timer->InitWithNamedFuncCallback(TimedOut, this, aDelay,
+ nsITimer::TYPE_ONE_SHOT,
+ "detail::RunnableMethodImpl::SetTimer");
+ }
+ }
+};
+
+// Type aliases for NewRunnableMethod.
+template <typename PtrType, typename Method>
+using OwningRunnableMethod =
+ typename ::nsRunnableMethodTraits<std::remove_reference_t<PtrType>, Method,
+ true, RunnableKind::Standard>::base_type;
+template <typename PtrType, typename Method, typename... Storages>
+using OwningRunnableMethodImpl =
+ RunnableMethodImpl<std::remove_reference_t<PtrType>, Method, true,
+ RunnableKind::Standard, Storages...>;
+
+// Type aliases for NewCancelableRunnableMethod.
+template <typename PtrType, typename Method>
+using CancelableRunnableMethod =
+ typename ::nsRunnableMethodTraits<std::remove_reference_t<PtrType>, Method,
+ true,
+ RunnableKind::Cancelable>::base_type;
+template <typename PtrType, typename Method, typename... Storages>
+using CancelableRunnableMethodImpl =
+ RunnableMethodImpl<std::remove_reference_t<PtrType>, Method, true,
+ RunnableKind::Cancelable, Storages...>;
+
+// Type aliases for NewIdleRunnableMethod.
+template <typename PtrType, typename Method>
+using IdleRunnableMethod =
+ typename ::nsRunnableMethodTraits<std::remove_reference_t<PtrType>, Method,
+ true, RunnableKind::Idle>::base_type;
+template <typename PtrType, typename Method, typename... Storages>
+using IdleRunnableMethodImpl =
+ RunnableMethodImpl<std::remove_reference_t<PtrType>, Method, true,
+ RunnableKind::Idle, Storages...>;
+
+// Type aliases for NewIdleRunnableMethodWithTimer.
+template <typename PtrType, typename Method>
+using IdleRunnableMethodWithTimer =
+ typename ::nsRunnableMethodTraits<std::remove_reference_t<PtrType>, Method,
+ true,
+ RunnableKind::IdleWithTimer>::base_type;
+template <typename PtrType, typename Method, typename... Storages>
+using IdleRunnableMethodWithTimerImpl =
+ RunnableMethodImpl<std::remove_reference_t<PtrType>, Method, true,
+ RunnableKind::IdleWithTimer, Storages...>;
+
+// Type aliases for NewNonOwningRunnableMethod.
+template <typename PtrType, typename Method>
+using NonOwningRunnableMethod =
+ typename ::nsRunnableMethodTraits<std::remove_reference_t<PtrType>, Method,
+ false, RunnableKind::Standard>::base_type;
+template <typename PtrType, typename Method, typename... Storages>
+using NonOwningRunnableMethodImpl =
+ RunnableMethodImpl<std::remove_reference_t<PtrType>, Method, false,
+ RunnableKind::Standard, Storages...>;
+
+// Type aliases for NonOwningCancelableRunnableMethod
+template <typename PtrType, typename Method>
+using NonOwningCancelableRunnableMethod =
+ typename ::nsRunnableMethodTraits<std::remove_reference_t<PtrType>, Method,
+ false,
+ RunnableKind::Cancelable>::base_type;
+template <typename PtrType, typename Method, typename... Storages>
+using NonOwningCancelableRunnableMethodImpl =
+ RunnableMethodImpl<std::remove_reference_t<PtrType>, Method, false,
+ RunnableKind::Cancelable, Storages...>;
+
+// Type aliases for NonOwningIdleRunnableMethod
+template <typename PtrType, typename Method>
+using NonOwningIdleRunnableMethod =
+ typename ::nsRunnableMethodTraits<std::remove_reference_t<PtrType>, Method,
+ false, RunnableKind::Idle>::base_type;
+template <typename PtrType, typename Method, typename... Storages>
+using NonOwningIdleRunnableMethodImpl =
+ RunnableMethodImpl<std::remove_reference_t<PtrType>, Method, false,
+ RunnableKind::Idle, Storages...>;
+
+// Type aliases for NewIdleRunnableMethodWithTimer.
+template <typename PtrType, typename Method>
+using NonOwningIdleRunnableMethodWithTimer =
+ typename ::nsRunnableMethodTraits<std::remove_reference_t<PtrType>, Method,
+ false,
+ RunnableKind::IdleWithTimer>::base_type;
+template <typename PtrType, typename Method, typename... Storages>
+using NonOwningIdleRunnableMethodWithTimerImpl =
+ RunnableMethodImpl<std::remove_reference_t<PtrType>, Method, false,
+ RunnableKind::IdleWithTimer, Storages...>;
+
+} // namespace detail
+
+// NewRunnableMethod and friends
+//
+// Very often in Gecko, you'll find yourself in a situation where you want
+// to invoke a method (with or without arguments) asynchronously. You
+// could write a small helper class inheriting from nsRunnable to handle
+// all these details, or you could let NewRunnableMethod take care of all
+// those details for you.
+//
+// The simplest use of NewRunnableMethod looks like:
+//
+// nsCOMPtr<nsIRunnable> event =
+// mozilla::NewRunnableMethod("description", myObject,
+// &MyClass::HandleEvent);
+// NS_DispatchToCurrentThread(event);
+//
+// Statically enforced constraints:
+// - myObject must be of (or implicitly convertible to) type MyClass
+// - MyClass must define AddRef and Release methods
+//
+// The "description" string should specify a human-readable name for the
+// runnable; the provided string is used by various introspection tools
+// in the browser.
+//
+// The created runnable will take a strong reference to `myObject`. For
+// non-refcounted objects, or refcounted objects with unusual refcounting
+// requirements, and if and only if you are 110% certain that `myObject`
+// will live long enough, you can use NewNonOwningRunnableMethod instead,
+// which will, as its name implies, take a non-owning reference. If you
+// find yourself having to use this function, you should accompany your use
+// with a proof comment describing why the runnable will not lead to
+// use-after-frees.
+//
+// (If you find yourself writing contorted code to Release() an object
+// asynchronously on a different thread, you should use the
+// NS_ProxyRelease function.)
+//
+// Invoking a method with arguments takes a little more care. The
+// natural extension of the above:
+//
+// nsCOMPtr<nsIRunnable> event =
+// mozilla::NewRunnableMethod("description", myObject,
+// &MyClass::HandleEvent,
+// arg1, arg2, ...);
+//
+// can lead to security hazards (e.g. passing in raw pointers to refcounted
+// objects and storing those raw pointers in the runnable). We therefore
+// require you to specify the storage types used by the runnable, just as
+// you would if you were writing out the class by hand:
+//
+// nsCOMPtr<nsIRunnable> event =
+// mozilla::NewRunnableMethod<RefPtr<T>, nsTArray<U>>
+// ("description", myObject, &MyClass::HandleEvent, arg1, arg2);
+//
+// Please note that you do not have to pass the same argument type as you
+// specify in the template arguments. For example, if you want to transfer
+// ownership to a runnable, you can write:
+//
+// RefPtr<T> ptr = ...;
+// nsTArray<U> array = ...;
+// nsCOMPtr<nsIRunnable> event =
+// mozilla::NewRunnableMethod<RefPtr<T>, nsTArray<U>>
+// ("description", myObject, &MyClass::DoSomething,
+// std::move(ptr), std::move(array));
+//
+// and there will be no extra AddRef/Release traffic, or copying of the array.
+//
+// Each type that you specify as a template argument to NewRunnableMethod
+// comes with its own style of storage in the runnable and its own style
+// of argument passing to the invoked method. See the comment for
+// ParameterStorage above for more details.
+//
+// If you need to customize the storage type and/or argument passing type,
+// you can write your own class to use as a template argument to
+// NewRunnableMethod. If you find yourself having to do that frequently,
+// please file a bug in Core::XPCOM about adding the custom type to the
+// core code in this file, and/or for custom rules for ParameterStorage
+// to select that strategy.
+//
+// For places that require you to use cancelable runnables, such as
+// workers, there's also NewCancelableRunnableMethod and its non-owning
+// counterpart. The runnables returned by these methods additionally
+// implement nsICancelableRunnable.
+//
+// Finally, all of the functions discussed above have additional overloads
+// that do not take a `const char*` as their first parameter; you may see
+// these in older code. The `const char*` overload is preferred and
+// should be used in new code exclusively.
+
+template <typename PtrType, typename Method>
+already_AddRefed<detail::OwningRunnableMethod<PtrType, Method>>
+NewRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod) {
+ return do_AddRef(new detail::OwningRunnableMethodImpl<PtrType, Method>(
+ aName, std::forward<PtrType>(aPtr), aMethod));
+}
+
+template <typename PtrType, typename Method>
+already_AddRefed<detail::CancelableRunnableMethod<PtrType, Method>>
+NewCancelableRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod) {
+ return do_AddRef(new detail::CancelableRunnableMethodImpl<PtrType, Method>(
+ aName, std::forward<PtrType>(aPtr), aMethod));
+}
+
+template <typename PtrType, typename Method>
+already_AddRefed<detail::IdleRunnableMethod<PtrType, Method>>
+NewIdleRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod) {
+ return do_AddRef(new detail::IdleRunnableMethodImpl<PtrType, Method>(
+ aName, std::forward<PtrType>(aPtr), aMethod));
+}
+
+template <typename PtrType, typename Method>
+already_AddRefed<detail::IdleRunnableMethodWithTimer<PtrType, Method>>
+NewIdleRunnableMethodWithTimer(const char* aName, PtrType&& aPtr,
+ Method aMethod) {
+ return do_AddRef(new detail::IdleRunnableMethodWithTimerImpl<PtrType, Method>(
+ aName, std::forward<PtrType>(aPtr), aMethod));
+}
+
+template <typename PtrType, typename Method>
+already_AddRefed<detail::NonOwningRunnableMethod<PtrType, Method>>
+NewNonOwningRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod) {
+ return do_AddRef(new detail::NonOwningRunnableMethodImpl<PtrType, Method>(
+ aName, std::forward<PtrType>(aPtr), aMethod));
+}
+
+template <typename PtrType, typename Method>
+already_AddRefed<detail::NonOwningCancelableRunnableMethod<PtrType, Method>>
+NewNonOwningCancelableRunnableMethod(const char* aName, PtrType&& aPtr,
+ Method aMethod) {
+ return do_AddRef(
+ new detail::NonOwningCancelableRunnableMethodImpl<PtrType, Method>(
+ aName, std::forward<PtrType>(aPtr), aMethod));
+}
+
+template <typename PtrType, typename Method>
+already_AddRefed<detail::NonOwningIdleRunnableMethod<PtrType, Method>>
+NewNonOwningIdleRunnableMethod(const char* aName, PtrType&& aPtr,
+ Method aMethod) {
+ return do_AddRef(new detail::NonOwningIdleRunnableMethodImpl<PtrType, Method>(
+ aName, std::forward<PtrType>(aPtr), aMethod));
+}
+
+template <typename PtrType, typename Method>
+already_AddRefed<detail::NonOwningIdleRunnableMethodWithTimer<PtrType, Method>>
+NewNonOwningIdleRunnableMethodWithTimer(const char* aName, PtrType&& aPtr,
+ Method aMethod) {
+ return do_AddRef(
+ new detail::NonOwningIdleRunnableMethodWithTimerImpl<PtrType, Method>(
+ aName, std::forward<PtrType>(aPtr), aMethod));
+}
+
+// Similar to NewRunnableMethod. Call like so:
+// nsCOMPtr<nsIRunnable> event =
+// NewRunnableMethod<Types,...>(myObject, &MyClass::HandleEvent, myArg1,...);
+// 'Types' are the stored type for each argument, see ParameterStorage for
+// details.
+template <typename... Storages, typename PtrType, typename Method,
+ typename... Args>
+already_AddRefed<detail::OwningRunnableMethod<PtrType, Method>>
+NewRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod,
+ Args&&... aArgs) {
+ static_assert(sizeof...(Storages) == sizeof...(Args),
+ "<Storages...> size should be equal to number of arguments");
+ return do_AddRef(
+ new detail::OwningRunnableMethodImpl<PtrType, Method, Storages...>(
+ aName, std::forward<PtrType>(aPtr), aMethod,
+ std::forward<Args>(aArgs)...));
+}
+
+template <typename... Storages, typename PtrType, typename Method,
+ typename... Args>
+already_AddRefed<detail::NonOwningRunnableMethod<PtrType, Method>>
+NewNonOwningRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod,
+ Args&&... aArgs) {
+ static_assert(sizeof...(Storages) == sizeof...(Args),
+ "<Storages...> size should be equal to number of arguments");
+ return do_AddRef(
+ new detail::NonOwningRunnableMethodImpl<PtrType, Method, Storages...>(
+ aName, std::forward<PtrType>(aPtr), aMethod,
+ std::forward<Args>(aArgs)...));
+}
+
+template <typename... Storages, typename PtrType, typename Method,
+ typename... Args>
+already_AddRefed<detail::CancelableRunnableMethod<PtrType, Method>>
+NewCancelableRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod,
+ Args&&... aArgs) {
+ static_assert(sizeof...(Storages) == sizeof...(Args),
+ "<Storages...> size should be equal to number of arguments");
+ return do_AddRef(
+ new detail::CancelableRunnableMethodImpl<PtrType, Method, Storages...>(
+ aName, std::forward<PtrType>(aPtr), aMethod,
+ std::forward<Args>(aArgs)...));
+}
+
+template <typename... Storages, typename PtrType, typename Method,
+ typename... Args>
+already_AddRefed<detail::NonOwningCancelableRunnableMethod<PtrType, Method>>
+NewNonOwningCancelableRunnableMethod(const char* aName, PtrType&& aPtr,
+ Method aMethod, Args&&... aArgs) {
+ static_assert(sizeof...(Storages) == sizeof...(Args),
+ "<Storages...> size should be equal to number of arguments");
+ return do_AddRef(
+ new detail::NonOwningCancelableRunnableMethodImpl<PtrType, Method,
+ Storages...>(
+ aName, std::forward<PtrType>(aPtr), aMethod,
+ std::forward<Args>(aArgs)...));
+}
+
+template <typename... Storages, typename PtrType, typename Method,
+ typename... Args>
+already_AddRefed<detail::IdleRunnableMethod<PtrType, Method>>
+NewIdleRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod,
+ Args&&... aArgs) {
+ static_assert(sizeof...(Storages) == sizeof...(Args),
+ "<Storages...> size should be equal to number of arguments");
+ return do_AddRef(
+ new detail::IdleRunnableMethodImpl<PtrType, Method, Storages...>(
+ aName, std::forward<PtrType>(aPtr), aMethod,
+ std::forward<Args>(aArgs)...));
+}
+
+template <typename... Storages, typename PtrType, typename Method,
+ typename... Args>
+already_AddRefed<detail::NonOwningIdleRunnableMethod<PtrType, Method>>
+NewNonOwningIdleRunnableMethod(const char* aName, PtrType&& aPtr,
+ Method aMethod, Args&&... aArgs) {
+ static_assert(sizeof...(Storages) == sizeof...(Args),
+ "<Storages...> size should be equal to number of arguments");
+ return do_AddRef(
+ new detail::NonOwningIdleRunnableMethodImpl<PtrType, Method, Storages...>(
+ aName, std::forward<PtrType>(aPtr), aMethod,
+ std::forward<Args>(aArgs)...));
+}
+
+} // namespace mozilla
+
+#endif // XPCOM_GLUE_AVOID_NSPR
+
+// This class is designed to be used when you have an event class E that has a
+// pointer back to resource class R. If R goes away while E is still pending,
+// then it is important to "revoke" E so that it does not try use R after R has
+// been destroyed. nsRevocableEventPtr makes it easy for R to manage such
+// situations:
+//
+// class R;
+//
+// class E : public mozilla::Runnable {
+// public:
+// void Revoke() {
+// mResource = nullptr;
+// }
+// private:
+// R *mResource;
+// };
+//
+// class R {
+// public:
+// void EventHandled() {
+// mEvent.Forget();
+// }
+// private:
+// nsRevocableEventPtr<E> mEvent;
+// };
+//
+// void R::PostEvent() {
+// // Make sure any pending event is revoked.
+// mEvent->Revoke();
+//
+// nsCOMPtr<nsIRunnable> event = new E();
+// if (NS_SUCCEEDED(NS_DispatchToCurrentThread(event))) {
+// // Keep pointer to event so we can revoke it.
+// mEvent = event;
+// }
+// }
+//
+// NS_IMETHODIMP E::Run() {
+// if (!mResource)
+// return NS_OK;
+// ...
+// mResource->EventHandled();
+// return NS_OK;
+// }
+//
+template <class T>
+class nsRevocableEventPtr {
+ public:
+ nsRevocableEventPtr() : mEvent(nullptr) {}
+ ~nsRevocableEventPtr() { Revoke(); }
+
+ const nsRevocableEventPtr& operator=(RefPtr<T>&& aEvent) {
+ if (mEvent != aEvent) {
+ Revoke();
+ mEvent = std::move(aEvent);
+ }
+ return *this;
+ }
+
+ void Revoke() {
+ if (mEvent) {
+ mEvent->Revoke();
+ mEvent = nullptr;
+ }
+ }
+
+ void Forget() { mEvent = nullptr; }
+ bool IsPending() { return mEvent != nullptr; }
+ T* get() { return mEvent; }
+
+ private:
+ // Not implemented
+ nsRevocableEventPtr(const nsRevocableEventPtr&);
+ nsRevocableEventPtr& operator=(const nsRevocableEventPtr&);
+
+ RefPtr<T> mEvent;
+};
+
+template <class T>
+inline already_AddRefed<T> do_AddRef(nsRevocableEventPtr<T>& aObj) {
+ return do_AddRef(aObj.get());
+}
+
+/**
+ * A simple helper to suffix thread pool name
+ * with incremental numbers.
+ */
+class nsThreadPoolNaming {
+ public:
+ nsThreadPoolNaming() = default;
+
+ /**
+ * Returns a thread name as "<aPoolName> #<n>" and increments the counter.
+ */
+ nsCString GetNextThreadName(const nsACString& aPoolName);
+
+ template <size_t LEN>
+ nsCString GetNextThreadName(const char (&aPoolName)[LEN]) {
+ return GetNextThreadName(nsDependentCString(aPoolName, LEN - 1));
+ }
+
+ private:
+ mozilla::Atomic<uint32_t> mCounter{0};
+
+ nsThreadPoolNaming(const nsThreadPoolNaming&) = delete;
+ void operator=(const nsThreadPoolNaming&) = delete;
+};
+
+/**
+ * Thread priority in most operating systems affect scheduling, not IO. This
+ * helper is used to set the current thread to low IO priority for the lifetime
+ * of the created object. You can only use this low priority IO setting within
+ * the context of the current thread.
+ */
+class MOZ_STACK_CLASS nsAutoLowPriorityIO {
+ public:
+ nsAutoLowPriorityIO();
+ ~nsAutoLowPriorityIO();
+
+ private:
+ bool lowIOPrioritySet;
+#if defined(XP_MACOSX)
+ int oldPriority;
+#endif
+};
+
+void NS_SetMainThread();
+
+// Used only on cooperatively scheduled "main" threads. Causes the thread to be
+// considered a main thread and also causes GetCurrentVirtualThread to return
+// aVirtualThread.
+void NS_SetMainThread(PRThread* aVirtualThread);
+
+// Used only on cooperatively scheduled "main" threads. Causes the thread to no
+// longer be considered a main thread. Also causes GetCurrentVirtualThread() to
+// return a unique value.
+void NS_UnsetMainThread();
+
+/**
+ * Return the expiration time of the next timer to run on the current
+ * thread. If that expiration time is greater than aDefault, then
+ * return aDefault. aSearchBound specifies a maximum number of timers
+ * to examine to find a timer on the current thread. If no timer that
+ * will run on the current thread is found after examining
+ * aSearchBound timers, return the highest seen expiration time as a
+ * best effort guess.
+ *
+ * Timers with either the type nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY or
+ * nsITIMER::TYPE_REPEATING_SLACK_LOW_PRIORITY will be skipped when
+ * searching for the next expiration time. This enables timers to
+ * have lower priority than callbacks dispatched from
+ * nsIThread::IdleDispatch.
+ */
+extern mozilla::TimeStamp NS_GetTimerDeadlineHintOnCurrentThread(
+ mozilla::TimeStamp aDefault, uint32_t aSearchBound);
+
+/**
+ * Dispatches the given event to a background thread. The primary benefit of
+ * this API is that you do not have to manage the lifetime of your own thread
+ * for running your own events; the thread manager will take care of the
+ * background thread's lifetime. Not having to manage your own thread also
+ * means less resource usage, as the underlying implementation here can manage
+ * spinning up and shutting down threads appropriately.
+ *
+ * NOTE: there is no guarantee that events dispatched via these APIs are run
+ * serially, in dispatch order; several dispatched events may run in parallel.
+ * If you depend on serial execution of dispatched events, you should use
+ * NS_CreateBackgroundTaskQueue instead, and dispatch events to the returned
+ * event target.
+ */
+extern nsresult NS_DispatchBackgroundTask(
+ already_AddRefed<nsIRunnable> aEvent,
+ uint32_t aDispatchFlags = NS_DISPATCH_NORMAL);
+extern "C" nsresult NS_DispatchBackgroundTask(
+ nsIRunnable* aEvent, uint32_t aDispatchFlags = NS_DISPATCH_NORMAL);
+
+/**
+ * Obtain a new serial event target that dispatches runnables to a background
+ * thread. In many cases, this is a straight replacement for creating your
+ * own, private thread, and is generally preferred to creating your own,
+ * private thread.
+ */
+extern "C" nsresult NS_CreateBackgroundTaskQueue(
+ const char* aName, nsISerialEventTarget** aTarget);
+
+// Predeclaration for logging function below
+namespace IPC {
+class Message;
+}
+
+class nsTimerImpl;
+
+namespace mozilla {
+
+// RAII class that will set the TLS entry to return the currently running
+// nsISerialEventTarget.
+// It should be used from inner event loop implementation.
+class SerialEventTargetGuard {
+ public:
+ explicit SerialEventTargetGuard(nsISerialEventTarget* aThread)
+ : mLastCurrentThread(sCurrentThreadTLS.get()) {
+ Set(aThread);
+ }
+
+ ~SerialEventTargetGuard() { sCurrentThreadTLS.set(mLastCurrentThread); }
+
+ static void InitTLS();
+ static nsISerialEventTarget* GetCurrentSerialEventTarget() {
+ return sCurrentThreadTLS.get();
+ }
+
+ protected:
+ friend class ::MessageLoop;
+ static void Set(nsISerialEventTarget* aThread) {
+ MOZ_ASSERT(aThread->IsOnCurrentThread());
+ sCurrentThreadTLS.set(aThread);
+ }
+
+ private:
+ static MOZ_THREAD_LOCAL(nsISerialEventTarget*) sCurrentThreadTLS;
+ nsISerialEventTarget* mLastCurrentThread;
+};
+
+// These functions return event targets that can be used to dispatch to the
+// current or main thread. They can also be used to test if you're on those
+// threads (via IsOnCurrentThread). These functions should be used in preference
+// to the nsIThread-based NS_Get{Current,Main}Thread functions since they will
+// return more useful answers in the case of threads sharing an event loop.
+
+nsIEventTarget* GetCurrentEventTarget();
+
+nsIEventTarget* GetMainThreadEventTarget();
+
+// These variants of the above functions assert that the given thread has a
+// serial event target (i.e., that it's not part of a thread pool) and returns
+// that.
+
+nsISerialEventTarget* GetCurrentSerialEventTarget();
+
+nsISerialEventTarget* GetMainThreadSerialEventTarget();
+
+// Returns a wrapper around the current thread which routes normal dispatches
+// through the tail dispatcher.
+// This means that they will run at the end of the current task, rather than
+// after all the subsequent tasks queued. This is useful to allow MozPromise
+// callbacks returned by IPDL methods to avoid an extra trip through the event
+// loop, and thus maintain correct ordering relative to other IPC events. The
+// current thread implementation must support tail dispatch.
+class TailDispatchingTarget : public nsISerialEventTarget {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ TailDispatchingTarget()
+#if DEBUG
+ : mOwnerThread(AbstractThread::GetCurrent())
+#endif
+ {
+ MOZ_ASSERT(mOwnerThread, "Must be used with AbstractThreads");
+ }
+
+ NS_IMETHOD
+ Dispatch(already_AddRefed<nsIRunnable> event, uint32_t flags) override {
+ MOZ_ASSERT(flags == DISPATCH_NORMAL);
+ MOZ_ASSERT(
+ AbstractThread::GetCurrent() == mOwnerThread,
+ "TailDispatchingTarget can only be used on the thread upon which it "
+ "was created - see the comment on the class declaration.");
+ AbstractThread::DispatchDirectTask(std::move(event));
+ return NS_OK;
+ }
+ NS_IMETHOD_(bool) IsOnCurrentThreadInfallible(void) override { return true; }
+ NS_IMETHOD IsOnCurrentThread(bool* _retval) override {
+ *_retval = true;
+ return NS_OK;
+ }
+ NS_IMETHOD DispatchFromScript(nsIRunnable* event, uint32_t flags) override {
+ MOZ_ASSERT_UNREACHABLE("not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ NS_IMETHOD DelayedDispatch(already_AddRefed<nsIRunnable> event,
+ uint32_t delay) override {
+ MOZ_ASSERT_UNREACHABLE("not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ private:
+ virtual ~TailDispatchingTarget() = default;
+#if DEBUG
+ const RefPtr<AbstractThread> mOwnerThread;
+#endif
+};
+
+// Returns the number of CPUs, like PR_GetNumberOfProcessors, except
+// that it can return a cached value on platforms where sandboxing
+// would prevent reading the current value (currently Linux). CPU
+// hotplugging is uncommon, so this is unlikely to make a difference
+// in practice.
+size_t GetNumberOfProcessors();
+
+/**
+ * A helper class to log tasks dispatch and run with "MOZ_LOG=events:1". The
+ * output is more machine readable and creates a link between dispatch and run.
+ *
+ * Usage example for the concrete template type nsIRunnable.
+ * To log a dispatch, which means putting an event to a queue:
+ * LogRunnable::LogDispatch(event);
+ * theQueue.putEvent(event);
+ *
+ * To log execution (running) of the event:
+ * nsCOMPtr<nsIRunnable> event = theQueue.popEvent();
+ * {
+ * LogRunnable::Run log(event);
+ * event->Run();
+ * event = null; // to include the destructor code in the span
+ * }
+ *
+ * The class is a template so that we can support various specific super-types
+ * of tasks in the future. We can't use void* because it may cast differently
+ * and tracking the pointer in logs would then be impossible.
+ */
+template <typename T>
+class LogTaskBase {
+ public:
+ LogTaskBase() = delete;
+
+ // Adds a simple log about dispatch of this runnable.
+ static void LogDispatch(T* aEvent);
+ // The `aContext` pointer adds another uniqe identifier, nothing more
+ static void LogDispatch(T* aEvent, void* aContext);
+
+ // Logs dispatch of the message and along that also the PID of the target
+ // proccess, purposed for uniquely identifying IPC messages.
+ static void LogDispatchWithPid(T* aEvent, int32_t aPid);
+
+ // This is designed to surround a call to `Run()` or any code representing
+ // execution of the task body.
+ // The constructor adds a simple log about start of the runnable execution and
+ // the destructor adds a log about ending the execution.
+ class MOZ_RAII Run {
+ public:
+ Run() = delete;
+ explicit Run(T* aEvent, bool aWillRunAgain = false);
+ explicit Run(T* aEvent, void* aContext, bool aWillRunAgain = false);
+ ~Run();
+
+ // When this is called, the log in this RAII dtor will only say
+ // "interrupted" expecting that the event will run again.
+ void WillRunAgain() { mWillRunAgain = true; }
+
+ private:
+ bool mWillRunAgain = false;
+ };
+};
+
+class MicroTaskRunnable;
+class Task; // TaskController
+class PresShell;
+namespace dom {
+class FrameRequestCallback;
+} // namespace dom
+
+// Specialized methods must be explicitly predeclared.
+template <>
+LogTaskBase<nsIRunnable>::Run::Run(nsIRunnable* aEvent, bool aWillRunAgain);
+template <>
+LogTaskBase<Task>::Run::Run(Task* aTask, bool aWillRunAgain);
+template <>
+void LogTaskBase<IPC::Message>::LogDispatchWithPid(IPC::Message* aEvent,
+ int32_t aPid);
+template <>
+LogTaskBase<IPC::Message>::Run::Run(IPC::Message* aMessage, bool aWillRunAgain);
+template <>
+LogTaskBase<nsTimerImpl>::Run::Run(nsTimerImpl* aEvent, bool aWillRunAgain);
+
+typedef LogTaskBase<nsIRunnable> LogRunnable;
+typedef LogTaskBase<MicroTaskRunnable> LogMicroTaskRunnable;
+typedef LogTaskBase<IPC::Message> LogIPCMessage;
+typedef LogTaskBase<nsTimerImpl> LogTimerEvent;
+typedef LogTaskBase<Task> LogTask;
+typedef LogTaskBase<PresShell> LogPresShellObserver;
+typedef LogTaskBase<dom::FrameRequestCallback> LogFrameRequestCallback;
+// If you add new types don't forget to add:
+// `template class LogTaskBase<YourType>;` to nsThreadUtils.cpp
+
+class DelayedRunnable : public mozilla::Runnable, public nsITimerCallback {
+ public:
+ DelayedRunnable(already_AddRefed<nsIEventTarget> aTarget,
+ already_AddRefed<nsIRunnable> aRunnable, uint32_t aDelay);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSITIMERCALLBACK
+
+ nsresult Init();
+
+ private:
+ ~DelayedRunnable() = default;
+ nsresult DoRun();
+
+ const nsCOMPtr<nsIEventTarget> mTarget;
+ nsCOMPtr<nsIRunnable> mWrappedRunnable;
+ nsCOMPtr<nsITimer> mTimer;
+ const mozilla::TimeStamp mDelayedFrom;
+ uint32_t mDelay;
+};
+
+} // namespace mozilla
+
+#endif // nsThreadUtils_h__
diff --git a/xpcom/threads/nsTimerImpl.cpp b/xpcom/threads/nsTimerImpl.cpp
new file mode 100644
index 0000000000..5e9061c9bd
--- /dev/null
+++ b/xpcom/threads/nsTimerImpl.cpp
@@ -0,0 +1,804 @@
+/* -*- 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 "nsTimerImpl.h"
+
+#include <utility>
+
+#include "GeckoProfiler.h"
+#include "TimerThread.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/Sprintf.h"
+#include "nsThreadManager.h"
+#include "nsThreadUtils.h"
+#include "pratom.h"
+#ifdef MOZ_TASK_TRACER
+# include "GeckoTaskTracerImpl.h"
+using namespace mozilla::tasktracer;
+#endif
+
+#ifdef XP_WIN
+# include <process.h>
+# ifndef getpid
+# define getpid _getpid
+# endif
+#else
+# include <unistd.h>
+#endif
+
+using mozilla::Atomic;
+using mozilla::LogLevel;
+using mozilla::MakeRefPtr;
+using mozilla::MutexAutoLock;
+using mozilla::TimeDuration;
+using mozilla::TimeStamp;
+
+static TimerThread* gThread = nullptr;
+
+// This module prints info about the precision of timers.
+static mozilla::LazyLogModule sTimerLog("nsTimerImpl");
+
+mozilla::LogModule* GetTimerLog() { return sTimerLog; }
+
+TimeStamp NS_GetTimerDeadlineHintOnCurrentThread(TimeStamp aDefault,
+ uint32_t aSearchBound) {
+ return gThread
+ ? gThread->FindNextFireTimeForCurrentThread(aDefault, aSearchBound)
+ : TimeStamp();
+}
+
+already_AddRefed<nsITimer> NS_NewTimer() { return NS_NewTimer(nullptr); }
+
+already_AddRefed<nsITimer> NS_NewTimer(nsIEventTarget* aTarget) {
+ return nsTimer::WithEventTarget(aTarget).forget();
+}
+
+mozilla::Result<nsCOMPtr<nsITimer>, nsresult> NS_NewTimerWithObserver(
+ nsIObserver* aObserver, uint32_t aDelay, uint32_t aType,
+ nsIEventTarget* aTarget) {
+ nsCOMPtr<nsITimer> timer;
+ MOZ_TRY(NS_NewTimerWithObserver(getter_AddRefs(timer), aObserver, aDelay,
+ aType, aTarget));
+ return std::move(timer);
+}
+nsresult NS_NewTimerWithObserver(nsITimer** aTimer, nsIObserver* aObserver,
+ uint32_t aDelay, uint32_t aType,
+ nsIEventTarget* aTarget) {
+ auto timer = nsTimer::WithEventTarget(aTarget);
+
+ MOZ_TRY(timer->Init(aObserver, aDelay, aType));
+ timer.forget(aTimer);
+ return NS_OK;
+}
+
+mozilla::Result<nsCOMPtr<nsITimer>, nsresult> NS_NewTimerWithCallback(
+ nsITimerCallback* aCallback, uint32_t aDelay, uint32_t aType,
+ nsIEventTarget* aTarget) {
+ nsCOMPtr<nsITimer> timer;
+ MOZ_TRY(NS_NewTimerWithCallback(getter_AddRefs(timer), aCallback, aDelay,
+ aType, aTarget));
+ return std::move(timer);
+}
+nsresult NS_NewTimerWithCallback(nsITimer** aTimer, nsITimerCallback* aCallback,
+ uint32_t aDelay, uint32_t aType,
+ nsIEventTarget* aTarget) {
+ auto timer = nsTimer::WithEventTarget(aTarget);
+
+ MOZ_TRY(timer->InitWithCallback(aCallback, aDelay, aType));
+ timer.forget(aTimer);
+ return NS_OK;
+}
+
+mozilla::Result<nsCOMPtr<nsITimer>, nsresult> NS_NewTimerWithCallback(
+ nsITimerCallback* aCallback, const TimeDuration& aDelay, uint32_t aType,
+ nsIEventTarget* aTarget) {
+ nsCOMPtr<nsITimer> timer;
+ MOZ_TRY(NS_NewTimerWithCallback(getter_AddRefs(timer), aCallback, aDelay,
+ aType, aTarget));
+ return std::move(timer);
+}
+nsresult NS_NewTimerWithCallback(nsITimer** aTimer, nsITimerCallback* aCallback,
+ const TimeDuration& aDelay, uint32_t aType,
+ nsIEventTarget* aTarget) {
+ auto timer = nsTimer::WithEventTarget(aTarget);
+
+ MOZ_TRY(timer->InitHighResolutionWithCallback(aCallback, aDelay, aType));
+ timer.forget(aTimer);
+ return NS_OK;
+}
+
+mozilla::Result<nsCOMPtr<nsITimer>, nsresult> NS_NewTimerWithFuncCallback(
+ nsTimerCallbackFunc aCallback, void* aClosure, uint32_t aDelay,
+ uint32_t aType, const char* aNameString, nsIEventTarget* aTarget) {
+ nsCOMPtr<nsITimer> timer;
+ MOZ_TRY(NS_NewTimerWithFuncCallback(getter_AddRefs(timer), aCallback,
+ aClosure, aDelay, aType, aNameString,
+ aTarget));
+ return std::move(timer);
+}
+nsresult NS_NewTimerWithFuncCallback(nsITimer** aTimer,
+ nsTimerCallbackFunc aCallback,
+ void* aClosure, uint32_t aDelay,
+ uint32_t aType, const char* aNameString,
+ nsIEventTarget* aTarget) {
+ auto timer = nsTimer::WithEventTarget(aTarget);
+
+ MOZ_TRY(timer->InitWithNamedFuncCallback(aCallback, aClosure, aDelay, aType,
+ aNameString));
+ timer.forget(aTimer);
+ return NS_OK;
+}
+
+mozilla::Result<nsCOMPtr<nsITimer>, nsresult> NS_NewTimerWithFuncCallback(
+ nsTimerCallbackFunc aCallback, void* aClosure, uint32_t aDelay,
+ uint32_t aType, nsTimerNameCallbackFunc aNameCallback,
+ nsIEventTarget* aTarget) {
+ nsCOMPtr<nsITimer> timer;
+ MOZ_TRY(NS_NewTimerWithFuncCallback(getter_AddRefs(timer), aCallback,
+ aClosure, aDelay, aType, aNameCallback,
+ aTarget));
+ return std::move(timer);
+}
+nsresult NS_NewTimerWithFuncCallback(nsITimer** aTimer,
+ nsTimerCallbackFunc aCallback,
+ void* aClosure, uint32_t aDelay,
+ uint32_t aType,
+ nsTimerNameCallbackFunc aNameCallback,
+ nsIEventTarget* aTarget) {
+ auto timer = nsTimer::WithEventTarget(aTarget);
+
+ MOZ_TRY(timer->InitWithNameableFuncCallback(aCallback, aClosure, aDelay,
+ aType, aNameCallback));
+ timer.forget(aTimer);
+ return NS_OK;
+}
+
+// This module prints info about which timers are firing, which is useful for
+// wakeups for the purposes of power profiling. Set the following environment
+// variable before starting the browser.
+//
+// MOZ_LOG=TimerFirings:4
+//
+// Then a line will be printed for every timer that fires. The name used for a
+// |Callback::Type::Function| timer depends on the circumstances.
+//
+// - If it was explicitly named (e.g. it was initialized with
+// InitWithNamedFuncCallback()) then that explicit name will be shown.
+//
+// - Otherwise, if we are on a platform that supports function name lookup
+// (Mac or Linux) then the looked-up name will be shown with a
+// "[from dladdr]" annotation. On Mac the looked-up name will be immediately
+// useful. On Linux it'll need post-processing with `tools/rb/fix_stacks.py`.
+//
+// - Otherwise, no name will be printed. If many timers hit this case then
+// you'll need to re-run the workload on a Mac to find out which timers they
+// are, and then give them explicit names.
+//
+// If you redirect this output to a file called "out", you can then
+// post-process it with a command something like the following.
+//
+// cat out | grep timer | sort | uniq -c | sort -r -n
+//
+// This will show how often each unique line appears, with the most common ones
+// first.
+//
+// More detailed docs are here:
+// https://developer.mozilla.org/en-US/docs/Mozilla/Performance/TimerFirings_logging
+//
+static mozilla::LazyLogModule sTimerFiringsLog("TimerFirings");
+
+static mozilla::LogModule* GetTimerFiringsLog() { return sTimerFiringsLog; }
+
+#include <math.h>
+
+double nsTimerImpl::sDeltaSumSquared = 0;
+double nsTimerImpl::sDeltaSum = 0;
+double nsTimerImpl::sDeltaNum = 0;
+
+static void myNS_MeanAndStdDev(double n, double sumOfValues,
+ double sumOfSquaredValues, 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) {
+ var = 0.0;
+ } 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;
+ }
+ *meanResult = mean;
+ *stdDevResult = stdDev;
+}
+
+NS_IMPL_QUERY_INTERFACE(nsTimer, nsITimer)
+NS_IMPL_ADDREF(nsTimer)
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsTimer::Release(void) {
+ nsrefcnt count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "nsTimer");
+
+ if (count == 1) {
+ // Last ref, in nsTimerImpl::mITimer. Make sure the cycle is broken.
+ mImpl->CancelImpl(true);
+ } else if (count == 0) {
+ delete this;
+ }
+
+ return count;
+}
+
+nsTimerImpl::nsTimerImpl(nsITimer* aTimer, nsIEventTarget* aTarget)
+ : mEventTarget(aTarget),
+ mHolder(nullptr),
+ mType(0),
+ mGeneration(0),
+ mITimer(aTimer),
+ mMutex("nsTimerImpl::mMutex"),
+ mFiring(0) {
+ // XXX some code creates timers during xpcom shutdown, when threads are no
+ // longer available, so we cannot turn this on yet.
+ // MOZ_ASSERT(mEventTarget);
+}
+
+// static
+nsresult nsTimerImpl::Startup() {
+ nsresult rv;
+
+ gThread = new TimerThread();
+
+ NS_ADDREF(gThread);
+ rv = gThread->InitLocks();
+
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(gThread);
+ }
+
+ return rv;
+}
+
+void nsTimerImpl::Shutdown() {
+ if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) {
+ double mean = 0, stddev = 0;
+ myNS_MeanAndStdDev(sDeltaNum, sDeltaSum, sDeltaSumSquared, &mean, &stddev);
+
+ MOZ_LOG(GetTimerLog(), LogLevel::Debug,
+ ("sDeltaNum = %f, sDeltaSum = %f, sDeltaSumSquared = %f\n",
+ sDeltaNum, sDeltaSum, sDeltaSumSquared));
+ MOZ_LOG(GetTimerLog(), LogLevel::Debug,
+ ("mean: %fms, stddev: %fms\n", mean, stddev));
+ }
+
+ if (!gThread) {
+ return;
+ }
+
+ gThread->Shutdown();
+ NS_RELEASE(gThread);
+}
+
+nsresult nsTimerImpl::InitCommon(uint32_t aDelayMS, uint32_t aType,
+ Callback&& aNewCallback) {
+ return InitCommon(TimeDuration::FromMilliseconds(aDelayMS), aType,
+ std::move(aNewCallback));
+}
+
+nsresult nsTimerImpl::InitCommon(const TimeDuration& aDelay, uint32_t aType,
+ Callback&& newCallback) {
+ mMutex.AssertCurrentThreadOwns();
+
+ if (!gThread) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!mEventTarget) {
+ NS_ERROR("mEventTarget is NULL");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ gThread->RemoveTimer(this);
+ mCallback.swap(newCallback);
+ ++mGeneration;
+
+ mType = (uint8_t)aType;
+ mDelay = aDelay;
+ mTimeout = TimeStamp::Now() + mDelay;
+
+ return gThread->AddTimer(this);
+}
+
+nsresult nsTimerImpl::InitWithFuncCallbackCommon(nsTimerCallbackFunc aFunc,
+ void* aClosure,
+ uint32_t aDelay,
+ uint32_t aType,
+ const Callback::Name& aName) {
+ if (NS_WARN_IF(!aFunc)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ Callback cb; // Goes out of scope after the unlock, prevents deadlock
+ cb.mType = Callback::Type::Function;
+ cb.mCallback.c = aFunc;
+ cb.mClosure = aClosure;
+ cb.mName = aName;
+
+ MutexAutoLock lock(mMutex);
+ return InitCommon(aDelay, aType, std::move(cb));
+}
+
+nsresult nsTimerImpl::InitWithNamedFuncCallback(nsTimerCallbackFunc aFunc,
+ void* aClosure, uint32_t aDelay,
+ uint32_t aType,
+ const char* aNameString) {
+ Callback::Name name(aNameString);
+ return InitWithFuncCallbackCommon(aFunc, aClosure, aDelay, aType, name);
+}
+
+nsresult nsTimerImpl::InitWithNameableFuncCallback(
+ nsTimerCallbackFunc aFunc, void* aClosure, uint32_t aDelay, uint32_t aType,
+ nsTimerNameCallbackFunc aNameFunc) {
+ Callback::Name name(aNameFunc);
+ return InitWithFuncCallbackCommon(aFunc, aClosure, aDelay, aType, name);
+}
+
+nsresult nsTimerImpl::InitWithCallback(nsITimerCallback* aCallback,
+ uint32_t aDelayInMs, uint32_t aType) {
+ return InitHighResolutionWithCallback(
+ aCallback, TimeDuration::FromMilliseconds(aDelayInMs), aType);
+}
+
+nsresult nsTimerImpl::InitHighResolutionWithCallback(
+ nsITimerCallback* aCallback, const TimeDuration& aDelay, uint32_t aType) {
+ if (NS_WARN_IF(!aCallback)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ Callback cb; // Goes out of scope after the unlock, prevents deadlock
+ cb.mType = Callback::Type::Interface;
+ cb.mCallback.i = aCallback;
+ NS_ADDREF(cb.mCallback.i);
+
+ MutexAutoLock lock(mMutex);
+ return InitCommon(aDelay, aType, std::move(cb));
+}
+
+nsresult nsTimerImpl::Init(nsIObserver* aObserver, uint32_t aDelayInMs,
+ uint32_t aType) {
+ if (NS_WARN_IF(!aObserver)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ Callback cb; // Goes out of scope after the unlock, prevents deadlock
+ cb.mType = Callback::Type::Observer;
+ cb.mCallback.o = aObserver;
+ NS_ADDREF(cb.mCallback.o);
+
+ MutexAutoLock lock(mMutex);
+ return InitCommon(aDelayInMs, aType, std::move(cb));
+}
+
+nsresult nsTimerImpl::Cancel() {
+ CancelImpl(false);
+ return NS_OK;
+}
+
+void nsTimerImpl::CancelImpl(bool aClearITimer) {
+ Callback cbTrash;
+ RefPtr<nsITimer> timerTrash;
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (gThread) {
+ gThread->RemoveTimer(this);
+ }
+
+ cbTrash.swap(mCallback);
+ ++mGeneration;
+
+ // Don't clear this if we're firing; once Fire returns, we'll get this call
+ // again.
+ if (aClearITimer && !mFiring) {
+ MOZ_RELEASE_ASSERT(
+ mITimer,
+ "mITimer was nulled already! "
+ "This indicates that someone has messed up the refcount on nsTimer!");
+ timerTrash.swap(mITimer);
+ }
+ }
+}
+
+nsresult nsTimerImpl::SetDelay(uint32_t aDelay) {
+ MutexAutoLock lock(mMutex);
+ if (GetCallback().mType == Callback::Type::Unknown && !IsRepeating()) {
+ // This may happen if someone tries to re-use a one-shot timer
+ // by re-setting delay instead of reinitializing the timer.
+ NS_ERROR(
+ "nsITimer->SetDelay() called when the "
+ "one-shot timer is not set up.");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ bool reAdd = false;
+ if (gThread) {
+ reAdd = NS_SUCCEEDED(gThread->RemoveTimer(this));
+ }
+
+ mDelay = TimeDuration::FromMilliseconds(aDelay);
+ mTimeout = TimeStamp::Now() + mDelay;
+
+ if (reAdd) {
+ gThread->AddTimer(this);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsTimerImpl::GetDelay(uint32_t* aDelay) {
+ MutexAutoLock lock(mMutex);
+ *aDelay = mDelay.ToMilliseconds();
+ return NS_OK;
+}
+
+nsresult nsTimerImpl::SetType(uint32_t aType) {
+ MutexAutoLock lock(mMutex);
+ mType = (uint8_t)aType;
+ // XXX if this is called, we should change the actual type.. this could effect
+ // repeating timers. we need to ensure in Fire() that if mType has changed
+ // during the callback that we don't end up with the timer in the queue twice.
+ return NS_OK;
+}
+
+nsresult nsTimerImpl::GetType(uint32_t* aType) {
+ MutexAutoLock lock(mMutex);
+ *aType = mType;
+ return NS_OK;
+}
+
+nsresult nsTimerImpl::GetClosure(void** aClosure) {
+ MutexAutoLock lock(mMutex);
+ *aClosure = GetCallback().mClosure;
+ return NS_OK;
+}
+
+nsresult nsTimerImpl::GetCallback(nsITimerCallback** aCallback) {
+ MutexAutoLock lock(mMutex);
+ if (GetCallback().mType == Callback::Type::Interface) {
+ NS_IF_ADDREF(*aCallback = GetCallback().mCallback.i);
+ } else {
+ *aCallback = nullptr;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsTimerImpl::GetTarget(nsIEventTarget** aTarget) {
+ MutexAutoLock lock(mMutex);
+ NS_IF_ADDREF(*aTarget = mEventTarget);
+ return NS_OK;
+}
+
+nsresult nsTimerImpl::SetTarget(nsIEventTarget* aTarget) {
+ MutexAutoLock lock(mMutex);
+ if (NS_WARN_IF(mCallback.mType != Callback::Type::Unknown)) {
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+
+ if (aTarget) {
+ mEventTarget = aTarget;
+ } else {
+ mEventTarget = mozilla::GetCurrentSerialEventTarget();
+ }
+ return NS_OK;
+}
+
+nsresult nsTimerImpl::GetAllowedEarlyFiringMicroseconds(uint32_t* aValueOut) {
+ *aValueOut = gThread ? gThread->AllowedEarlyFiringMicroseconds() : 0;
+ return NS_OK;
+}
+
+void nsTimerImpl::Fire(int32_t aGeneration) {
+ uint8_t oldType;
+ uint32_t oldDelay;
+ TimeStamp oldTimeout;
+ Callback callbackDuringFire;
+ nsCOMPtr<nsITimer> kungFuDeathGrip;
+
+ {
+ // Don't fire callbacks or fiddle with refcounts when the mutex is locked.
+ // If some other thread Cancels/Inits after this, they're just too late.
+ MutexAutoLock lock(mMutex);
+ if (aGeneration != mGeneration) {
+ return;
+ }
+
+ ++mFiring;
+ callbackDuringFire = mCallback;
+ oldType = mType;
+ oldDelay = mDelay.ToMilliseconds();
+ oldTimeout = mTimeout;
+ // Ensure that the nsITimer does not unhook from the nsTimerImpl during
+ // Fire; this will cause null pointer crashes if the user of the timer drops
+ // its reference, and then uses the nsITimer* passed in the callback.
+ kungFuDeathGrip = mITimer;
+ }
+
+ AUTO_PROFILER_LABEL("nsTimerImpl::Fire", OTHER);
+
+ TimeStamp now = TimeStamp::Now();
+ if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) {
+ TimeDuration delta = now - oldTimeout;
+ int32_t d = delta.ToMilliseconds(); // delta in ms
+ sDeltaSum += abs(d);
+ sDeltaSumSquared += double(d) * double(d);
+ sDeltaNum++;
+
+ MOZ_LOG(GetTimerLog(), LogLevel::Debug,
+ ("[this=%p] expected delay time %4ums\n", this, oldDelay));
+ MOZ_LOG(GetTimerLog(), LogLevel::Debug,
+ ("[this=%p] actual delay time %4dms\n", this, oldDelay + d));
+ MOZ_LOG(GetTimerLog(), LogLevel::Debug,
+ ("[this=%p] (mType is %d) -------\n", this, oldType));
+ MOZ_LOG(GetTimerLog(), LogLevel::Debug,
+ ("[this=%p] delta %4dms\n", this, d));
+ }
+
+ if (MOZ_LOG_TEST(GetTimerFiringsLog(), LogLevel::Debug)) {
+ LogFiring(callbackDuringFire, oldType, oldDelay);
+ }
+
+ switch (callbackDuringFire.mType) {
+ case Callback::Type::Function:
+ callbackDuringFire.mCallback.c(mITimer, callbackDuringFire.mClosure);
+ break;
+ case Callback::Type::Interface:
+ callbackDuringFire.mCallback.i->Notify(mITimer);
+ break;
+ case Callback::Type::Observer:
+ callbackDuringFire.mCallback.o->Observe(mITimer, NS_TIMER_CALLBACK_TOPIC,
+ nullptr);
+ break;
+ default:;
+ }
+
+ MutexAutoLock lock(mMutex);
+ if (aGeneration == mGeneration) {
+ if (IsRepeating()) {
+ // Repeating timer has not been re-init or canceled; reschedule
+ if (IsSlack()) {
+ mTimeout = TimeStamp::Now() + mDelay;
+ } else {
+ mTimeout = mTimeout + mDelay;
+ }
+ if (gThread) {
+ gThread->AddTimer(this);
+ }
+ } else {
+ // Non-repeating timer that has not been re-scheduled. Clear.
+ mCallback.clear();
+ }
+ }
+
+ --mFiring;
+
+ MOZ_LOG(GetTimerLog(), LogLevel::Debug,
+ ("[this=%p] Took %fms to fire timer callback\n", this,
+ (TimeStamp::Now() - now).ToMilliseconds()));
+}
+
+#if defined(HAVE_DLADDR) && defined(HAVE___CXA_DEMANGLE)
+# define USE_DLADDR 1
+#endif
+
+#ifdef USE_DLADDR
+# include <cxxabi.h>
+# include <dlfcn.h>
+#endif
+
+// See the big comment above GetTimerFiringsLog() to understand this code.
+void nsTimerImpl::LogFiring(const Callback& aCallback, uint8_t aType,
+ uint32_t aDelay) {
+ const char* typeStr;
+ switch (aType) {
+ case nsITimer::TYPE_ONE_SHOT:
+ typeStr = "ONE_SHOT ";
+ break;
+ case nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY:
+ typeStr = "ONE_LOW ";
+ break;
+ case nsITimer::TYPE_REPEATING_SLACK:
+ typeStr = "SLACK ";
+ break;
+ case nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY:
+ typeStr = "SLACK_LOW ";
+ break;
+ case nsITimer::TYPE_REPEATING_PRECISE: /* fall through */
+ case nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP:
+ typeStr = "PRECISE ";
+ break;
+ default:
+ MOZ_CRASH("bad type");
+ }
+
+ switch (aCallback.mType) {
+ case Callback::Type::Function: {
+ bool needToFreeName = false;
+ const char* annotation = "";
+ const char* name;
+ static const size_t buflen = 1024;
+ char buf[buflen];
+
+ if (aCallback.mName.is<Callback::NameString>()) {
+ name = aCallback.mName.as<Callback::NameString>();
+
+ } else if (aCallback.mName.is<Callback::NameFunc>()) {
+ aCallback.mName.as<Callback::NameFunc>()(
+ mITimer, /* aAnonymize = */ false, aCallback.mClosure, buf, buflen);
+ name = buf;
+
+ } else {
+ MOZ_ASSERT(aCallback.mName.is<Callback::NameNothing>());
+#ifdef USE_DLADDR
+ annotation = "[from dladdr] ";
+
+ Dl_info info;
+ void* addr = reinterpret_cast<void*>(aCallback.mCallback.c);
+ if (dladdr(addr, &info) == 0) {
+ name = "???[dladdr: failed]";
+
+ } else if (info.dli_sname) {
+ int status;
+ name = abi::__cxa_demangle(info.dli_sname, nullptr, nullptr, &status);
+ if (status == 0) {
+ // Success. Because we didn't pass in a buffer to __cxa_demangle it
+ // allocates its own one with malloc() which we must free() later.
+ MOZ_ASSERT(name);
+ needToFreeName = true;
+ } else if (status == -1) {
+ name = "???[__cxa_demangle: OOM]";
+ } else if (status == -2) {
+ name = "???[__cxa_demangle: invalid mangled name]";
+ } else if (status == -3) {
+ name = "???[__cxa_demangle: invalid argument]";
+ } else {
+ name = "???[__cxa_demangle: unexpected status value]";
+ }
+
+ } else if (info.dli_fname) {
+ // The "#0: " prefix is necessary for `fix_stacks.py` to interpret
+ // this string as something to convert.
+ SprintfLiteral(buf, "#0: ???[%s +0x%" PRIxPTR "]\n", info.dli_fname,
+ uintptr_t(addr) - uintptr_t(info.dli_fbase));
+ name = buf;
+
+ } else {
+ name = "???[dladdr: no symbol or shared object obtained]";
+ }
+#else
+ name = "???[dladdr is unimplemented or doesn't work well on this OS]";
+#endif
+ }
+
+ MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug,
+ ("[%d] fn timer (%s %5d ms): %s%s\n", getpid(), typeStr,
+ aDelay, annotation, name));
+
+ if (needToFreeName) {
+ free(const_cast<char*>(name));
+ }
+
+ break;
+ }
+
+ case Callback::Type::Interface: {
+ MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug,
+ ("[%d] iface timer (%s %5d ms): %p\n", getpid(), typeStr, aDelay,
+ aCallback.mCallback.i));
+ break;
+ }
+
+ case Callback::Type::Observer: {
+ MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug,
+ ("[%d] obs timer (%s %5d ms): %p\n", getpid(), typeStr, aDelay,
+ aCallback.mCallback.o));
+ break;
+ }
+
+ case Callback::Type::Unknown:
+ default: {
+ MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug,
+ ("[%d] ??? timer (%s, %5d ms)\n", getpid(), typeStr, aDelay));
+ break;
+ }
+ }
+}
+
+void nsTimerImpl::GetName(nsACString& aName) {
+ MutexAutoLock lock(mMutex);
+ Callback& cb(GetCallback());
+ switch (cb.mType) {
+ case Callback::Type::Function:
+ if (cb.mName.is<Callback::NameString>()) {
+ aName.Assign(cb.mName.as<Callback::NameString>());
+ } else if (cb.mName.is<Callback::NameFunc>()) {
+ static const size_t buflen = 1024;
+ char buf[buflen];
+ cb.mName.as<Callback::NameFunc>()(mITimer, /* aAnonymize = */ true,
+ cb.mClosure, buf, buflen);
+ aName.Assign(buf);
+ } else {
+ MOZ_ASSERT(cb.mName.is<Callback::NameNothing>());
+ aName.AssignLiteral("Anonymous_callback_timer");
+ }
+ break;
+
+ case Callback::Type::Interface:
+ if (nsCOMPtr<nsINamed> named = do_QueryInterface(cb.mCallback.i)) {
+ named->GetName(aName);
+ } else {
+ aName.AssignLiteral("Anonymous_interface_timer");
+ }
+ break;
+
+ case Callback::Type::Observer:
+ if (nsCOMPtr<nsINamed> named = do_QueryInterface(cb.mCallback.o)) {
+ named->GetName(aName);
+ } else {
+ aName.AssignLiteral("Anonymous_observer_timer");
+ }
+ break;
+
+ case Callback::Type::Unknown:
+ aName.AssignLiteral("Canceled_timer");
+ break;
+ }
+}
+
+void nsTimerImpl::SetHolder(nsTimerImplHolder* aHolder) { mHolder = aHolder; }
+
+nsTimer::~nsTimer() = default;
+
+size_t nsTimer::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this);
+}
+
+/* static */
+RefPtr<nsTimer> nsTimer::WithEventTarget(nsIEventTarget* aTarget) {
+ if (!aTarget) {
+ aTarget = mozilla::GetCurrentSerialEventTarget();
+ }
+ return do_AddRef(new nsTimer(aTarget));
+}
+
+/* static */
+nsresult nsTimer::XPCOMConstructor(nsISupports* aOuter, REFNSIID aIID,
+ void** aResult) {
+ *aResult = nullptr;
+ if (aOuter != nullptr) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+
+ auto timer = WithEventTarget(nullptr);
+
+ return timer->QueryInterface(aIID, aResult);
+}
+
+/* static */
+const nsTimerImpl::Callback::NameNothing nsTimerImpl::Callback::Nothing = 0;
+
+#ifdef MOZ_TASK_TRACER
+void nsTimerImpl::GetTLSTraceInfo() { mTracedTask.GetTLSTraceInfo(); }
+
+TracedTaskCommon nsTimerImpl::GetTracedTask() { return mTracedTask; }
+
+#endif
diff --git a/xpcom/threads/nsTimerImpl.h b/xpcom/threads/nsTimerImpl.h
new file mode 100644
index 0000000000..30bcc76ed3
--- /dev/null
+++ b/xpcom/threads/nsTimerImpl.h
@@ -0,0 +1,286 @@
+/* -*- 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___
+
+#include "nsITimer.h"
+#include "nsIEventTarget.h"
+#include "nsIObserver.h"
+
+#include "nsCOMPtr.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Variant.h"
+
+#ifdef MOZ_TASK_TRACER
+# include "TracedTaskCommon.h"
+#endif
+
+extern mozilla::LogModule* GetTimerLog();
+
+#define NS_TIMER_CID \
+ { /* 5ff24248-1dd2-11b2-8427-fbab44f29bc8 */ \
+ 0x5ff24248, 0x1dd2, 0x11b2, { \
+ 0x84, 0x27, 0xfb, 0xab, 0x44, 0xf2, 0x9b, 0xc8 \
+ } \
+ }
+
+class nsIObserver;
+class nsTimerImplHolder;
+
+namespace mozilla {
+class LogModule;
+}
+
+// TimerThread, nsTimerEvent, and nsTimer have references to these. nsTimer has
+// a separate lifecycle so we can Cancel() the underlying timer when the user of
+// the nsTimer has let go of its last reference.
+class nsTimerImpl {
+ ~nsTimerImpl() { MOZ_ASSERT(!mHolder); }
+
+ public:
+ typedef mozilla::TimeStamp TimeStamp;
+
+ nsTimerImpl(nsITimer* aTimer, nsIEventTarget* aTarget);
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsTimerImpl)
+ NS_DECL_NON_VIRTUAL_NSITIMER
+
+ static nsresult Startup();
+ static void Shutdown();
+
+ void SetDelayInternal(uint32_t aDelay, TimeStamp aBase = TimeStamp::Now());
+ void CancelImpl(bool aClearITimer);
+
+ void Fire(int32_t aGeneration);
+
+#ifdef MOZ_TASK_TRACER
+ void GetTLSTraceInfo();
+ mozilla::tasktracer::TracedTaskCommon GetTracedTask();
+#endif
+
+ int32_t GetGeneration() { return mGeneration; }
+
+ struct Callback {
+ Callback() : mType(Type::Unknown), mName(Nothing), mClosure(nullptr) {
+ mCallback.c = nullptr;
+ }
+
+ Callback(const Callback& other) : Callback() { *this = other; }
+
+ enum class Type : uint8_t {
+ Unknown = 0,
+ Interface = 1,
+ Function = 2,
+ Observer = 3,
+ };
+
+ Callback& operator=(const Callback& other) {
+ if (this != &other) {
+ clear();
+ mType = other.mType;
+ switch (mType) {
+ case Type::Unknown:
+ break;
+ case Type::Interface:
+ mCallback.i = other.mCallback.i;
+ NS_ADDREF(mCallback.i);
+ break;
+ case Type::Function:
+ mCallback.c = other.mCallback.c;
+ break;
+ case Type::Observer:
+ mCallback.o = other.mCallback.o;
+ NS_ADDREF(mCallback.o);
+ break;
+ }
+ mName = other.mName;
+ mClosure = other.mClosure;
+ }
+ return *this;
+ }
+
+ ~Callback() { clear(); }
+
+ void clear() {
+ if (mType == Type::Interface) {
+ NS_RELEASE(mCallback.i);
+ } else if (mType == Type::Observer) {
+ NS_RELEASE(mCallback.o);
+ }
+ mType = Type::Unknown;
+ }
+
+ void swap(Callback& other) {
+ std::swap(mType, other.mType);
+ std::swap(mCallback, other.mCallback);
+ std::swap(mName, other.mName);
+ std::swap(mClosure, other.mClosure);
+ }
+
+ Type mType;
+
+ union CallbackUnion {
+ nsTimerCallbackFunc c;
+ // These refcounted references are managed manually, as they are in a
+ // union
+ nsITimerCallback* MOZ_OWNING_REF i;
+ nsIObserver* MOZ_OWNING_REF o;
+ } mCallback;
+
+ // |Name| is a tagged union type representing one of (a) nothing, (b) a
+ // string, or (c) a function. mozilla::Variant doesn't naturally handle the
+ // "nothing" case, so we define a dummy type and value (which is unused and
+ // so the exact value doesn't matter) for it.
+ typedef const int NameNothing;
+ typedef const char* NameString;
+ typedef nsTimerNameCallbackFunc NameFunc;
+ typedef mozilla::Variant<NameNothing, NameString, NameFunc> Name;
+ static const NameNothing Nothing;
+ Name mName;
+
+ void* mClosure;
+ };
+
+ nsresult InitCommon(uint32_t aDelayMS, uint32_t aType,
+ Callback&& newCallback);
+
+ nsresult InitCommon(const mozilla::TimeDuration& aDelay, uint32_t aType,
+ Callback&& newCallback);
+
+ Callback& GetCallback() {
+ mMutex.AssertCurrentThreadOwns();
+ return mCallback;
+ }
+
+ bool IsRepeating() const {
+ static_assert(nsITimer::TYPE_ONE_SHOT < nsITimer::TYPE_REPEATING_SLACK,
+ "invalid ordering of timer types!");
+ static_assert(
+ nsITimer::TYPE_REPEATING_SLACK < nsITimer::TYPE_REPEATING_PRECISE,
+ "invalid ordering of timer types!");
+ static_assert(nsITimer::TYPE_REPEATING_PRECISE <
+ nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP,
+ "invalid ordering of timer types!");
+ return mType >= nsITimer::TYPE_REPEATING_SLACK &&
+ mType < nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY;
+ }
+
+ bool IsLowPriority() const {
+ return mType == nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY ||
+ mType == nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY;
+ }
+
+ bool IsSlack() const {
+ return mType == nsITimer::TYPE_REPEATING_SLACK ||
+ mType == nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY;
+ }
+
+ void GetName(nsACString& aName);
+
+ void SetHolder(nsTimerImplHolder* aHolder);
+
+ nsCOMPtr<nsIEventTarget> mEventTarget;
+
+ void LogFiring(const Callback& aCallback, uint8_t aType, uint32_t aDelay);
+
+ nsresult InitWithFuncCallbackCommon(nsTimerCallbackFunc aFunc, void* aClosure,
+ uint32_t aDelay, uint32_t aType,
+ const Callback::Name& aName);
+
+ // This weak reference must be cleared by the nsTimerImplHolder by calling
+ // SetHolder(nullptr) before the holder is destroyed.
+ nsTimerImplHolder* mHolder;
+
+ // These members are set by the initiating thread, when the timer's type is
+ // changed and during the period where it fires on that thread.
+ uint8_t mType;
+
+ // The generation number of this timer, re-generated each time the timer is
+ // initialized so one-shot timers can be canceled and re-initialized by the
+ // arming thread without any bad race conditions.
+ // Updated only after this timer has been removed from the timer thread.
+ int32_t mGeneration;
+
+ mozilla::TimeDuration mDelay;
+ // Updated only after this timer has been removed from the timer thread.
+ mozilla::TimeStamp mTimeout;
+
+#ifdef MOZ_TASK_TRACER
+ mozilla::tasktracer::TracedTaskCommon mTracedTask;
+#endif
+
+ static double sDeltaSum;
+ static double sDeltaSumSquared;
+ static double sDeltaNum;
+ RefPtr<nsITimer> mITimer;
+ mozilla::Mutex mMutex;
+ Callback mCallback;
+ // Counter because in rare cases we can Fire reentrantly
+ unsigned int mFiring;
+};
+
+class nsTimer final : public nsITimer {
+ explicit nsTimer(nsIEventTarget* aTarget)
+ : mImpl(new nsTimerImpl(this, aTarget)) {}
+
+ virtual ~nsTimer();
+
+ public:
+ friend class TimerThread;
+ friend class nsTimerEvent;
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_FORWARD_SAFE_NSITIMER(mImpl);
+
+ virtual size_t SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const override;
+
+ // Create a timer targeting the given target. nullptr indicates that the
+ // current thread should be used as the timer's target.
+ static RefPtr<nsTimer> WithEventTarget(nsIEventTarget* aTarget);
+
+ static nsresult XPCOMConstructor(nsISupports* aOuter, REFNSIID aIID,
+ void** aResult);
+
+ private:
+ // nsTimerImpl holds a strong ref to us. When our refcount goes to 1, we will
+ // null this to break the cycle.
+ RefPtr<nsTimerImpl> mImpl;
+};
+
+// A class that holds on to an nsTimerImpl. This lets the nsTimerImpl object
+// directly instruct its holder to forget the timer, avoiding list lookups.
+class nsTimerImplHolder {
+ public:
+ explicit nsTimerImplHolder(nsTimerImpl* aTimerImpl) : mTimerImpl(aTimerImpl) {
+ if (mTimerImpl) {
+ mTimerImpl->SetHolder(this);
+ }
+ }
+
+ ~nsTimerImplHolder() {
+ if (mTimerImpl) {
+ mTimerImpl->SetHolder(nullptr);
+ }
+ }
+
+ void Forget(nsTimerImpl* aTimerImpl) {
+ if (MOZ_UNLIKELY(!mTimerImpl)) {
+ return;
+ }
+ MOZ_ASSERT(aTimerImpl == mTimerImpl);
+ mTimerImpl->SetHolder(nullptr);
+ mTimerImpl = nullptr;
+ }
+
+ protected:
+ RefPtr<nsTimerImpl> mTimerImpl;
+};
+
+#endif /* nsTimerImpl_h___ */