Bug 1695580 - In xpcom, cancel pending DelayedRunnable timers on shutdown. r=KrisWright

Because DelayedRunnables are fire-and-forget, there is no way for a targeted
EventTarget to clean them up on shutdown. Thus if a timer fires after
EventTarget shutdown it will fail to dispatch the timer event, and avoid
releasing the timer callback because it's not on the targeted thread. This
causes a leak as there is a ref-cycle between nsTimerImpl::mCallback and
DelayedRunnable::mTimer.

This patch adds nsIDelayedRunnableObserver for a target to observe which
DelayedRunnables are relying on their timer to run them. This allows the target
to schedule a shutdown task to cancel those timers and release the runnables on
the target thread.

Supported DelayedRunnable targets with this patch are TaskQueues,
eventqueue-based nsThreads and XPCOMThreadWrappers that wrap a supported
nsThread.

An assertion makes sure at runtime that future new uses of DelayedRunnable
target nsIDelayedRunnableObserver-supported event targets.

Differential Revision: https://phabricator.services.mozilla.com/D109781
This commit is contained in:
Andreas Pehrson 2021-04-06 12:16:11 +00:00
Родитель 80e4501701
Коммит f9c6ad1082
14 изменённых файлов: 295 добавлений и 10 удалений

Просмотреть файл

@ -621,6 +621,7 @@ nsresult ShutdownXPCOM(nsIServiceManager* aServMgr) {
mozilla::AppShutdown::AdvanceShutdownPhase(
mozilla::ShutdownPhase::XPCOMShutdownThreads);
nsThreadManager::get().CancelBackgroundDelayedRunnables();
gXPCOMThreadsShutDown = true;
NS_ProcessPendingEvents(thread);

Просмотреть файл

@ -31,12 +31,14 @@ MOZ_THREAD_LOCAL(AbstractThread*) AbstractThread::sCurrentThreadTLS;
class XPCOMThreadWrapper final : public AbstractThread,
public nsIThreadObserver,
public nsIDirectTaskDispatcher {
public nsIDirectTaskDispatcher,
public nsIDelayedRunnableObserver {
public:
XPCOMThreadWrapper(nsIThreadInternal* aThread, bool aRequireTailDispatch,
bool aOnThread)
: AbstractThread(aRequireTailDispatch),
mThread(aThread),
mDelayedRunnableObserver(do_QueryInterface(mThread)),
mDirectTaskDispatcher(do_QueryInterface(aThread)),
mOnThread(aOnThread) {
MOZ_DIAGNOSTIC_ASSERT(mThread && mDirectTaskDispatcher);
@ -158,8 +160,22 @@ class XPCOMThreadWrapper final : public AbstractThread,
return mDirectTaskDispatcher->HaveDirectTasks(aResult);
}
//-----------------------------------------------------------------------------
// nsIDelayedRunnableObserver
//-----------------------------------------------------------------------------
void OnDelayedRunnableCreated(DelayedRunnable* aRunnable) override {
mDelayedRunnableObserver->OnDelayedRunnableCreated(aRunnable);
}
void OnDelayedRunnableScheduled(DelayedRunnable* aRunnable) override {
mDelayedRunnableObserver->OnDelayedRunnableScheduled(aRunnable);
}
void OnDelayedRunnableRan(DelayedRunnable* aRunnable) override {
mDelayedRunnableObserver->OnDelayedRunnableRan(aRunnable);
}
private:
const RefPtr<nsIThreadInternal> mThread;
const nsCOMPtr<nsIDelayedRunnableObserver> mDelayedRunnableObserver;
const nsCOMPtr<nsIDirectTaskDispatcher> mDirectTaskDispatcher;
Maybe<AutoTaskDispatcher> mTailDispatcher;
const bool mOnThread;
@ -215,8 +231,16 @@ class XPCOMThreadWrapper final : public AbstractThread,
const RefPtr<nsIRunnable> mRunnable;
};
};
NS_IMPL_ISUPPORTS_INHERITED(XPCOMThreadWrapper, AbstractThread,
nsIThreadObserver, nsIDirectTaskDispatcher);
NS_INTERFACE_MAP_BEGIN(XPCOMThreadWrapper)
NS_INTERFACE_MAP_ENTRY(nsIThreadObserver)
NS_INTERFACE_MAP_ENTRY(nsIDirectTaskDispatcher)
NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIDelayedRunnableObserver,
mDelayedRunnableObserver)
NS_INTERFACE_MAP_END_INHERITING(AbstractThread)
NS_IMPL_ADDREF_INHERITED(XPCOMThreadWrapper, AbstractThread)
NS_IMPL_RELEASE_INHERITED(XPCOMThreadWrapper, AbstractThread)
NS_IMPL_ISUPPORTS(AbstractThread, nsIEventTarget, nsISerialEventTarget)

Просмотреть файл

@ -13,15 +13,25 @@ DelayedRunnable::DelayedRunnable(already_AddRefed<nsIEventTarget> aTarget,
uint32_t aDelay)
: mozilla::Runnable("DelayedRunnable"),
mTarget(aTarget),
mObserver(do_QueryInterface(mTarget)),
mWrappedRunnable(aRunnable),
mDelayedFrom(TimeStamp::NowLoRes()),
mDelay(aDelay) {}
mDelay(aDelay) {
MOZ_DIAGNOSTIC_ASSERT(mObserver,
"Target must implement nsIDelayedRunnableObserver");
}
nsresult DelayedRunnable::Init() {
mObserver->OnDelayedRunnableCreated(this);
return NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, mDelay,
nsITimer::TYPE_ONE_SHOT, mTarget);
}
void DelayedRunnable::CancelTimer() {
MOZ_ASSERT(mTarget->IsOnCurrentThread());
mTimer->Cancel();
}
NS_IMETHODIMP DelayedRunnable::Run() {
MOZ_ASSERT(mTimer, "DelayedRunnable without Init?");
@ -33,6 +43,9 @@ NS_IMETHODIMP DelayedRunnable::Run() {
// Are we too early?
if ((mozilla::TimeStamp::NowLoRes() - mDelayedFrom).ToMilliseconds() <
mDelay) {
if (mObserver) {
mObserver->OnDelayedRunnableScheduled(this);
}
return NS_OK; // Let the nsITimer run us.
}
@ -44,6 +57,9 @@ NS_IMETHODIMP DelayedRunnable::Notify(nsITimer* aTimer) {
// If we already ran, the timer should have been canceled.
MOZ_ASSERT(mWrappedRunnable);
if (mObserver) {
mObserver->OnDelayedRunnableRan(this);
}
return DoRun();
}

Просмотреть файл

@ -9,6 +9,7 @@
#include "mozilla/TimeStamp.h"
#include "nsCOMPtr.h"
#include "nsIDelayedRunnableObserver.h"
#include "nsIRunnable.h"
#include "nsITimer.h"
#include "nsThreadUtils.h"
@ -26,11 +27,18 @@ class DelayedRunnable : public Runnable, public nsITimerCallback {
nsresult Init();
/**
* Cancels the underlying timer. Called when the target is going away, so the
* runnable can be released safely on the target thread.
*/
void CancelTimer();
private:
~DelayedRunnable() = default;
nsresult DoRun();
const nsCOMPtr<nsIEventTarget> mTarget;
const nsCOMPtr<nsIDelayedRunnableObserver> mObserver;
nsCOMPtr<nsIRunnable> mWrappedRunnable;
nsCOMPtr<nsITimer> mTimer;
const TimeStamp mDelayedFrom;

Просмотреть файл

@ -6,6 +6,7 @@
#include "mozilla/TaskQueue.h"
#include "mozilla/DelayedRunnable.h"
#include "nsThreadUtils.h"
namespace mozilla {
@ -27,9 +28,11 @@ TaskQueue::TaskQueue(already_AddRefed<nsIEventTarget> aTarget,
TaskQueue::~TaskQueue() {
// No one is referencing this TaskQueue anymore, meaning no tasks can be
// pending as all Runner hold a reference to this TaskQueue.
MOZ_ASSERT(mScheduledDelayedRunnables.IsEmpty());
}
NS_IMPL_ISUPPORTS_INHERITED(TaskQueue, AbstractThread, nsIDirectTaskDispatcher);
NS_IMPL_ISUPPORTS_INHERITED(TaskQueue, AbstractThread, nsIDirectTaskDispatcher,
nsIDelayedRunnableObserver);
TaskDispatcher& TaskQueue::TailDispatcher() {
MOZ_ASSERT(IsCurrentThreadIn());
@ -104,6 +107,52 @@ void TaskQueue::AwaitShutdownAndIdle() {
AwaitIdleLocked();
}
void TaskQueue::OnDelayedRunnableCreated(DelayedRunnable* aRunnable) {
#ifdef DEBUG
MonitorAutoLock mon(mQueueMonitor);
MOZ_ASSERT(!mDelayedRunnablesCancelPromise);
#endif
}
void TaskQueue::OnDelayedRunnableScheduled(DelayedRunnable* aRunnable) {
MOZ_ASSERT(IsOnCurrentThread());
mScheduledDelayedRunnables.AppendElement(aRunnable);
}
void TaskQueue::OnDelayedRunnableRan(DelayedRunnable* aRunnable) {
MOZ_ASSERT(IsOnCurrentThread());
MOZ_ALWAYS_TRUE(mScheduledDelayedRunnables.RemoveElement(aRunnable));
}
auto TaskQueue::CancelDelayedRunnables() -> RefPtr<CancelPromise> {
MonitorAutoLock mon(mQueueMonitor);
return CancelDelayedRunnablesLocked();
}
auto TaskQueue::CancelDelayedRunnablesLocked() -> RefPtr<CancelPromise> {
mQueueMonitor.AssertCurrentThreadOwns();
if (mDelayedRunnablesCancelPromise) {
return mDelayedRunnablesCancelPromise;
}
mDelayedRunnablesCancelPromise =
mDelayedRunnablesCancelHolder.Ensure(__func__);
nsCOMPtr<nsIRunnable> cancelRunnable =
NewRunnableMethod("TaskQueue::CancelDelayedRunnablesImpl", this,
&TaskQueue::CancelDelayedRunnablesImpl);
MOZ_ALWAYS_SUCCEEDS(DispatchLocked(/* passed by ref */ cancelRunnable,
NS_DISPATCH_NORMAL, TailDispatch));
return mDelayedRunnablesCancelPromise;
}
void TaskQueue::CancelDelayedRunnablesImpl() {
MOZ_ASSERT(IsOnCurrentThread());
for (const auto& runnable : mScheduledDelayedRunnables) {
runnable->CancelTimer();
}
mScheduledDelayedRunnables.Clear();
mDelayedRunnablesCancelHolder.Resolve(true, __func__);
}
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.
@ -111,6 +160,7 @@ RefPtr<ShutdownPromise> TaskQueue::BeginShutdown() {
currentThread->TailDispatchTasksFor(this);
}
MonitorAutoLock mon(mQueueMonitor);
Unused << CancelDelayedRunnablesLocked();
mIsShutdown = true;
RefPtr<ShutdownPromise> p = mShutdownPromise.Ensure(__func__);
MaybeResolveShutdown();

Просмотреть файл

@ -15,6 +15,7 @@
#include "mozilla/MozPromise.h"
#include "mozilla/RefPtr.h"
#include "mozilla/TaskDispatcher.h"
#include "nsIDelayedRunnableObserver.h"
#include "nsIDirectTaskDispatcher.h"
#include "nsThreadUtils.h"
@ -47,7 +48,9 @@ typedef MozPromise<bool, bool, false> ShutdownPromise;
// 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 TaskQueue : public AbstractThread,
public nsIDirectTaskDispatcher,
public nsIDelayedRunnableObserver {
class EventTargetWrapper;
public:
@ -93,6 +96,18 @@ class TaskQueue : public AbstractThread, public nsIDirectTaskDispatcher {
// So we can access nsIEventTarget::Dispatch(nsIRunnable*, uint32_t aFlags)
using nsIEventTarget::Dispatch;
// nsIDelayedRunnableObserver
void OnDelayedRunnableCreated(DelayedRunnable* aRunnable) override;
void OnDelayedRunnableScheduled(DelayedRunnable* aRunnable) override;
void OnDelayedRunnableRan(DelayedRunnable* aRunnable) override;
using CancelPromise = MozPromise<bool, bool, false>;
// Dispatches a task to cancel any pending DelayedRunnables. Idempotent. Only
// dispatches the task on the first call. Creating DelayedRunnables after this
// is called will result in assertion failures.
RefPtr<CancelPromise> CancelDelayedRunnables();
// 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
@ -126,6 +141,11 @@ class TaskQueue : public AbstractThread, public nsIDirectTaskDispatcher {
nsresult DispatchLocked(nsCOMPtr<nsIRunnable>& aRunnable, uint32_t aFlags,
DispatchReason aReason = NormalDispatch);
RefPtr<CancelPromise> CancelDelayedRunnablesLocked();
// Cancels any scheduled DelayedRunnables on this TaskQueue.
void CancelDelayedRunnablesImpl();
void MaybeResolveShutdown() {
mQueueMonitor.AssertCurrentThreadOwns();
if (mIsShutdown && !mIsRunning) {
@ -136,7 +156,8 @@ class TaskQueue : public AbstractThread, public nsIDirectTaskDispatcher {
nsCOMPtr<nsIEventTarget> mTarget;
// Monitor that protects the queue and mIsRunning;
// Monitor that protects the queue, mIsRunning and
// mDelayedRunnablesCancelPromise;
Monitor mQueueMonitor;
typedef struct TaskStruct {
@ -147,6 +168,18 @@ class TaskQueue : public AbstractThread, public nsIDirectTaskDispatcher {
// Queue of tasks to run.
std::queue<TaskStruct> mTasks;
// DelayedRunnables (from DelayedDispatch) that are managed by their
// respective timers, but have not yet run. Accessed only on this
// TaskQueue.
nsTArray<RefPtr<DelayedRunnable>> mScheduledDelayedRunnables;
// Manages resolving mDelayedRunnablesCancelPromise.
MozPromiseHolder<CancelPromise> mDelayedRunnablesCancelHolder;
// Set once the task to cancel all pending DelayedRunnables has been
// dispatched. Guarded by mQueueMonitor.
RefPtr<CancelPromise> mDelayedRunnablesCancelPromise;
// 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.

Просмотреть файл

@ -33,13 +33,18 @@ ThreadEventTarget::ThreadEventTarget(ThreadTargetSink* aSink,
mThread = PR_GetCurrentThread();
}
ThreadEventTarget::~ThreadEventTarget() {
MOZ_ASSERT(mScheduledDelayedRunnables.IsEmpty());
}
void ThreadEventTarget::SetCurrentThread(PRThread* aThread) {
mThread = aThread;
}
void ThreadEventTarget::ClearCurrentThread() { mThread = nullptr; }
NS_IMPL_ISUPPORTS(ThreadEventTarget, nsIEventTarget, nsISerialEventTarget)
NS_IMPL_ISUPPORTS(ThreadEventTarget, nsIEventTarget, nsISerialEventTarget,
nsIDelayedRunnableObserver)
NS_IMETHODIMP
ThreadEventTarget::DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags) {
@ -136,3 +141,23 @@ ThreadEventTarget::IsOnCurrentThreadInfallible() {
// we are called, we can never be on this thread.
return false;
}
void ThreadEventTarget::OnDelayedRunnableCreated(DelayedRunnable* aRunnable) {}
void ThreadEventTarget::OnDelayedRunnableScheduled(DelayedRunnable* aRunnable) {
MOZ_ASSERT(IsOnCurrentThread());
mScheduledDelayedRunnables.AppendElement(aRunnable);
}
void ThreadEventTarget::OnDelayedRunnableRan(DelayedRunnable* aRunnable) {
MOZ_ASSERT(IsOnCurrentThread());
MOZ_ALWAYS_TRUE(mScheduledDelayedRunnables.RemoveElement(aRunnable));
}
void ThreadEventTarget::NotifyShutdown() {
MOZ_ASSERT(IsOnCurrentThread());
for (const auto& runnable : mScheduledDelayedRunnables) {
runnable->CancelTimer();
}
mScheduledDelayedRunnables.Clear();
}

Просмотреть файл

@ -10,19 +10,31 @@
#include "mozilla/MemoryReporting.h"
#include "mozilla/Mutex.h"
#include "mozilla/SynchronizedEventQueue.h" // for ThreadTargetSink
#include "nsIDelayedRunnableObserver.h"
#include "nsISerialEventTarget.h"
namespace mozilla {
class DelayedRunnable;
// ThreadEventTarget handles the details of posting an event to a thread. It can
// be used with any ThreadTargetSink implementation.
class ThreadEventTarget final : public nsISerialEventTarget {
class ThreadEventTarget final : public nsISerialEventTarget,
public nsIDelayedRunnableObserver {
public:
ThreadEventTarget(ThreadTargetSink* aSink, bool aIsMainThread);
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIEVENTTARGET_FULL
// nsIDelayedRunnableObserver
void OnDelayedRunnableCreated(DelayedRunnable* aRunnable) override;
void OnDelayedRunnableScheduled(DelayedRunnable* aRunnable) override;
void OnDelayedRunnableRan(DelayedRunnable* aRunnable) override;
// Notification from, and on, the owner thread that it is shutting down.
// Cancels any scheduled DelayedRunnables.
void NotifyShutdown();
// Disconnects the target so that it can no longer post events.
void Disconnect(const MutexAutoLock& aProofOfLock) {
mSink->Disconnect(aProofOfLock);
@ -43,10 +55,14 @@ class ThreadEventTarget final : public nsISerialEventTarget {
}
private:
~ThreadEventTarget() = default;
~ThreadEventTarget();
RefPtr<ThreadTargetSink> mSink;
bool mIsMainThread;
// DelayedRunnables (from DelayedDispatch) that are managed by their
// respective timers, but have not yet run. Accessed only on this nsThread.
nsTArray<RefPtr<mozilla::DelayedRunnable>> mScheduledDelayedRunnables;
};
} // namespace mozilla

Просмотреть файл

@ -30,6 +30,7 @@ XPCOM_MANIFESTS += [
EXPORTS += [
"MainThreadUtils.h",
"nsICancelableRunnable.h",
"nsIDelayedRunnableObserver.h",
"nsIDiscardableRunnable.h",
"nsIIdleRunnable.h",
"nsMemoryPressure.h",

Просмотреть файл

@ -0,0 +1,50 @@
/* -*- 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_NSIDELAYEDRUNNABLEOBSERVER_H_
#define XPCOM_THREADS_NSIDELAYEDRUNNABLEOBSERVER_H_
#include "nsISupports.h"
namespace mozilla {
class DelayedRunnable;
}
#define NS_IDELAYEDRUNNABLEOBSERVER_IID \
{ \
0xd226bade, 0xac13, 0x46fe, { \
0x9f, 0xcc, 0xde, 0xe7, 0x48, 0x35, 0xcd, 0x82 \
} \
}
class NS_NO_VTABLE nsIDelayedRunnableObserver : public nsISupports {
public:
NS_DECLARE_STATIC_IID_ACCESSOR(NS_IDELAYEDRUNNABLEOBSERVER_IID)
/**
* Called by the DelayedRunnable after being created, on the dispatching
* thread. This allows for various lifetime checks and gives assertions a
* chance to provide useful stack traces.
*/
virtual void OnDelayedRunnableCreated(
mozilla::DelayedRunnable* aRunnable) = 0;
/**
* Called by the DelayedRunnable on its target thread when delegating the
* responsibility for being run to its underlying timer.
*/
virtual void OnDelayedRunnableScheduled(
mozilla::DelayedRunnable* aRunnable) = 0;
/**
* Called by the DelayedRunnable on its target thread after having been run by
* its underlying timer.
*/
virtual void OnDelayedRunnableRan(mozilla::DelayedRunnable* aRunnable) = 0;
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsIDelayedRunnableObserver,
NS_IDELAYEDRUNNABLEOBSERVER_IID)
#endif

Просмотреть файл

@ -186,6 +186,7 @@ NS_INTERFACE_MAP_BEGIN(nsThread)
NS_INTERFACE_MAP_ENTRY(nsIEventTarget)
NS_INTERFACE_MAP_ENTRY(nsISerialEventTarget)
NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIDelayedRunnableObserver, mEventTarget)
NS_INTERFACE_MAP_ENTRY(nsIDirectTaskDispatcher)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIThread)
if (aIID.Equals(NS_GET_IID(nsIClassInfo))) {
@ -236,6 +237,9 @@ class nsThreadShutdownEvent : public Runnable {
mShutdownContext(aCtx) {}
NS_IMETHOD Run() override {
mThread->mShutdownContext = mShutdownContext;
if (mThread->mEventTarget) {
mThread->mEventTarget->NotifyShutdown();
}
MessageLoop::current()->Quit();
return NS_OK;
}
@ -1387,6 +1391,18 @@ nsIEventTarget* nsThread::EventTarget() { return this; }
nsISerialEventTarget* nsThread::SerialEventTarget() { return this; }
void nsThread::OnDelayedRunnableCreated(mozilla::DelayedRunnable* aRunnable) {
mEventTarget->OnDelayedRunnableCreated(aRunnable);
}
void nsThread::OnDelayedRunnableScheduled(mozilla::DelayedRunnable* aRunnable) {
mEventTarget->OnDelayedRunnableScheduled(aRunnable);
}
void nsThread::OnDelayedRunnableRan(mozilla::DelayedRunnable* aRunnable) {
mEventTarget->OnDelayedRunnableRan(aRunnable);
}
nsLocalExecutionRecord nsThread::EnterLocalExecution() {
MOZ_RELEASE_ASSERT(!mIsInLocalExecutionMode);
MOZ_ASSERT(IsOnCurrentThread());

Просмотреть файл

@ -21,6 +21,7 @@
#include "mozilla/TaskDispatcher.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtr.h"
#include "nsIDelayedRunnableObserver.h"
#include "nsIDirectTaskDispatcher.h"
#include "nsIEventTarget.h"
#include "nsISerialEventTarget.h"
@ -31,6 +32,7 @@
namespace mozilla {
class CycleCollectedJSContext;
class DelayedRunnable;
class SynchronizedEventQueue;
class ThreadEventQueue;
class ThreadEventTarget;
@ -152,6 +154,7 @@ class PerformanceCounterState {
// A native thread
class nsThread : public nsIThreadInternal,
public nsISupportsPriority,
public nsIDelayedRunnableObserver,
public nsIDirectTaskDispatcher,
private mozilla::LinkedListElement<nsThread> {
friend mozilla::LinkedList<nsThread>;
@ -258,6 +261,10 @@ class nsThread : public nsIThreadInternal,
mUseHangMonitor = aValue;
}
void OnDelayedRunnableCreated(mozilla::DelayedRunnable* aRunnable) override;
void OnDelayedRunnableScheduled(mozilla::DelayedRunnable* aRunnable) override;
void OnDelayedRunnableRan(mozilla::DelayedRunnable* aRunnable) override;
private:
void DoMainThreadSpecificProcessing() const;

Просмотреть файл

@ -51,6 +51,9 @@ class BackgroundEventTarget final : public nsIEventTarget {
already_AddRefed<nsISerialEventTarget> CreateBackgroundTaskQueue(
const char* aName);
using CancelPromise = TaskQueue::CancelPromise::AllPromiseType;
RefPtr<CancelPromise> CancelBackgroundDelayedRunnables();
void BeginShutdown(nsTArray<RefPtr<ShutdownPromise>>&);
void FinishShutdown();
@ -62,6 +65,7 @@ class BackgroundEventTarget final : public nsIEventTarget {
Mutex mMutex;
nsTArray<RefPtr<TaskQueue>> mTaskQueues;
bool mIsBackgroundDelayedRunnablesCanceled;
};
NS_IMPL_ISUPPORTS(BackgroundEventTarget, nsIEventTarget)
@ -198,6 +202,19 @@ BackgroundEventTarget::CreateBackgroundTaskQueue(const char* aName) {
return queue.forget();
}
auto BackgroundEventTarget::CancelBackgroundDelayedRunnables()
-> RefPtr<CancelPromise> {
MOZ_ASSERT(NS_IsMainThread());
MutexAutoLock lock(mMutex);
mIsBackgroundDelayedRunnablesCanceled = true;
nsTArray<RefPtr<TaskQueue::CancelPromise>> promises;
for (const auto& tq : mTaskQueues) {
promises.AppendElement(tq->CancelDelayedRunnables());
}
return TaskQueue::CancelPromise::All(GetMainThreadSerialEventTarget(),
promises);
}
extern "C" {
// This uses the C language linkage because it's exposed to Rust
// via the xpcom/rust/moz_task crate.
@ -402,6 +419,8 @@ void nsThreadManager::Shutdown() {
// in-flight asynchronous thread shutdowns to complete.
mMainThread->WaitForAllAsynchronousShutdowns();
mMainThread->mEventTarget->NotifyShutdown();
// In case there are any more events somehow...
NS_ProcessPendingEvents(mMainThread);
@ -490,6 +509,17 @@ nsThreadManager::CreateBackgroundTaskQueue(const char* aName) {
return mBackgroundEventTarget->CreateBackgroundTaskQueue(aName);
}
void nsThreadManager::CancelBackgroundDelayedRunnables() {
if (!mInitialized) {
return;
}
bool canceled = false;
mBackgroundEventTarget->CancelBackgroundDelayedRunnables()->Then(
GetMainThreadSerialEventTarget(), __func__, [&] { canceled = true; });
::SpinEventLoopUntil([&]() { return canceled; });
}
nsThread* nsThreadManager::GetCurrentThread() {
// read thread local storage
void* data = PR_GetThreadPrivate(mCurThreadIndex);

Просмотреть файл

@ -67,6 +67,14 @@ class nsThreadManager : public nsIThreadManager {
already_AddRefed<nsISerialEventTarget> CreateBackgroundTaskQueue(
const char* aName);
// For each background TaskQueue cancel pending DelayedRunnables, and prohibit
// creating future DelayedRunnables for them, since we'll soon be shutting
// them down.
// Pending DelayedRunnables are canceled on their respective TaskQueue.
// We block main thread until they are all done, but spin the eventloop in the
// meantime.
void CancelBackgroundDelayedRunnables();
~nsThreadManager();
void EnableMainThreadEventPrioritization();