mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-16 23:05:42 +00:00
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:
parent
f600c8f2d3
commit
9717c27492
@ -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",
|
||||
|
50
xpcom/threads/nsIDelayedRunnableObserver.h
Normal file
50
xpcom/threads/nsIDelayedRunnableObserver.h
Normal file
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user