forked from mirrors/gecko-dev
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
142 lines
4.6 KiB
C++
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;
|
|
}
|