nsThreadManager::ShutdownMainThread() drains the main thread's event queue and then dooms it, so that nothing can be queued once the main thread stops processing events. The drain loop checked for an empty queue first and only processed events when something was pending, so when the queue happened to be empty on entry it doomed immediately, without a final pass. That final pass matters: processing runs a microtask checkpoint, and a checkpoint can settle promises and dispatch follow-up runnables - in particular the unhandled-rejection notification that AfterProcessMicrotasks queues for promises rejected without a handler. A promise can be rejected during the XPCOMShutdownFinal phase (e.g. when a ClearOnShutdown handler tears down an object that settles a pending promise) with no checkpoint having run since. If the queue is then empty when ShutdownMainThread() runs, the old loop doomed it without a checkpoint and the notification was never dispatched; the promises it would have held were only released when the cycle-collected context was torn down, after nsCycleCollector_shutdown(), tripping the "Cycle collected object used on a thread without a cycle collector" diagnostic assertion. Rewrite the loop as a do/while so it always processes at least once before checking for an empty queue. The final pass runs a checkpoint that dispatches any pending notification while the main thread still processes events and the cycle collector is alive; the loop then delivers it before dooming the queue. This makes the "queue empty on entry" path behave like the "queue non-empty" path that already runs a checkpoint today. Differential Revision: https://phabricator.services.mozilla.com/D312384
857 lines
27 KiB
C++
857 lines
27 KiB
C++
/* 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 "TaskController.h"
|
|
#include "ThreadEventTarget.h"
|
|
#include "mozilla/AbstractThread.h"
|
|
#include "mozilla/AppShutdown.h"
|
|
#include "mozilla/ClearOnShutdown.h"
|
|
#include "mozilla/CycleCollectedJSContext.h" // nsAutoMicroTask
|
|
#include "mozilla/EventQueue.h"
|
|
#include "mozilla/InputTaskManager.h"
|
|
#include "mozilla/Mutex.h"
|
|
#include "mozilla/NeverDestroyed.h"
|
|
#include "mozilla/Perfetto.h"
|
|
#include "mozilla/Preferences.h"
|
|
#include "mozilla/ProfilerMarkers.h"
|
|
#include "mozilla/SpinEventLoopUntil.h"
|
|
#include "mozilla/StaticPtr.h"
|
|
#include "mozilla/TaskQueue.h"
|
|
#include "mozilla/ThreadEventQueue.h"
|
|
#include "mozilla/ThreadLocal.h"
|
|
#include "mozilla/ipc/SharedMemoryMapping.h"
|
|
#include "nsExceptionHandler.h"
|
|
#include "nsIClassInfoImpl.h"
|
|
#include "nsTArray.h"
|
|
#include "nsThread.h"
|
|
#include "nsThreadPool.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "nsXULAppAPI.h"
|
|
#ifdef MOZ_CANARY
|
|
# include <fcntl.h>
|
|
# include <unistd.h>
|
|
#endif
|
|
|
|
#include "MainThreadIdlePeriod.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() = default;
|
|
|
|
nsresult Init();
|
|
|
|
already_AddRefed<TaskQueue> CreateBackgroundTaskQueue(StaticString aName);
|
|
|
|
void Shutdown();
|
|
|
|
private:
|
|
~BackgroundEventTarget() = default;
|
|
|
|
RefPtr<nsThreadPool> mPool;
|
|
RefPtr<nsThreadPool> mIOPool;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(BackgroundEventTarget, nsIEventTarget)
|
|
|
|
nsresult BackgroundEventTarget::Init() {
|
|
RefPtr pool = MakeRefPtr<nsThreadPool>();
|
|
|
|
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 the base idle thread alive for up to 5 minutes
|
|
rv = pool->SetIdleThreadMaximumTimeout(300000);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Leave excess idle threads alive for up to 1 second
|
|
rv = pool->SetIdleThreadGraceTimeout(1000);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Initialize the background I/O event target.
|
|
RefPtr ioPool = MakeRefPtr<nsThreadPool>();
|
|
|
|
// The io pool spends a lot of its time blocking on io, so we want to offload
|
|
// these jobs on a lower priority if available.
|
|
rv = ioPool->SetQoSForThreads(nsIThread::QOS_PRIORITY_LOW);
|
|
NS_ENSURE_SUCCESS(
|
|
rv, rv); // note: currently infallible, keeping this for brevity.
|
|
|
|
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.
|
|
// TODO: This pool is meant to host blocking (file, network) IO, so we might
|
|
// want to configure an even higher limit to allow more parallel operations
|
|
// to find another thread. But first we should audit the existing uses of
|
|
// NS_DISPATCH_EVENT_MAY_BLOCK if they are not just CPU heavy runnables.
|
|
rv = ioPool->SetThreadLimit(4);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = ioPool->SetIdleThreadLimit(1);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Leave allowed idle threads alive for up to 5 minutes
|
|
rv = ioPool->SetIdleThreadMaximumTimeout(300000);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Leave excess idle threads alive for up to 500ms seconds
|
|
rv = ioPool->SetIdleThreadGraceTimeout(500);
|
|
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,
|
|
DispatchFlags aFlags) {
|
|
nsCOMPtr<nsIRunnable> runnable(std::move(aRunnable));
|
|
|
|
// First, try to dispatch to `mIOPool` if we're a blocking event.
|
|
if (aFlags & NS_DISPATCH_EVENT_MAY_BLOCK) {
|
|
DispatchFlags ioPoolFlags = aFlags | ~NS_DISPATCH_EVENT_MAY_BLOCK;
|
|
if (ioPoolFlags & NS_DISPATCH_AT_END && !mIOPool->IsOnCurrentThread()) {
|
|
ioPoolFlags &= ~NS_DISPATCH_AT_END;
|
|
}
|
|
|
|
// First we'll try to dispatch to `mIOPool` if we're a blocking event. If
|
|
// this fails, we may be late enough in shutdown that `mIOPool` has been
|
|
// shut down, but `mPool` has not, so we'll fall through to dispatching
|
|
// there.
|
|
nsresult rv = mIOPool->Dispatch(do_AddRef(runnable),
|
|
ioPoolFlags | NS_DISPATCH_FALLIBLE);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
DispatchFlags poolFlags = aFlags & ~NS_DISPATCH_EVENT_MAY_BLOCK;
|
|
if (poolFlags & NS_DISPATCH_AT_END && !mPool->IsOnCurrentThread()) {
|
|
poolFlags &= ~NS_DISPATCH_AT_END;
|
|
}
|
|
|
|
// Either this event is not potentially blocking, or the dispatch to `mIOPool`
|
|
// failed - dispatch to `mPool`.
|
|
return mPool->Dispatch(runnable.forget(), poolFlags);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
BackgroundEventTarget::DispatchFromScript(nsIRunnable* aRunnable,
|
|
DispatchFlags 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;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
BackgroundEventTarget::RegisterShutdownTask(nsITargetShutdownTask* aTask) {
|
|
return mPool->RegisterShutdownTask(aTask);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
BackgroundEventTarget::UnregisterShutdownTask(nsITargetShutdownTask* aTask) {
|
|
return mPool->UnregisterShutdownTask(aTask);
|
|
}
|
|
|
|
nsIEventTarget::FeatureFlags BackgroundEventTarget::GetFeatures() {
|
|
return SUPPORTS_SHUTDOWN_TASKS | SUPPORTS_SHUTDOWN_TASK_DISPATCH;
|
|
}
|
|
|
|
void BackgroundEventTarget::Shutdown() {
|
|
// Note that our shutdown tasks are registered on `mPool` and will all
|
|
// execute there (as well as any events they may dispatch to ourselves,
|
|
// regardless of NS_DISPATCH_EVENT_MAY_BLOCK).
|
|
mIOPool->Shutdown();
|
|
mPool->Shutdown();
|
|
}
|
|
|
|
already_AddRefed<TaskQueue> BackgroundEventTarget::CreateBackgroundTaskQueue(
|
|
StaticString aName) {
|
|
return TaskQueue::Create(do_AddRef(this), aName).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();
|
|
nsThreadPool::InitTLS();
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
|
|
namespace mozilla {
|
|
|
|
void AssertIsOnMainThread() { MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); }
|
|
|
|
} // namespace mozilla
|
|
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/* static */
|
|
void nsThreadManager::ReleaseThread(void* aData) {
|
|
static_cast<nsThread*>(aData)->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)
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/*static*/ uint32_t nsIThreadManager::LargeStackSize() {
|
|
// This is just short of 2MB to avoid the Linux kernel allocating an entire
|
|
// 2MB huge page for the stack on first access. ASan and TSan builds are
|
|
// given a larger stack size due to extra data and red-zones which consume
|
|
// stack space.
|
|
#if defined(MOZ_ASAN) || defined(MOZ_TSAN)
|
|
return 4096 * 1024 - 2 * mozilla::ipc::shared_memory::SystemPageSize();
|
|
#else
|
|
return 2048 * 1024 - 2 * mozilla::ipc::shared_memory::SystemPageSize();
|
|
#endif
|
|
}
|
|
|
|
/*static*/ nsThreadManager& nsThreadManager::get() {
|
|
static NeverDestroyed<nsThreadManager> sInstance;
|
|
return *sInstance;
|
|
}
|
|
|
|
nsThreadManager::nsThreadManager()
|
|
: mCurThreadIndex(0),
|
|
mMutex("nsThreadManager::mMutex"),
|
|
mState(State::eUninit) {}
|
|
|
|
nsThreadManager::~nsThreadManager() = default;
|
|
|
|
nsresult nsThreadManager::Init() {
|
|
// Initialize perfetto if on Android.
|
|
InitPerfetto();
|
|
|
|
// 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.
|
|
{
|
|
OffTheBooksMutexAutoLock lock(mMutex);
|
|
if (mState > State::eUninit) {
|
|
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.
|
|
RefPtr idlePeriod = MakeRefPtr<MainThreadIdlePeriod>();
|
|
RefPtr idleManager = MakeRefPtr<IdleTaskManager>(idlePeriod.forget());
|
|
TaskController::Get()->SetIdleTaskManager(idleManager.forget());
|
|
|
|
// Create main thread queue that forwards events to TaskController and
|
|
// construct main thread.
|
|
UniquePtr<EventQueue> queue = MakeUnique<EventQueue>(true);
|
|
|
|
RefPtr synchronizedQueue =
|
|
MakeRefPtr<ThreadEventQueue>(std::move(queue), true);
|
|
|
|
mMainThread =
|
|
new nsThread(WrapNotNull(synchronizedQueue), nsThread::MAIN_THREAD,
|
|
{0, false, false, Some(W3_LONGTASK_BUSY_WINDOW_MS)});
|
|
|
|
nsresult rv = mMainThread->InitCurrentThread();
|
|
if (NS_FAILED(rv)) {
|
|
mMainThread = nullptr;
|
|
return rv;
|
|
}
|
|
#ifdef MOZ_MEMORY
|
|
jemalloc_set_main_thread();
|
|
jemalloc_thread_local_arena(true);
|
|
#endif
|
|
|
|
// Init AbstractThread.
|
|
AbstractThread::InitTLS();
|
|
AbstractThread::InitMainThread();
|
|
|
|
// Initialize the background event target.
|
|
RefPtr<BackgroundEventTarget> target(new BackgroundEventTarget());
|
|
|
|
rv = target->Init();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
{
|
|
OffTheBooksMutexAutoLock lock(mMutex);
|
|
|
|
mBackgroundEventTarget = std::move(target);
|
|
|
|
mState = State::eActive;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsThreadManager::ShutdownNonMainThreads() {
|
|
MOZ_ASSERT(NS_IsMainThread(), "shutdown not called from main thread");
|
|
|
|
// Empty the main thread event queue before we begin shutting down threads.
|
|
NS_ProcessPendingEvents(mMainThread);
|
|
|
|
mMainThread->mEvents->RunShutdownTasks();
|
|
|
|
RefPtr<BackgroundEventTarget> backgroundEventTarget;
|
|
{
|
|
OffTheBooksMutexAutoLock lock(mMutex);
|
|
MOZ_ASSERT(mState == State::eActive, "shutdown called multiple times");
|
|
backgroundEventTarget = mBackgroundEventTarget;
|
|
}
|
|
|
|
// This will execute the shutdown tasks of still associated TaskQueues,
|
|
// if any.
|
|
backgroundEventTarget->Shutdown();
|
|
|
|
{
|
|
// Prevent new nsThreads from being created, and collect a list of threads
|
|
// which need to be shut down.
|
|
//
|
|
// We don't prevent new thread creation until we've shut down background
|
|
// task queues, to ensure that they are able to start thread pool threads
|
|
// for shutdown tasks.
|
|
nsTArray<RefPtr<nsThread>> threadsToShutdown;
|
|
{
|
|
OffTheBooksMutexAutoLock lock(mMutex);
|
|
mState = State::eShutdown;
|
|
|
|
for (auto* thread : mThreadList) {
|
|
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.
|
|
|
|
// As we're going to be waiting for all asynchronous shutdowns below, we
|
|
// can begin asynchronously shutting down all XPCOM threads here, rather
|
|
// than shutting each thread down one-at-a-time.
|
|
for (const auto& thread : threadsToShutdown) {
|
|
thread->AsyncShutdown();
|
|
}
|
|
}
|
|
|
|
// NB: It's possible that there are events in the queue that want to *start*
|
|
// an asynchronous shutdown. But we have already started async shutdown of
|
|
// 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();
|
|
|
|
// There are no more background threads at this point.
|
|
}
|
|
|
|
void nsThreadManager::ShutdownMainThread() {
|
|
#ifdef DEBUG
|
|
{
|
|
OffTheBooksMutexAutoLock lock(mMutex);
|
|
MOZ_ASSERT(mState == State::eShutdown, "Must have called BeginShutdown");
|
|
}
|
|
#endif
|
|
|
|
// Do NS_ProcessPendingEvents but with special handling to set
|
|
// mEventsAreDoomed atomically with the removal of the last event. This means
|
|
// that PutEvent cannot succeed if the event would be left in the main thread
|
|
// queue after our final call to NS_ProcessPendingEvents.
|
|
// See comments in `nsThread::ThreadFunc` for a more detailed explanation.
|
|
//
|
|
// Always process at least once before dooming the queue to ensure any
|
|
// pending microtask processing takes place.
|
|
do {
|
|
NS_ProcessPendingEvents(mMainThread);
|
|
} while (!mMainThread->mEvents->ShutdownIfNoPendingEvents());
|
|
|
|
// 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);
|
|
|
|
OffTheBooksMutexAutoLock lock(mMutex);
|
|
mBackgroundEventTarget = nullptr;
|
|
}
|
|
|
|
void nsThreadManager::ReleaseMainThread() {
|
|
#ifdef DEBUG
|
|
{
|
|
OffTheBooksMutexAutoLock lock(mMutex);
|
|
MOZ_ASSERT(mState == State::eShutdown, "Must have called BeginShutdown");
|
|
MOZ_ASSERT(!mBackgroundEventTarget, "Must have called ShutdownMainThread");
|
|
}
|
|
#endif
|
|
MOZ_ASSERT(mMainThread);
|
|
|
|
// Release main thread object.
|
|
mMainThread = nullptr;
|
|
|
|
// Remove the TLS entry for the main thread.
|
|
PR_SetThreadPrivate(mCurThreadIndex, nullptr);
|
|
}
|
|
|
|
void nsThreadManager::RegisterCurrentThread(nsThread& aThread) {
|
|
MOZ_ASSERT(aThread.GetPRThread() == PR_GetCurrentThread(), "bad aThread");
|
|
|
|
aThread.AddRef(); // for TLS entry
|
|
PR_SetThreadPrivate(mCurThreadIndex, &aThread);
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
OffTheBooksMutexAutoLock lock(mMutex);
|
|
MOZ_ASSERT(aThread.isInList(),
|
|
"Thread was not added to the thread list before registering!");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void nsThreadManager::UnregisterCurrentThread(nsThread& aThread) {
|
|
MOZ_ASSERT(aThread.GetPRThread() == PR_GetCurrentThread(), "bad aThread");
|
|
|
|
PR_SetThreadPrivate(mCurThreadIndex, nullptr);
|
|
// Ref-count balanced via ReleaseThread
|
|
}
|
|
|
|
// Not to be used for MainThread!
|
|
RefPtr<nsThread> nsThreadManager::CreateCurrentThread(
|
|
SynchronizedEventQueue* aQueue) {
|
|
// Make sure we don't have an nsThread yet.
|
|
MOZ_ASSERT(!PR_GetThreadPrivate(mCurThreadIndex));
|
|
|
|
if (!AllowNewXPCOMThreads()) {
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<nsThread> thread = new nsThread(
|
|
WrapNotNull(aQueue), nsThread::NOT_MAIN_THREAD, {.stackSize = 0});
|
|
if (NS_FAILED(thread->InitCurrentThread())) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Note: 'thread' now has an additional reference, held in TLS (because
|
|
// nsThreadManager::RegisterCurrentThread manually AddRef()s it). That keeps
|
|
// the object alive, even if our caller disregards our returned RefPtr.
|
|
return thread;
|
|
}
|
|
|
|
nsresult nsThreadManager::DispatchToBackgroundThread(
|
|
nsIRunnable* aEvent, nsIEventTarget::DispatchFlags aDispatchFlags) {
|
|
RefPtr<BackgroundEventTarget> backgroundTarget;
|
|
{
|
|
OffTheBooksMutexAutoLock lock(mMutex);
|
|
if (!AllowNewXPCOMThreadsLocked() || !mBackgroundEventTarget) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
backgroundTarget = mBackgroundEventTarget;
|
|
}
|
|
|
|
return backgroundTarget->Dispatch(aEvent, aDispatchFlags);
|
|
}
|
|
|
|
already_AddRefed<TaskQueue> nsThreadManager::CreateBackgroundTaskQueue(
|
|
mozilla::StaticString aName) {
|
|
RefPtr<BackgroundEventTarget> backgroundTarget;
|
|
{
|
|
OffTheBooksMutexAutoLock lock(mMutex);
|
|
if (!AllowNewXPCOMThreadsLocked() || !mBackgroundEventTarget) {
|
|
return nullptr;
|
|
}
|
|
backgroundTarget = mBackgroundEventTarget;
|
|
}
|
|
|
|
return backgroundTarget->CreateBackgroundTaskQueue(aName);
|
|
}
|
|
|
|
nsThread* nsThreadManager::GetCurrentThread() {
|
|
// read thread local storage
|
|
void* data = PR_GetThreadPrivate(mCurThreadIndex);
|
|
if (data) {
|
|
return static_cast<nsThread*>(data);
|
|
}
|
|
|
|
// Keep this function working early during startup or late during shutdown on
|
|
// the main thread.
|
|
if (!AllowNewXPCOMThreads() || NS_IsMainThread()) {
|
|
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.
|
|
//
|
|
// nsThread::InitCurrentThread() will check AllowNewXPCOMThreads, and return
|
|
// an error if we're too late in shutdown to create new XPCOM threads.
|
|
RefPtr<nsThread> thread = new nsThread();
|
|
if (NS_FAILED(thread->InitCurrentThread())) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Note: 'thread' now has an additional reference, held in TLS (because
|
|
// nsThreadManager::RegisterCurrentThread manually AddRef()s it). That keeps
|
|
// the object alive, even though our local reference is going out of scope.
|
|
return thread.get();
|
|
}
|
|
|
|
bool nsThreadManager::IsNSThread() const {
|
|
{
|
|
OffTheBooksMutexAutoLock lock(mMutex);
|
|
if (mState == State::eUninit) {
|
|
return false;
|
|
}
|
|
}
|
|
if (auto* thread = (nsThread*)PR_GetThreadPrivate(mCurThreadIndex)) {
|
|
return thread->EventQueue();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsThreadManager::NewNamedThread(
|
|
const nsACString& aName, nsIThreadManager::ThreadCreationOptions aOptions,
|
|
nsIThread** aResult) {
|
|
// Note: can be called from arbitrary threads
|
|
|
|
AUTO_PROFILER_MARKER_TEXT("NewThread", OTHER,
|
|
MarkerOptions(MarkerStack::Capture()), aName);
|
|
|
|
TimeStamp startTime = TimeStamp::Now();
|
|
|
|
RefPtr queue = MakeRefPtr<ThreadEventQueue>(MakeUnique<EventQueue>());
|
|
RefPtr thr = MakeRefPtr<nsThread>(WrapNotNull(queue),
|
|
nsThread::NOT_MAIN_THREAD, aOptions);
|
|
|
|
// Note: nsThread::Init() will check AllowNewXPCOMThreads, and return an
|
|
// error if we're too late in shutdown to create new XPCOM threads. If we
|
|
// aren't, the thread will be synchronously added to mThreadList.
|
|
nsresult rv = thr->Init(aName);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
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(const nsACString& aVeryGoodReasonToDoThis,
|
|
nsINestedEventLoopCondition* aCondition) {
|
|
return SpinEventLoopUntilInternal(aVeryGoodReasonToDoThis, aCondition,
|
|
ShutdownPhase::NotInShutdown);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsThreadManager::SpinEventLoopUntilOrQuit(
|
|
const nsACString& aVeryGoodReasonToDoThis,
|
|
nsINestedEventLoopCondition* aCondition) {
|
|
return SpinEventLoopUntilInternal(aVeryGoodReasonToDoThis, aCondition,
|
|
ShutdownPhase::AppShutdownConfirmed);
|
|
}
|
|
|
|
// statics from SpinEventLoopUntil.h
|
|
AutoNestedEventLoopAnnotation* AutoNestedEventLoopAnnotation::sCurrent =
|
|
nullptr;
|
|
StaticMutex AutoNestedEventLoopAnnotation::sStackMutex;
|
|
|
|
// static from SpinEventLoopUntil.h
|
|
void AutoNestedEventLoopAnnotation::AnnotateXPCOMSpinEventLoopStack(
|
|
const nsACString& aStack) {
|
|
if (aStack.Length() > 0) {
|
|
nsCString prefixedStack(XRE_GetProcessTypeString());
|
|
prefixedStack += ": "_ns + aStack;
|
|
CrashReporter::RecordAnnotationNSCString(
|
|
CrashReporter::Annotation::XPCOMSpinEventLoopStack, prefixedStack);
|
|
} else {
|
|
CrashReporter::UnrecordAnnotation(
|
|
CrashReporter::Annotation::XPCOMSpinEventLoopStack);
|
|
}
|
|
}
|
|
|
|
nsresult nsThreadManager::SpinEventLoopUntilInternal(
|
|
const nsACString& aVeryGoodReasonToDoThis,
|
|
nsINestedEventLoopCondition* aCondition,
|
|
ShutdownPhase aShutdownPhaseToCheck) {
|
|
// XXX: We would want to AssertIsOnMainThread(); but that breaks some GTest.
|
|
nsCOMPtr<nsINestedEventLoopCondition> condition(aCondition);
|
|
nsresult rv = NS_OK;
|
|
|
|
if (!mozilla::SpinEventLoopUntil(aVeryGoodReasonToDoThis, [&]() -> bool {
|
|
// Check if an ongoing shutdown reached our limits.
|
|
if (aShutdownPhaseToCheck > ShutdownPhase::NotInShutdown &&
|
|
AppShutdown::GetCurrentShutdownPhase() >= aShutdownPhaseToCheck) {
|
|
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;
|
|
}
|
|
|
|
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),
|
|
NS_DISPATCH_FALLIBLE);
|
|
}
|
|
return mMainThread->DispatchFromScript(aEvent, NS_DISPATCH_FALLIBLE);
|
|
}
|
|
|
|
class AutoMicroTaskWrapperRunnable final : public Runnable {
|
|
public:
|
|
explicit AutoMicroTaskWrapperRunnable(nsIRunnable* aEvent)
|
|
: Runnable("AutoMicroTaskWrapperRunnable"), mEvent(aEvent) {
|
|
MOZ_ASSERT(aEvent);
|
|
}
|
|
|
|
private:
|
|
~AutoMicroTaskWrapperRunnable() = default;
|
|
|
|
NS_IMETHOD Run() override {
|
|
nsAutoMicroTask mt;
|
|
|
|
return mEvent->Run();
|
|
}
|
|
|
|
RefPtr<nsIRunnable> mEvent;
|
|
};
|
|
|
|
NS_IMETHODIMP
|
|
nsThreadManager::DispatchToMainThreadWithMicroTask(nsIRunnable* aEvent,
|
|
uint32_t aPriority,
|
|
uint8_t aArgc) {
|
|
RefPtr runnable = MakeRefPtr<AutoMicroTaskWrapperRunnable>(aEvent);
|
|
|
|
return DispatchToMainThread(runnable, aPriority, aArgc);
|
|
}
|
|
|
|
void nsThreadManager::EnableMainThreadEventPrioritization() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
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);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsThreadManager::DispatchDirectTaskToCurrentThread(nsIRunnable* aEvent) {
|
|
NS_ENSURE_STATE(aEvent);
|
|
nsCOMPtr<nsIRunnable> runnable = aEvent;
|
|
return GetCurrentThread()->DispatchDirectTask(runnable.forget());
|
|
}
|
|
|
|
bool nsThreadManager::AllowNewXPCOMThreads() {
|
|
mozilla::OffTheBooksMutexAutoLock lock(mMutex);
|
|
return AllowNewXPCOMThreadsLocked();
|
|
}
|