Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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/DelayedRunnable.h"
#include "mozilla/Maybe.h"
#include "mozilla/MozPromise.h" // We initialize the MozPromise logging in this file.
#include "mozilla/ProfilerRunnable.h"
#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"
#include <memory>
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_THREADSAFE_ISUPPORTS
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;
NS_IMETHOD RegisterShutdownTask(nsITargetShutdownTask* aTask) override {
return mThread->RegisterShutdownTask(aTask);
}
NS_IMETHOD UnregisterShutdownTask(nsITargetShutdownTask* aTask) override {
return mThread->UnregisterShutdownTask(aTask);
}
bool IsCurrentThreadIn() const override {
return mThread->IsOnCurrentThread();
}
TaskDispatcher& TailDispatcher() override {
MOZ_ASSERT(IsCurrentThreadIn());
MOZ_ASSERT(IsTailDispatcherAvailable());
if (!mTailDispatcher) {
mTailDispatcher =
std::make_unique<AutoTaskDispatcher>(mDirectTaskDispatcher,
/* aIsTailDispatcher = */ true);
mThread->AddObserver(this);
}
return *mTailDispatcher;
}
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; }
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;
std::unique_ptr<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) {
mTailDispatcher->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);
AUTO_PROFILE_FOLLOWING_RUNNABLE(mRunnable);
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(XPCOMThreadWrapper, nsIThreadObserver,
nsIDirectTaskDispatcher, nsISerialEventTarget, nsIEventTarget)
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));
}
}
} // namespace mozilla