Bug 1886022 - Refactor canvas shutdown to account for process crashes. r=gfx-reviewers,lsalzman

We previously refactor canvas shutdown to account for the fact that they
needed to be shutdown in conjunction with the DOM worker reference
kept alive by the CanvasManagerChild. Unfortunately if the compositor
process crashes, or otherwise the CanvasManagerChild actor is torn down,
we also prematurely shutdown the canvas when it would previously
fallback to Skia in the content process.

This patch abstracts out canvas shutdown into the CanvasShutdownManager
which has the owning reference to the ThreadSafeWorkerRef. It corrects a
similar bug on the main thread as well for HTMLCanvasElement.

Differential Revision: https://phabricator.services.mozilla.com/D204988
This commit is contained in:
Andrew Osmond 2024-03-19 14:09:13 +00:00
Родитель 47c14cf7a4
Коммит 0a149a309c
8 изменённых файлов: 176 добавлений и 105 удалений

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

@ -24,7 +24,7 @@
#include "mozilla/dom/HTMLCanvasElement.h"
#include "mozilla/dom/GeneratePlaceholderCanvasData.h"
#include "mozilla/dom/VideoFrame.h"
#include "mozilla/gfx/CanvasManagerChild.h"
#include "mozilla/gfx/CanvasShutdownManager.h"
#include "nsPresContext.h"
#include "nsIInterfaceRequestorUtils.h"
@ -872,41 +872,6 @@ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasGradient, mContext)
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasPattern, mContext)
class CanvasShutdownObserver final : public nsIObserver {
public:
explicit CanvasShutdownObserver(CanvasRenderingContext2D* aCanvas)
: mCanvas(aCanvas) {}
void OnShutdown() {
if (!mCanvas) {
return;
}
mCanvas = nullptr;
nsContentUtils::UnregisterShutdownObserver(this);
}
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
private:
~CanvasShutdownObserver() = default;
CanvasRenderingContext2D* mCanvas;
};
NS_IMPL_ISUPPORTS(CanvasShutdownObserver, nsIObserver)
NS_IMETHODIMP
CanvasShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) {
if (mCanvas && strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
mCanvas->OnShutdown();
OnShutdown();
}
return NS_OK;
}
NS_IMPL_CYCLE_COLLECTING_ADDREF(CanvasRenderingContext2D)
NS_IMPL_CYCLE_COLLECTING_RELEASE(CanvasRenderingContext2D)
@ -1243,14 +1208,8 @@ void CanvasRenderingContext2D::OnShutdown() {
}
bool CanvasRenderingContext2D::AddShutdownObserver() {
auto* const canvasManager = CanvasManagerChild::Get();
auto* const canvasManager = CanvasShutdownManager::Get();
if (NS_WARN_IF(!canvasManager)) {
if (NS_IsMainThread()) {
mShutdownObserver = new CanvasShutdownObserver(this);
nsContentUtils::RegisterShutdownObserver(mShutdownObserver);
return true;
}
mHasShutdown = true;
return false;
}
@ -1260,13 +1219,7 @@ bool CanvasRenderingContext2D::AddShutdownObserver() {
}
void CanvasRenderingContext2D::RemoveShutdownObserver() {
if (mShutdownObserver) {
mShutdownObserver->OnShutdown();
mShutdownObserver = nullptr;
return;
}
auto* const canvasManager = CanvasManagerChild::MaybeGet();
auto* const canvasManager = CanvasShutdownManager::MaybeGet();
if (!canvasManager) {
return;
}

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

@ -846,7 +846,6 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal,
// Whether or not we have already shutdown.
bool mHasShutdown = false;
RefPtr<CanvasShutdownObserver> mShutdownObserver;
bool AddShutdownObserver();
void RemoveShutdownObserver();
bool AlreadyShutDown() const { return mHasShutdown; }

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

@ -10,6 +10,7 @@
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerRef.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/CanvasShutdownManager.h"
#include "mozilla/gfx/Swizzle.h"
#include "mozilla/ipc/Endpoint.h"
#include "mozilla/layers/ActiveResource.h"
@ -30,7 +31,10 @@ MOZ_THREAD_LOCAL(CanvasManagerChild*) CanvasManagerChild::sLocalManager;
Atomic<uint32_t> CanvasManagerChild::sNextId(1);
CanvasManagerChild::CanvasManagerChild(uint32_t aId) : mId(aId) {}
CanvasManagerChild::CanvasManagerChild(ThreadSafeWorkerRef* aWorkerRef,
uint32_t aId)
: mWorkerRef(aWorkerRef), mId(aId) {}
CanvasManagerChild::~CanvasManagerChild() = default;
void CanvasManagerChild::ActorDestroy(ActorDestroyReason aReason) {
@ -42,11 +46,6 @@ void CanvasManagerChild::ActorDestroy(ActorDestroyReason aReason) {
}
void CanvasManagerChild::DestroyInternal() {
std::set<CanvasRenderingContext2D*> activeCanvas = std::move(mActiveCanvas);
for (const auto& i : activeCanvas) {
i->OnShutdown();
}
if (mActiveResourceTracker) {
mActiveResourceTracker->AgeAllGenerations();
mActiveResourceTracker.reset();
@ -67,12 +66,6 @@ void CanvasManagerChild::Destroy() {
}
/* static */ void CanvasManagerChild::Shutdown() {
MOZ_ASSERT(NS_IsMainThread());
// The worker threads should destroy their own CanvasManagerChild instances
// during their shutdown sequence. We just need to take care of the main
// thread. We need to init here because we may have never created a
// CanvasManagerChild for the main thread in the first place.
if (sLocalManager.init()) {
RefPtr<CanvasManagerChild> manager = sLocalManager.get();
if (manager) {
@ -103,6 +96,11 @@ void CanvasManagerChild::Destroy() {
return managerWeak;
}
auto* shutdownManager = CanvasShutdownManager::Get();
if (NS_WARN_IF(!shutdownManager)) {
return nullptr;
}
// We are only used on the main thread, or on worker threads.
WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
MOZ_ASSERT_IF(!worker, NS_IsMainThread());
@ -121,29 +119,8 @@ void CanvasManagerChild::Destroy() {
return nullptr;
}
auto manager = MakeRefPtr<CanvasManagerChild>(sNextId++);
if (worker) {
// The ThreadSafeWorkerRef will let us know when the worker is shutting
// down. This will let us clear our threadlocal reference and close the
// actor. We rely upon an explicit shutdown for the main thread.
RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
worker, "CanvasManager", [manager]() { manager->Destroy(); });
if (NS_WARN_IF(!workerRef)) {
return nullptr;
}
manager->mWorkerRef = new ThreadSafeWorkerRef(workerRef);
} else if (NS_IsMainThread()) {
if (NS_WARN_IF(
AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed))) {
return nullptr;
}
} else {
MOZ_ASSERT_UNREACHABLE("Can only be used on main or DOM worker threads!");
return nullptr;
}
auto manager = MakeRefPtr<CanvasManagerChild>(shutdownManager->GetWorkerRef(),
sNextId++);
if (NS_WARN_IF(!childEndpoint.Bind(manager))) {
return nullptr;
}
@ -175,16 +152,6 @@ void CanvasManagerChild::Destroy() {
return sLocalManager.get();
}
void CanvasManagerChild::AddShutdownObserver(
dom::CanvasRenderingContext2D* aCanvas) {
mActiveCanvas.insert(aCanvas);
}
void CanvasManagerChild::RemoveShutdownObserver(
dom::CanvasRenderingContext2D* aCanvas) {
mActiveCanvas.erase(aCanvas);
}
void CanvasManagerChild::EndCanvasTransaction() {
if (!mCanvasChild) {
return;

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

@ -10,7 +10,6 @@
#include "mozilla/gfx/PCanvasManagerChild.h"
#include "mozilla/gfx/Types.h"
#include "mozilla/ThreadLocal.h"
#include <set>
namespace mozilla {
namespace dom {
@ -35,7 +34,8 @@ class CanvasManagerChild final : public PCanvasManagerChild {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CanvasManagerChild, override);
explicit CanvasManagerChild(uint32_t aId);
explicit CanvasManagerChild(dom::ThreadSafeWorkerRef* aWorkerRef,
uint32_t aId);
uint32_t Id() const { return mId; }
already_AddRefed<DataSourceSurface> GetSnapshot(
uint32_t aManagerId, int32_t aProtocolId,
@ -49,9 +49,6 @@ class CanvasManagerChild final : public PCanvasManagerChild {
static bool CreateParent(
mozilla::ipc::Endpoint<PCanvasManagerParent>&& aEndpoint);
void AddShutdownObserver(dom::CanvasRenderingContext2D* aCanvas);
void RemoveShutdownObserver(dom::CanvasRenderingContext2D* aCanvas);
bool IsCanvasActive() { return mActive; }
void EndCanvasTransaction();
void ClearCachedResources();
@ -73,7 +70,6 @@ class CanvasManagerChild final : public PCanvasManagerChild {
RefPtr<layers::CanvasChild> mCanvasChild;
RefPtr<webgpu::WebGPUChild> mWebGPUChild;
UniquePtr<layers::ActiveResourceTracker> mActiveResourceTracker;
std::set<dom::CanvasRenderingContext2D*> mActiveCanvas;
const uint32_t mId;
bool mActive = true;
bool mBlocked = false;

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

@ -0,0 +1,108 @@
/* -*- 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 "CanvasShutdownManager.h"
#include "mozilla/AppShutdown.h"
#include "mozilla/dom/CanvasRenderingContext2D.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerRef.h"
#include "mozilla/gfx/CanvasManagerChild.h"
using namespace mozilla::dom;
namespace mozilla::gfx {
// The owning thread will tell us to close when it is shutdown, either via
// CanvasShutdownManager::Shutdown for the main thread, or via a shutdown
// callback from ThreadSafeWorkerRef for worker threads.
MOZ_THREAD_LOCAL(CanvasShutdownManager*) CanvasShutdownManager::sLocalManager;
CanvasShutdownManager::CanvasShutdownManager(StrongWorkerRef* aWorkerRef)
: mWorkerRef(new ThreadSafeWorkerRef(aWorkerRef)) {}
CanvasShutdownManager::CanvasShutdownManager() = default;
CanvasShutdownManager::~CanvasShutdownManager() = default;
void CanvasShutdownManager::Destroy() {
std::set<CanvasRenderingContext2D*> activeCanvas = std::move(mActiveCanvas);
for (const auto& i : activeCanvas) {
i->OnShutdown();
}
CanvasManagerChild::Shutdown();
mWorkerRef = nullptr;
}
/* static */ void CanvasShutdownManager::Shutdown() {
auto* manager = MaybeGet();
if (!manager) {
return;
}
sLocalManager.set(nullptr);
manager->Destroy();
delete manager;
}
/* static */ CanvasShutdownManager* CanvasShutdownManager::MaybeGet() {
if (NS_WARN_IF(!sLocalManager.init())) {
return nullptr;
}
return sLocalManager.get();
}
/* static */ CanvasShutdownManager* CanvasShutdownManager::Get() {
if (NS_WARN_IF(!sLocalManager.init())) {
return nullptr;
}
CanvasShutdownManager* managerWeak = sLocalManager.get();
if (managerWeak) {
return managerWeak;
}
if (WorkerPrivate* worker = GetCurrentThreadWorkerPrivate()) {
// The ThreadSafeWorkerRef will let us know when the worker is shutting
// down. This will let us clear our threadlocal reference and close the
// actor. We rely upon an explicit shutdown for the main thread.
RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
worker, "CanvasShutdownManager", []() { Shutdown(); });
if (NS_WARN_IF(!workerRef)) {
return nullptr;
}
CanvasShutdownManager* manager = new CanvasShutdownManager(workerRef);
sLocalManager.set(manager);
return manager;
}
if (NS_IsMainThread()) {
if (NS_WARN_IF(
AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed))) {
return nullptr;
}
CanvasShutdownManager* manager = new CanvasShutdownManager();
sLocalManager.set(manager);
return manager;
}
MOZ_ASSERT_UNREACHABLE("Can only be used on main or DOM worker threads!");
return nullptr;
}
void CanvasShutdownManager::AddShutdownObserver(
dom::CanvasRenderingContext2D* aCanvas) {
mActiveCanvas.insert(aCanvas);
}
void CanvasShutdownManager::RemoveShutdownObserver(
dom::CanvasRenderingContext2D* aCanvas) {
mActiveCanvas.erase(aCanvas);
}
} // namespace mozilla::gfx

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

@ -0,0 +1,46 @@
/* -*- 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 _include_gfx_ipc_CanvasShutdownManager_h__
#define _include_gfx_ipc_CanvasShutdownManager_h__
#include "mozilla/RefPtr.h"
#include "mozilla/ThreadLocal.h"
#include <set>
namespace mozilla {
namespace dom {
class CanvasRenderingContext2D;
class StrongWorkerRef;
class ThreadSafeWorkerRef;
} // namespace dom
namespace gfx {
class CanvasShutdownManager final {
public:
static CanvasShutdownManager* Get();
static CanvasShutdownManager* MaybeGet();
static void Shutdown();
dom::ThreadSafeWorkerRef* GetWorkerRef() const { return mWorkerRef; }
void AddShutdownObserver(dom::CanvasRenderingContext2D* aCanvas);
void RemoveShutdownObserver(dom::CanvasRenderingContext2D* aCanvas);
private:
explicit CanvasShutdownManager(dom::StrongWorkerRef* aWorkerRef);
CanvasShutdownManager();
~CanvasShutdownManager();
void Destroy();
RefPtr<dom::ThreadSafeWorkerRef> mWorkerRef;
std::set<dom::CanvasRenderingContext2D*> mActiveCanvas;
static MOZ_THREAD_LOCAL(CanvasShutdownManager*) sLocalManager;
};
} // namespace gfx
} // namespace mozilla
#endif // _include_gfx_ipc_CanvasShutdownManager_h__

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

@ -13,6 +13,7 @@ EXPORTS.mozilla.gfx += [
"CanvasManagerChild.h",
"CanvasManagerParent.h",
"CanvasRenderThread.h",
"CanvasShutdownManager.h",
"CrossProcessPaint.h",
"FileHandleWrapper.h",
"GPUChild.h",
@ -42,6 +43,7 @@ UNIFIED_SOURCES += [
"CanvasManagerChild.cpp",
"CanvasManagerParent.cpp",
"CanvasRenderThread.cpp",
"CanvasShutdownManager.cpp",
"CompositorSession.cpp",
"CompositorWidgetVsyncObserver.cpp",
"CrossProcessPaint.cpp",

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

@ -21,8 +21,8 @@
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/gfx/GPUProcessManager.h"
#include "mozilla/gfx/GraphicsMessages.h"
#include "mozilla/gfx/CanvasManagerChild.h"
#include "mozilla/gfx/CanvasRenderThread.h"
#include "mozilla/gfx/CanvasShutdownManager.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/EnumTypeTraits.h"
@ -1343,7 +1343,7 @@ void gfxPlatform::ShutdownLayersIPC() {
if (XRE_IsContentProcess()) {
gfx::VRManagerChild::ShutDown();
gfx::CanvasManagerChild::Shutdown();
gfx::CanvasShutdownManager::Shutdown();
// cf bug 1215265.
if (StaticPrefs::layers_child_process_shutdown()) {
layers::CompositorManagerChild::Shutdown();
@ -1354,7 +1354,7 @@ void gfxPlatform::ShutdownLayersIPC() {
VideoBridgeParent::Shutdown();
RDDProcessManager::RDDProcessShutdown();
gfx::VRManagerChild::ShutDown();
gfx::CanvasManagerChild::Shutdown();
gfx::CanvasShutdownManager::Shutdown();
layers::CompositorManagerChild::Shutdown();
layers::ImageBridgeChild::ShutDown();
// This could be running on either the Compositor thread, the Renderer