Files
firefox/image/DecodePool.cpp
T
Jens Stutte 603e064255 Bug 2025772 - Stop running image decode tasks during shutdown. r=tnikkel
Image decode tasks dispatched to TaskController pool threads can still
run after xpcom-shutdown-threads, because TaskController is only shut
down very late in the process teardown sequence. This causes
NotifyDecodeComplete and DropImageReference to attempt dispatching to the
main thread after it has stopped accepting events, triggering a
!mRawPtr assertion in already_AddRefed's destructor.

Check DecodePool::IsShuttingDown() in DecodingTask::Run() before running
the task, matching what AnimationSurfaceProvider already does internally.

Also update the comment in SurfaceCache::ReleaseImageOnMainThread to
clarify that the shutdown path is unreachable in release builds and the
(void)aImage cast is intentional to preserve the DEBUG assertion.

Differential Revision: https://phabricator.services.mozilla.com/D290391
2026-03-29 06:33:27 +00:00

211 lines
5.5 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 "DecodePool.h"
#include <algorithm>
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Monitor.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/SchedulerGroup.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPrefs_image.h"
#include "mozilla/TaskController.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/AppShutdown.h"
#include "nsCOMPtr.h"
#include "nsIObserverService.h"
#include "nsThreadManager.h"
#include "nsThreadUtils.h"
#include "nsXPCOMCIDInternal.h"
#include "prsystem.h"
#include "Decoder.h"
#include "IDecodingTask.h"
#include "RasterImage.h"
#if defined(XP_WIN)
# include <objbase.h>
# include "mozilla/WindowsProcessMitigations.h"
#endif
using std::max;
using std::min;
namespace mozilla {
namespace image {
///////////////////////////////////////////////////////////////////////////////
// DecodePool implementation.
///////////////////////////////////////////////////////////////////////////////
/* static */
StaticRefPtr<DecodePool> DecodePool::sSingleton;
/* static */
uint32_t DecodePool::sNumCores = 0;
NS_IMPL_ISUPPORTS(DecodePool, nsIObserver)
/* static */
void DecodePool::Initialize() {
MOZ_ASSERT(NS_IsMainThread());
sNumCores = max<int32_t>(PR_GetNumberOfProcessors(), 1);
DecodePool::Singleton();
}
/* static */
DecodePool* DecodePool::Singleton() {
if (!sSingleton) {
MOZ_ASSERT(NS_IsMainThread());
sSingleton = new DecodePool();
ClearOnShutdown(&sSingleton);
}
return sSingleton;
}
/* static */
uint32_t DecodePool::NumberOfCores() { return sNumCores; }
#if defined(XP_WIN)
class IOThreadIniter final : public Runnable {
public:
explicit IOThreadIniter() : Runnable("image::IOThreadIniter") {}
NS_IMETHOD Run() override {
MOZ_ASSERT(!NS_IsMainThread());
CoInitialize(nullptr);
return NS_OK;
}
};
#endif
DecodePool::DecodePool() : mMutex("image::IOThread") {
// Initialize the I/O thread.
#if defined(XP_WIN)
// On Windows we use the io thread to get icons from the system. Any thread
// that makes system calls needs to call CoInitialize. And these system calls
// (SHGetFileInfo) should only be called from one thread at a time, in case
// we ever create more than one io thread. If win32k is locked down, we can't
// call SHGetFileInfo anyway, so we don't need the initializer.
nsCOMPtr<nsIRunnable> initer =
IsWin32kLockedDown() ? nullptr : new IOThreadIniter();
nsresult rv = NS_NewNamedThread("ImageIO", getter_AddRefs(mIOThread), initer);
#else
nsresult rv = NS_NewNamedThread("ImageIO", getter_AddRefs(mIOThread));
#endif
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && mIOThread,
"Should successfully create image I/O thread");
nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
if (obsSvc) {
obsSvc->AddObserver(this, "xpcom-shutdown-threads", false);
}
}
DecodePool::~DecodePool() {
MOZ_ASSERT(NS_IsMainThread(), "Must shut down DecodePool on main thread!");
}
NS_IMETHODIMP
DecodePool::Observe(nsISupports*, const char* aTopic, const char16_t*) {
MOZ_ASSERT(strcmp(aTopic, "xpcom-shutdown-threads") == 0, "Unexpected topic");
mShuttingDown = true;
nsCOMPtr<nsIThread> ioThread;
{
MutexAutoLock lock(mMutex);
ioThread.swap(mIOThread);
}
if (ioThread) {
ioThread->Shutdown();
}
return NS_OK;
}
/* static */ bool DecodePool::IsShuttingDown() {
if (MOZ_UNLIKELY(!sSingleton)) {
return AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownThreads);
}
return sSingleton->mShuttingDown;
}
class DecodingTask final : public Task {
public:
explicit DecodingTask(RefPtr<IDecodingTask>&& aTask)
: Task(Kind::OffMainThreadOnly, aTask->Priority() == TaskPriority::eLow
? EventQueuePriority::Normal
: EventQueuePriority::RenderBlocking),
mTask(aTask) {}
TaskResult Run() override {
if (MOZ_LIKELY(!DecodePool::IsShuttingDown())) {
mTask->Run();
}
return TaskResult::Complete;
}
#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
bool GetName(nsACString& aName) override {
aName.AssignLiteral("ImageDecodingTask");
return true;
}
#endif
private:
RefPtr<IDecodingTask> mTask;
};
void DecodePool::AsyncRun(IDecodingTask* aTask) {
MOZ_ASSERT(aTask);
TaskController::Get()->AddTask(
MakeAndAddRef<DecodingTask>((RefPtr<IDecodingTask>(aTask))));
}
bool DecodePool::SyncRunIfPreferred(IDecodingTask* aTask,
const nsCString& aURI) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aTask);
AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("DecodePool::SyncRunIfPreferred",
GRAPHICS, aURI);
if (aTask->ShouldPreferSyncRun()) {
aTask->Run();
return true;
}
AsyncRun(aTask);
return false;
}
void DecodePool::SyncRunIfPossible(IDecodingTask* aTask,
const nsCString& aURI) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aTask);
AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("DecodePool::SyncRunIfPossible",
GRAPHICS, aURI);
aTask->Run();
}
already_AddRefed<nsISerialEventTarget> DecodePool::GetIOEventTarget() {
MutexAutoLock threadPoolLock(mMutex);
nsCOMPtr<nsISerialEventTarget> target = mIOThread;
return target.forget();
}
} // namespace image
} // namespace mozilla