From 0a149a309ceafb81d2509f352347a98b19cc89fc Mon Sep 17 00:00:00 2001 From: Andrew Osmond Date: Tue, 19 Mar 2024 14:09:13 +0000 Subject: [PATCH] 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 --- dom/canvas/CanvasRenderingContext2D.cpp | 53 +----------- dom/canvas/CanvasRenderingContext2D.h | 1 - gfx/ipc/CanvasManagerChild.cpp | 57 +++---------- gfx/ipc/CanvasManagerChild.h | 8 +- gfx/ipc/CanvasShutdownManager.cpp | 108 ++++++++++++++++++++++++ gfx/ipc/CanvasShutdownManager.h | 46 ++++++++++ gfx/ipc/moz.build | 2 + gfx/thebes/gfxPlatform.cpp | 6 +- 8 files changed, 176 insertions(+), 105 deletions(-) create mode 100644 gfx/ipc/CanvasShutdownManager.cpp create mode 100644 gfx/ipc/CanvasShutdownManager.h diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp index 4c7d5169685b..4ad484ec8a0b 100644 --- a/dom/canvas/CanvasRenderingContext2D.cpp +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -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; } diff --git a/dom/canvas/CanvasRenderingContext2D.h b/dom/canvas/CanvasRenderingContext2D.h index 6efa87c490b2..c42a94778ac3 100644 --- a/dom/canvas/CanvasRenderingContext2D.h +++ b/dom/canvas/CanvasRenderingContext2D.h @@ -846,7 +846,6 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal, // Whether or not we have already shutdown. bool mHasShutdown = false; - RefPtr mShutdownObserver; bool AddShutdownObserver(); void RemoveShutdownObserver(); bool AlreadyShutDown() const { return mHasShutdown; } diff --git a/gfx/ipc/CanvasManagerChild.cpp b/gfx/ipc/CanvasManagerChild.cpp index 4e9e2e6acfdc..493fde1013c6 100644 --- a/gfx/ipc/CanvasManagerChild.cpp +++ b/gfx/ipc/CanvasManagerChild.cpp @@ -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 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 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 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(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 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(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; diff --git a/gfx/ipc/CanvasManagerChild.h b/gfx/ipc/CanvasManagerChild.h index 6369d87ada7a..c59ba7c50201 100644 --- a/gfx/ipc/CanvasManagerChild.h +++ b/gfx/ipc/CanvasManagerChild.h @@ -10,7 +10,6 @@ #include "mozilla/gfx/PCanvasManagerChild.h" #include "mozilla/gfx/Types.h" #include "mozilla/ThreadLocal.h" -#include 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 GetSnapshot( uint32_t aManagerId, int32_t aProtocolId, @@ -49,9 +49,6 @@ class CanvasManagerChild final : public PCanvasManagerChild { static bool CreateParent( mozilla::ipc::Endpoint&& 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 mCanvasChild; RefPtr mWebGPUChild; UniquePtr mActiveResourceTracker; - std::set mActiveCanvas; const uint32_t mId; bool mActive = true; bool mBlocked = false; diff --git a/gfx/ipc/CanvasShutdownManager.cpp b/gfx/ipc/CanvasShutdownManager.cpp new file mode 100644 index 000000000000..68839582469f --- /dev/null +++ b/gfx/ipc/CanvasShutdownManager.cpp @@ -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 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 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 diff --git a/gfx/ipc/CanvasShutdownManager.h b/gfx/ipc/CanvasShutdownManager.h new file mode 100644 index 000000000000..fbf3e982967e --- /dev/null +++ b/gfx/ipc/CanvasShutdownManager.h @@ -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 + +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 mWorkerRef; + std::set mActiveCanvas; + static MOZ_THREAD_LOCAL(CanvasShutdownManager*) sLocalManager; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // _include_gfx_ipc_CanvasShutdownManager_h__ diff --git a/gfx/ipc/moz.build b/gfx/ipc/moz.build index b8a52079e417..cc007a8193c3 100644 --- a/gfx/ipc/moz.build +++ b/gfx/ipc/moz.build @@ -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", diff --git a/gfx/thebes/gfxPlatform.cpp b/gfx/thebes/gfxPlatform.cpp index d1b05a2b821c..c57564b0547b 100644 --- a/gfx/thebes/gfxPlatform.cpp +++ b/gfx/thebes/gfxPlatform.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