Automatically generated with:
cp dom/.clang-format xpcom/ && mach format xpcom/**.{cpp,h}
Manual changes:
* nsTDependentSubstring.cpp was relying on include order, prevent that
from being changed by clang-format.
* Missing nsAtom.h include from nsGkAtoms.h
* nsWindowsHelpers.h needed to keep the include order
* MemoryPressureLevelMac.h needed mozilla/Attributes.h
Differential Revision: https://phabricator.services.mozilla.com/D311687
385 lines
12 KiB
C++
385 lines
12 KiB
C++
/* 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/TaskQueue.h"
|
|
|
|
#include "mozilla/FlowMarkers.h"
|
|
#include "mozilla/ProfilerRunnable.h"
|
|
#include "nsIEventTarget.h"
|
|
#include "nsITargetShutdownTask.h"
|
|
#include "nsQueryObject.h"
|
|
#include "nsThreadUtils.h"
|
|
|
|
namespace mozilla {
|
|
|
|
static LazyLogModule sTaskQueueLog("TaskQueue");
|
|
|
|
#define LOG_TQ(level, msg, ...) \
|
|
MOZ_LOG(sTaskQueueLog, level, (msg, ##__VA_ARGS__))
|
|
|
|
RefPtr<TaskQueue> TaskQueue::Create(already_AddRefed<nsIEventTarget> aTarget,
|
|
StaticString aName,
|
|
bool aSupportsTailDispatch) {
|
|
nsCOMPtr<nsIEventTarget> target(std::move(aTarget));
|
|
LOG_TQ(LogLevel::Debug,
|
|
"Creating TaskQueue '%s' on target %p (supportsTailDispatch=%d)",
|
|
aName.get(), target.get(), aSupportsTailDispatch);
|
|
|
|
RefPtr<TaskQueue> queue =
|
|
new TaskQueue(do_AddRef(target), aName, aSupportsTailDispatch);
|
|
|
|
return queue;
|
|
}
|
|
|
|
TaskQueue::TaskQueue(already_AddRefed<nsIEventTarget> aTarget,
|
|
const char* aName, bool aSupportsTailDispatch)
|
|
: AbstractThread(aSupportsTailDispatch),
|
|
mTarget(aTarget),
|
|
mQueueMonitor("TaskQueue::Queue"),
|
|
mTailDispatcher(nullptr),
|
|
mIsTargetShutdownTaskRegistered(false),
|
|
mIsRunning(false),
|
|
mIsShutdown(false),
|
|
mName(aName) {}
|
|
|
|
TaskQueue::~TaskQueue() {
|
|
LOG_TQ(LogLevel::Debug, "Destroying TaskQueue '%s'", mName);
|
|
// A TaskQueue with shutdown tasks deserves a regular shutdown.
|
|
// Note that if the target SUPPORTS_SHUTDOWN_TASK_DISPATCH the TaskQueue will
|
|
// be kept alive until explicit (or target) shutdown, anyways.
|
|
MOZ_ASSERT(mIsShutdown || mShutdownTasks.IsEmpty());
|
|
}
|
|
|
|
NS_IMPL_ADDREF(TaskQueue)
|
|
NS_IMPL_RELEASE(TaskQueue)
|
|
|
|
NS_INTERFACE_MAP_BEGIN(TaskQueue)
|
|
NS_INTERFACE_MAP_ENTRY(nsIDirectTaskDispatcher)
|
|
NS_INTERFACE_MAP_ENTRY(nsISerialEventTarget)
|
|
NS_INTERFACE_MAP_ENTRY(nsIEventTarget)
|
|
NS_INTERFACE_MAP_ENTRY(nsITargetShutdownTask)
|
|
NS_INTERFACE_MAP_ENTRY_CONCRETE(TaskQueue)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
TaskDispatcher& TaskQueue::TailDispatcher() {
|
|
MOZ_ASSERT(IsCurrentThreadIn());
|
|
MOZ_ASSERT(mTailDispatcher);
|
|
return *mTailDispatcher;
|
|
}
|
|
|
|
void TaskQueue::TargetShutdown() {
|
|
// Nobody needs to wait for the promise as the Runner will ensure all
|
|
// dispatched tasks are completed before the TaskQueue is destroyed
|
|
// given the target SUPPORTS_SHUTDOWN_TASK_DISPATCH.
|
|
LOG_TQ(LogLevel::Debug, "TaskQueue::TargetShutdown '%s'", mName);
|
|
BeginShutdown();
|
|
}
|
|
|
|
void TaskQueue::MaybeUnregisterTargetShutdownTask() {
|
|
if (mIsTargetShutdownTaskRegistered) {
|
|
mTarget->UnregisterShutdownTask(this);
|
|
// We cannot always expect success here because the target might shut
|
|
// down already and this call might be an indirect consequence through
|
|
// some other target shutdown task running first.
|
|
mIsTargetShutdownTaskRegistered = false;
|
|
}
|
|
}
|
|
|
|
// Note aRunnable is passed by ref to support conditional ownership transfer.
|
|
// See Dispatch() in TaskQueue.h for more details.
|
|
nsresult TaskQueue::DispatchLocked(nsCOMPtr<nsIRunnable>& aRunnable,
|
|
DispatchFlags aFlags,
|
|
DispatchReason aReason) {
|
|
mQueueMonitor.AssertCurrentThreadOwns();
|
|
|
|
// Continue to allow dispatches after shutdown until the last runnable has
|
|
// been processed, at which point no more runnables will be accepted.
|
|
if (mIsShutdown) {
|
|
LOG_TQ(LogLevel::Debug,
|
|
"TaskQueue::DispatchLocked '%s' %s dispatch during shutdown", mName,
|
|
mIsRunning ? "accepting" : "rejecting");
|
|
if (!mIsRunning) {
|
|
return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
|
|
}
|
|
}
|
|
|
|
AbstractThread* currentThread;
|
|
if (aReason != TailDispatch && (currentThread = GetCurrent()) &&
|
|
RequiresTailDispatch(currentThread) &&
|
|
currentThread->IsTailDispatcherAvailable()) {
|
|
return currentThread->TailDispatcher().AddTask(this, aRunnable.forget());
|
|
}
|
|
|
|
PROFILER_MARKER("TaskQueue::DispatchLocked", OTHER,
|
|
{MarkerStack::MaybeCapture(
|
|
profiler_feature_active(ProfilerFeature::Flows))},
|
|
FlowMarker, Flow::FromPointer(aRunnable.get()));
|
|
LogRunnable::LogDispatch(aRunnable);
|
|
mTasks.EmplaceBack(TaskStruct{std::move(aRunnable), aFlags});
|
|
|
|
if (mIsRunning) {
|
|
return NS_OK;
|
|
}
|
|
auto runner = MakeRefPtr<Runner>(this, mTarget, mObserver, std::move(mTasks));
|
|
nsresult rv =
|
|
mTarget->Dispatch(runner.forget(), aFlags | NS_DISPATCH_FALLIBLE);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("Failed to dispatch runnable to run TaskQueue");
|
|
return rv;
|
|
}
|
|
mIsRunning = true;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult TaskQueue::RegisterShutdownTask(nsITargetShutdownTask* aTask) {
|
|
NS_ENSURE_ARG(aTask);
|
|
|
|
LOG_TQ(LogLevel::Debug,
|
|
"TaskQueue::RegisterShutdownTask '%s' registering shutdown task %p",
|
|
mName, aTask);
|
|
MonitorAutoLock mon(mQueueMonitor);
|
|
if (mIsShutdown) {
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
if (!mIsTargetShutdownTaskRegistered && mShutdownTasks.IsEmpty()) {
|
|
FeatureFlags f = mTarget->GetFeatures();
|
|
if ((f & SUPPORTS_SHUTDOWN_TASKS) &&
|
|
(f & SUPPORTS_SHUTDOWN_TASK_DISPATCH)) {
|
|
MOZ_TRY(mTarget->RegisterShutdownTask(this));
|
|
mIsTargetShutdownTaskRegistered = true;
|
|
}
|
|
}
|
|
return mShutdownTasks.AddTask(aTask);
|
|
}
|
|
|
|
nsresult TaskQueue::UnregisterShutdownTask(nsITargetShutdownTask* aTask) {
|
|
NS_ENSURE_ARG(aTask);
|
|
|
|
LOG_TQ(
|
|
LogLevel::Debug,
|
|
"TaskQueue::UnregisterShutdownTask '%s' unregistering shutdown task %p",
|
|
mName, aTask);
|
|
MonitorAutoLock mon(mQueueMonitor);
|
|
nsresult rv = mShutdownTasks.RemoveTask(aTask);
|
|
if (mShutdownTasks.IsEmpty()) {
|
|
MaybeUnregisterTargetShutdownTask();
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
nsIEventTarget::FeatureFlags TaskQueue::GetFeatures() {
|
|
FeatureFlags supports = SUPPORTS_BASE;
|
|
nsCOMPtr<nsIEventTarget> target;
|
|
{
|
|
MonitorAutoLock mon(mQueueMonitor);
|
|
target = mTarget;
|
|
}
|
|
if (target) {
|
|
supports = target->GetFeatures();
|
|
}
|
|
// If the target does not SUPPORTS_SHUTDOWN_TASKS/_SHUTDOWN_TASK_DISPATCH, we
|
|
// still support SHUTDOWN_TASKS but we cannot guarantee they're executed on
|
|
// target shutdown. See bug 2011046 where we might want to change this.
|
|
return supports | SUPPORTS_SHUTDOWN_TASKS;
|
|
}
|
|
|
|
void TaskQueue::AwaitIdle() {
|
|
MonitorAutoLock mon(mQueueMonitor);
|
|
AwaitIdleLocked();
|
|
}
|
|
|
|
void TaskQueue::AwaitIdleLocked() {
|
|
// Make sure there are no tasks for this queue waiting in the caller's tail
|
|
// dispatcher.
|
|
MOZ_ASSERT_IF(AbstractThread::GetCurrent(),
|
|
!AbstractThread::GetCurrent()->HasTailTasksFor(this));
|
|
|
|
mQueueMonitor.AssertCurrentThreadOwns();
|
|
MOZ_ASSERT(mIsRunning || mTasks.IsEmpty());
|
|
while (mIsRunning) {
|
|
mQueueMonitor.Wait();
|
|
}
|
|
LOG_TQ(LogLevel::Debug, "TaskQueue::AwaitIdleLocked '%s' is now idle", mName);
|
|
}
|
|
|
|
void TaskQueue::AwaitShutdownAndIdle() {
|
|
MOZ_ASSERT(!IsCurrentThreadIn());
|
|
// Make sure there are no tasks for this queue waiting in the caller's tail
|
|
// dispatcher.
|
|
MOZ_ASSERT_IF(AbstractThread::GetCurrent(),
|
|
!AbstractThread::GetCurrent()->HasTailTasksFor(this));
|
|
|
|
MonitorAutoLock mon(mQueueMonitor);
|
|
while (!mIsShutdown) {
|
|
mQueueMonitor.Wait();
|
|
}
|
|
AwaitIdleLocked();
|
|
}
|
|
|
|
RefPtr<ShutdownPromise> TaskQueue::BeginShutdown() {
|
|
LOG_TQ(LogLevel::Debug, "TaskQueue::BeginShutdown '%s'", mName);
|
|
// Dispatch any tasks for this queue waiting in the caller's tail dispatcher,
|
|
// since this is the last opportunity to do so.
|
|
if (AbstractThread* currentThread = AbstractThread::GetCurrent()) {
|
|
currentThread->TailDispatchTasksFor(this);
|
|
}
|
|
|
|
MonitorAutoLock mon(mQueueMonitor);
|
|
if (!mIsShutdown) {
|
|
MaybeUnregisterTargetShutdownTask();
|
|
// Dispatch all cleanup tasks to the queue before we put it into full
|
|
// shutdown.
|
|
TargetShutdownTaskSet::TasksArray tasks = mShutdownTasks.Extract();
|
|
for (auto& task : tasks) {
|
|
LOG_TQ(LogLevel::Debug,
|
|
"TaskQueue::BeginShutdown '%s' dispatching shutdown task %p",
|
|
mName, task.get());
|
|
nsCOMPtr runnable{task->AsRunnable()};
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
DispatchLocked(runnable, NS_DISPATCH_NORMAL, TailDispatch));
|
|
}
|
|
mIsShutdown = true;
|
|
}
|
|
|
|
RefPtr<ShutdownPromise> p = mShutdownPromise.Ensure(__func__);
|
|
MaybeResolveShutdown();
|
|
mon.NotifyAll();
|
|
return p;
|
|
}
|
|
|
|
void TaskQueue::MaybeResolveShutdown() {
|
|
mQueueMonitor.AssertCurrentThreadOwns();
|
|
if (mIsShutdown && !mIsRunning) {
|
|
LOG_TQ(LogLevel::Debug, "TaskQueue::MaybeResolveShutdown '%s' resolve",
|
|
mName);
|
|
MOZ_ASSERT(!mIsTargetShutdownTaskRegistered);
|
|
mShutdownPromise.ResolveIfExists(true, __func__);
|
|
// Disconnect from our target as we won't try to dispatch any more events.
|
|
mTarget = nullptr;
|
|
mObserver = nullptr;
|
|
}
|
|
}
|
|
|
|
bool TaskQueue::IsEmpty() {
|
|
MonitorAutoLock mon(mQueueMonitor);
|
|
// NOTE: If `mTasks` is empty, we may still have queued tasks which are owned
|
|
// by the active runner. As we will always start running as soon as a task has
|
|
// been added, we can skip checking `mTasks` and can just check `mIsRunning`.
|
|
return !mIsRunning;
|
|
}
|
|
|
|
bool TaskQueue::IsCurrentThreadIn() const {
|
|
bool in = mRunningThread == PR_GetCurrentThread();
|
|
return in;
|
|
}
|
|
|
|
void TaskQueue::SetObserver(Observer* aObserver) {
|
|
MonitorAutoLock mon(mQueueMonitor);
|
|
MOZ_ASSERT_IF(aObserver, !mObserver);
|
|
mObserver = std::move(aObserver);
|
|
}
|
|
|
|
nsresult TaskQueue::Runner::Run() {
|
|
MOZ_ASSERT(mNextTask < mTasks.Length(), "No tasks to do?");
|
|
|
|
// Process mTasks[mNextTask] with an AutoTaskGuard on the stack.
|
|
{
|
|
AutoTaskGuard g(mQueue, mObserver);
|
|
|
|
TaskStruct& task = mTasks[mNextTask++];
|
|
MOZ_ASSERT(task.event);
|
|
|
|
LogRunnable::Run log(task.event);
|
|
|
|
AUTO_PROFILE_FOLLOWING_RUNNABLE(task.event);
|
|
task.event->Run();
|
|
|
|
// Drop the reference to task.event before AutoTaskGuard is destroyed. The
|
|
// event will hold a reference to the object it's calling, and we don't want
|
|
// to keep it alive, it may be making assumptions what holds references to
|
|
// it. This is especially the case if the object is waiting for us to
|
|
// shutdown, so that it can shutdown (like in the MediaDecoderStateMachine's
|
|
// SHUTDOWN case).
|
|
task.event = nullptr;
|
|
}
|
|
|
|
// If mTasks is exhausted, check to see if new tasks have been dispatched.
|
|
if (mNextTask >= mTasks.Length()) {
|
|
MonitorAutoLock mon(mQueue->mQueueMonitor);
|
|
MOZ_ASSERT(mQueue->mIsRunning);
|
|
|
|
// If mQueue->mTasks is empty, we're done for now, and can stop running.
|
|
if (mQueue->mTasks.IsEmpty()) {
|
|
mQueue->mIsRunning = false;
|
|
mQueue->MaybeResolveShutdown();
|
|
mon.NotifyAll();
|
|
return NS_OK;
|
|
}
|
|
|
|
// Otherwise, reload from mQueue and continue running.
|
|
mTarget = mQueue->mTarget;
|
|
mObserver = mQueue->mObserver;
|
|
mTasks = std::move(mQueue->mTasks);
|
|
mNextTask = 0;
|
|
}
|
|
|
|
// We still have tasks to execute. Dispatch this Runner to the target to
|
|
// ensure it runs again. Note that we don't just run in a loop here. This
|
|
// keeps the ratio of Runnables dispatched to the TaskQueue and the underlying
|
|
// EventTarget as 1:1, hopefully avoiding flooding.
|
|
MOZ_ASSERT(mNextTask < mTasks.Length());
|
|
nsresult rv =
|
|
mTarget->Dispatch(this, mTasks[mNextTask].flags | NS_DISPATCH_AT_END |
|
|
NS_DISPATCH_FALLIBLE);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("Underlying EventTarget for TaskQueue not accepting new tasks");
|
|
|
|
// The Dispatch to mTarget failed. Unfortunately we cannot recover at this
|
|
// point, so shut down the TaskQueue, as we can no longer accept new events.
|
|
MonitorAutoLock mon(mQueue->mQueueMonitor);
|
|
mQueue->mIsRunning = false;
|
|
mQueue->mIsShutdown = true;
|
|
mQueue->MaybeUnregisterTargetShutdownTask();
|
|
mQueue->MaybeResolveShutdown();
|
|
mon.NotifyAll();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// nsIDirectTaskDispatcher
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
TaskQueue::DispatchDirectTask(already_AddRefed<nsIRunnable> aEvent) {
|
|
if (!IsCurrentThreadIn()) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
mDirectTasks.AddTask(std::move(aEvent));
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP TaskQueue::DrainDirectTasks() {
|
|
if (!IsCurrentThreadIn()) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
mDirectTasks.DrainTasks();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP TaskQueue::HaveDirectTasks(bool* aValue) {
|
|
if (!IsCurrentThreadIn()) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
*aValue = mDirectTasks.HaveTasks();
|
|
return NS_OK;
|
|
}
|
|
|
|
#undef LOG_TQ
|
|
|
|
} // namespace mozilla
|