fune/xpcom/threads/ThreadEventTarget.cpp
Nika Layzell 3065034574 Bug 1764119 - Part 2: Don't check gXPCOMThreadsShutdown in ThreadEventTarget::Dispatch, r=xpcom-reviewers,kmag,jstutte
We already have a mechanism for ending threads accepting new messages
when they are shut down, so this only allows tasks started from thread
shutdown tasks during xpcom shutdown to behave consistently.

We already didn't prevent dispatching to the background thread pool at
this time, so it should make little difference there as well, and may
just instead save us from deadlocks where code expects a dispatch to
succeed and it does not during actor shutdown.

This patch both relaxes the check to only be a NS_ASSERTION, and relaxes
it to allow dispatching to the current thread even after
xpcom-shutdown-threads as that thread is definitely still alive.

Differential Revision: https://phabricator.services.mozilla.com/D144592
2022-05-02 20:38:43 +00:00

142 lines
4.6 KiB
C++

/* -*- 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/DelayedRunnable.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"
using namespace mozilla;
ThreadEventTarget::ThreadEventTarget(ThreadTargetSink* aSink,
bool aIsMainThread)
: mSink(aSink)
#ifdef DEBUG
,
mIsMainThread(aIsMainThread)
#endif
{
mThread = PR_GetCurrentThread();
}
ThreadEventTarget::~ThreadEventTarget() = default;
void ThreadEventTarget::SetCurrentThread(PRThread* aThread) {
mThread = aThread;
}
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;
}
NS_ASSERTION(!gXPCOMThreadsShutDown || mIsMainThread ||
PR_GetCurrentThread() == mThread,
"Dispatch to non-main thread after xpcom-shutdown-threads");
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(
"ThreadEventTarget::Dispatch"_ns,
[&, 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::RegisterShutdownTask(nsITargetShutdownTask* aTask) {
return mSink->RegisterShutdownTask(aTask);
}
NS_IMETHODIMP
ThreadEventTarget::UnregisterShutdownTask(nsITargetShutdownTask* aTask) {
return mSink->UnregisterShutdownTask(aTask);
}
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;
}