Bug 1875661 - Refactor shutdown for remote canvas. r=gfx-reviewers,lsalzman

This patch makes it so that we always shutdown gracefully by making
us flush the event queue for CanvasRenderThread, after blocking on the
task queues draining. This allows our dependencies like RemoteTextureMap
to shutdown successfully.

It also fixes a bug where CanvasTranslator::CanSend would still return
true on Windows, while blocked on in CanvasTranslator::ActorDestroy
waiting for the task queue to shutdown. The CanSend status is only
updated after ActorDestroy is called from IProtocol::DestroySubtree.

Differential Revision: https://phabricator.services.mozilla.com/D199186
This commit is contained in:
Andrew Osmond
2024-01-25 22:22:49 +00:00
parent bf13e43144
commit 62d98779e8
8 changed files with 110 additions and 53 deletions

View File

@@ -34,7 +34,8 @@ static bool sCanvasRenderThreadEverStarted = false;
CanvasRenderThread::CanvasRenderThread(nsCOMPtr<nsIThread>&& aThread, CanvasRenderThread::CanvasRenderThread(nsCOMPtr<nsIThread>&& aThread,
nsCOMPtr<nsIThreadPool>&& aWorkers, nsCOMPtr<nsIThreadPool>&& aWorkers,
bool aCreatedThread) bool aCreatedThread)
: mThread(std::move(aThread)), : mMutex("CanvasRenderThread::mMutex"),
mThread(std::move(aThread)),
mWorkers(std::move(aWorkers)), mWorkers(std::move(aWorkers)),
mCreatedThread(aCreatedThread) {} mCreatedThread(aCreatedThread) {}
@@ -148,16 +149,39 @@ void CanvasRenderThread::Shutdown() {
return; return;
} }
// This closes all of the IPDL actors with possibly active task queues.
CanvasManagerParent::Shutdown(); CanvasManagerParent::Shutdown();
// Any task queues that are in the process of shutting down are tracked in
// mPendingShutdownTaskQueues. We need to block on each one until all events
// are flushed so that we can safely teardown RemoteTextureMap afterwards.
while (true) {
RefPtr<TaskQueue> taskQueue;
{
MutexAutoLock lock(sCanvasRenderThread->mMutex);
auto& pendingQueues = sCanvasRenderThread->mPendingShutdownTaskQueues;
if (pendingQueues.IsEmpty()) {
break;
}
taskQueue = pendingQueues.PopLastElement();
}
taskQueue->AwaitShutdownAndIdle();
}
bool createdThread = sCanvasRenderThread->mCreatedThread;
nsCOMPtr<nsIThread> oldThread = sCanvasRenderThread->GetCanvasRenderThread();
nsCOMPtr<nsIThreadPool> oldWorkers = sCanvasRenderThread->mWorkers;
// Ensure that we flush the CanvasRenderThread event queue before clearing our
// singleton.
NS_DispatchAndSpinEventLoopUntilComplete(
"CanvasRenderThread::Shutdown"_ns, oldThread,
NS_NewRunnableFunction("CanvasRenderThread::Shutdown", []() -> void {}));
// Null out sCanvasRenderThread before we enter synchronous Shutdown, // Null out sCanvasRenderThread before we enter synchronous Shutdown,
// from here on we are to be considered shut down for our consumers. // from here on we are to be considered shut down for our consumers.
nsCOMPtr<nsIThreadPool> oldWorkers = sCanvasRenderThread->mWorkers;
nsCOMPtr<nsIThread> oldThread;
if (sCanvasRenderThread->mCreatedThread) {
oldThread = sCanvasRenderThread->GetCanvasRenderThread();
MOZ_ASSERT(oldThread);
}
sCanvasRenderThread = nullptr; sCanvasRenderThread = nullptr;
if (oldWorkers) { if (oldWorkers) {
@@ -166,7 +190,7 @@ void CanvasRenderThread::Shutdown() {
// We do a synchronous shutdown here while spinning the MT event loop, but // We do a synchronous shutdown here while spinning the MT event loop, but
// only if we created a dedicated CanvasRender thread. // only if we created a dedicated CanvasRender thread.
if (oldThread) { if (createdThread) {
oldThread->Shutdown(); oldThread->Shutdown();
} }
} }
@@ -216,6 +240,32 @@ CanvasRenderThread::CreateWorkerTaskQueue() {
.forget(); .forget();
} }
/* static */ void CanvasRenderThread::ShutdownWorkerTaskQueue(
TaskQueue* aTaskQueue) {
MOZ_ASSERT(aTaskQueue);
aTaskQueue->BeginShutdown();
if (!sCanvasRenderThread) {
MOZ_ASSERT_UNREACHABLE("No CanvasRenderThread!");
return;
}
MutexAutoLock lock(sCanvasRenderThread->mMutex);
auto& pendingQueues = sCanvasRenderThread->mPendingShutdownTaskQueues;
pendingQueues.AppendElement(aTaskQueue);
}
/* static */ void CanvasRenderThread::FinishShutdownWorkerTaskQueue(
TaskQueue* aTaskQueue) {
if (!sCanvasRenderThread) {
return;
}
MutexAutoLock lock(sCanvasRenderThread->mMutex);
sCanvasRenderThread->mPendingShutdownTaskQueues.RemoveElement(aTaskQueue);
}
/* static */ void CanvasRenderThread::Dispatch( /* static */ void CanvasRenderThread::Dispatch(
already_AddRefed<nsIRunnable> aRunnable) { already_AddRefed<nsIRunnable> aRunnable) {
if (!sCanvasRenderThread) { if (!sCanvasRenderThread) {

View File

@@ -8,8 +8,10 @@
#define _include_gfx_ipc_CanvasRenderThread_h__ #define _include_gfx_ipc_CanvasRenderThread_h__
#include "mozilla/AlreadyAddRefed.h" #include "mozilla/AlreadyAddRefed.h"
#include "mozilla/Mutex.h"
#include "nsCOMPtr.h" #include "nsCOMPtr.h"
#include "nsISupportsImpl.h" #include "nsISupportsImpl.h"
#include "nsTArray.h"
class nsIRunnable; class nsIRunnable;
class nsIThread; class nsIThread;
@@ -53,6 +55,10 @@ class CanvasRenderThread final {
static already_AddRefed<TaskQueue> CreateWorkerTaskQueue(); static already_AddRefed<TaskQueue> CreateWorkerTaskQueue();
static void ShutdownWorkerTaskQueue(TaskQueue* aTaskQueue);
static void FinishShutdownWorkerTaskQueue(TaskQueue* aTaskQueue);
static void Dispatch(already_AddRefed<nsIRunnable> aRunnable); static void Dispatch(already_AddRefed<nsIRunnable> aRunnable);
private: private:
@@ -60,13 +66,17 @@ class CanvasRenderThread final {
nsCOMPtr<nsIThreadPool>&& aWorkers, bool aCreatedThread); nsCOMPtr<nsIThreadPool>&& aWorkers, bool aCreatedThread);
~CanvasRenderThread(); ~CanvasRenderThread();
Mutex mMutex;
nsCOMPtr<nsIThread> const mThread; nsCOMPtr<nsIThread> const mThread;
nsCOMPtr<nsIThreadPool> const mWorkers; nsCOMPtr<nsIThreadPool> const mWorkers;
nsTArray<RefPtr<TaskQueue>> mPendingShutdownTaskQueues MOZ_GUARDED_BY(mMutex);
// True if mThread points to CanvasRender thread, false if mThread points to // True if mThread points to CanvasRender thread, false if mThread points to
// Compositor/Render thread. // Compositor/Render thread.
bool mCreatedThread; const bool mCreatedThread;
}; };
} // namespace gfx } // namespace gfx

View File

@@ -98,8 +98,7 @@ bool CanvasDrawEventRecorder::Init(TextureType aTextureType,
if (!mHelpers->InitTranslator(aTextureType, aBackendType, if (!mHelpers->InitTranslator(aTextureType, aBackendType,
std::move(header->handle), std::move(header->handle),
std::move(bufferHandles), mDefaultBufferSize, std::move(bufferHandles), mDefaultBufferSize,
std::move(readerSem), std::move(writerSem), std::move(readerSem), std::move(writerSem))) {
/* aUseIPDLThread */ false)) {
return false; return false;
} }

View File

@@ -67,11 +67,13 @@ class CanvasDrawEventRecorder final : public gfx::DrawEventRecorderPrivate,
public: public:
virtual ~Helpers() = default; virtual ~Helpers() = default;
virtual bool InitTranslator( virtual bool InitTranslator(TextureType aTextureType,
TextureType aTextureType, gfx::BackendType aBackendType, gfx::BackendType aBackendType,
Handle&& aReadHandle, nsTArray<Handle>&& aBufferHandles, Handle&& aReadHandle,
uint64_t aBufferSize, CrossProcessSemaphoreHandle&& aReaderSem, nsTArray<Handle>&& aBufferHandles,
CrossProcessSemaphoreHandle&& aWriterSem, bool aUseIPDLThread) = 0; uint64_t aBufferSize,
CrossProcessSemaphoreHandle&& aReaderSem,
CrossProcessSemaphoreHandle&& aWriterSem) = 0;
virtual bool AddBuffer(Handle&& aBufferHandle, uint64_t aBufferSize) = 0; virtual bool AddBuffer(Handle&& aBufferHandle, uint64_t aBufferSize) = 0;

View File

@@ -37,8 +37,7 @@ class RecorderHelpers final : public CanvasDrawEventRecorder::Helpers {
Handle&& aReadHandle, nsTArray<Handle>&& aBufferHandles, Handle&& aReadHandle, nsTArray<Handle>&& aBufferHandles,
uint64_t aBufferSize, uint64_t aBufferSize,
CrossProcessSemaphoreHandle&& aReaderSem, CrossProcessSemaphoreHandle&& aReaderSem,
CrossProcessSemaphoreHandle&& aWriterSem, CrossProcessSemaphoreHandle&& aWriterSem) override {
bool aUseIPDLThread) override {
NS_ASSERT_OWNINGTHREAD(RecorderHelpers); NS_ASSERT_OWNINGTHREAD(RecorderHelpers);
if (NS_WARN_IF(!mCanvasChild)) { if (NS_WARN_IF(!mCanvasChild)) {
return false; return false;
@@ -46,7 +45,7 @@ class RecorderHelpers final : public CanvasDrawEventRecorder::Helpers {
return mCanvasChild->SendInitTranslator( return mCanvasChild->SendInitTranslator(
aTextureType, aBackendType, std::move(aReadHandle), aTextureType, aBackendType, std::move(aReadHandle),
std::move(aBufferHandles), aBufferSize, std::move(aReaderSem), std::move(aBufferHandles), aBufferSize, std::move(aReaderSem),
std::move(aWriterSem), aUseIPDLThread); std::move(aWriterSem));
} }
bool AddBuffer(Handle&& aBufferHandle, uint64_t aBufferSize) override { bool AddBuffer(Handle&& aBufferHandle, uint64_t aBufferSize) override {

View File

@@ -66,7 +66,8 @@ UniquePtr<TextureData> CanvasTranslator::CreateTextureData(
CanvasTranslator::CanvasTranslator( CanvasTranslator::CanvasTranslator(
layers::SharedSurfacesHolder* aSharedSurfacesHolder, layers::SharedSurfacesHolder* aSharedSurfacesHolder,
const dom::ContentParentId& aContentId, uint32_t aManagerId) const dom::ContentParentId& aContentId, uint32_t aManagerId)
: mSharedSurfacesHolder(aSharedSurfacesHolder), : mTranslationTaskQueue(gfx::CanvasRenderThread::CreateWorkerTaskQueue()),
mSharedSurfacesHolder(aSharedSurfacesHolder),
mMaxSpinCount(StaticPrefs::gfx_canvas_remote_max_spin_count()), mMaxSpinCount(StaticPrefs::gfx_canvas_remote_max_spin_count()),
mContentId(aContentId), mContentId(aContentId),
mManagerId(aManagerId) { mManagerId(aManagerId) {
@@ -133,7 +134,7 @@ mozilla::ipc::IPCResult CanvasTranslator::RecvInitTranslator(
TextureType aTextureType, gfx::BackendType aBackendType, TextureType aTextureType, gfx::BackendType aBackendType,
Handle&& aReadHandle, nsTArray<Handle>&& aBufferHandles, Handle&& aReadHandle, nsTArray<Handle>&& aBufferHandles,
uint64_t aBufferSize, CrossProcessSemaphoreHandle&& aReaderSem, uint64_t aBufferSize, CrossProcessSemaphoreHandle&& aReaderSem,
CrossProcessSemaphoreHandle&& aWriterSem, bool aUseIPDLThread) { CrossProcessSemaphoreHandle&& aWriterSem) {
if (mHeaderShmem) { if (mHeaderShmem) {
return IPC_FAIL(this, "RecvInitTranslator called twice."); return IPC_FAIL(this, "RecvInitTranslator called twice.");
} }
@@ -167,10 +168,6 @@ mozilla::ipc::IPCResult CanvasTranslator::RecvInitTranslator(
<< "GFX: CanvasTranslator failed creating WebGL shared context"; << "GFX: CanvasTranslator failed creating WebGL shared context";
} }
if (!aUseIPDLThread) {
mTranslationTaskQueue = gfx::CanvasRenderThread::CreateWorkerTaskQueue();
}
// Use the first buffer as our current buffer. // Use the first buffer as our current buffer.
mDefaultBufferSize = aBufferSize; mDefaultBufferSize = aBufferSize;
auto handleIter = aBufferHandles.begin(); auto handleIter = aBufferHandles.begin();
@@ -365,20 +362,20 @@ void CanvasTranslator::NextBuffer() {
void CanvasTranslator::ActorDestroy(ActorDestroyReason why) { void CanvasTranslator::ActorDestroy(ActorDestroyReason why) {
MOZ_ASSERT(gfx::CanvasRenderThread::IsInCanvasRenderThread()); MOZ_ASSERT(gfx::CanvasRenderThread::IsInCanvasRenderThread());
if (!mTranslationTaskQueue) { // Since we might need to access the actor status off the owning IPDL thread,
gfx::CanvasRenderThread::Dispatch( // we need to cache it here.
NewRunnableMethod("CanvasTranslator::FinishShutdown", this, mIPDLClosed = true;
&CanvasTranslator::FinishShutdown));
DispatchToTaskQueue(NewRunnableMethod("CanvasTranslator::ClearTextureInfo",
this,
&CanvasTranslator::ClearTextureInfo));
if (mTranslationTaskQueue) {
gfx::CanvasRenderThread::ShutdownWorkerTaskQueue(mTranslationTaskQueue);
return; return;
} }
mTranslationTaskQueue->BeginShutdown();
mTranslationTaskQueue->AwaitShutdownAndIdle();
FinishShutdown();
} }
void CanvasTranslator::FinishShutdown() { ClearTextureInfo(); }
bool CanvasTranslator::CheckDeactivated() { bool CanvasTranslator::CheckDeactivated() {
if (mDeactivated) { if (mDeactivated) {
return true; return true;
@@ -471,7 +468,7 @@ void CanvasTranslator::CheckAndSignalWriter() {
// The writer is making a decision about whether to wait. So, we must // The writer is making a decision about whether to wait. So, we must
// wait until it has decided to avoid races. Check if the writer is // wait until it has decided to avoid races. Check if the writer is
// closed to avoid hangs. // closed to avoid hangs.
if (!CanSend()) { if (mIPDLClosed) {
return; return;
} }
continue; continue;
@@ -566,7 +563,7 @@ void CanvasTranslator::TranslateRecording() {
[&](RecordedEvent* recordedEvent) -> bool { [&](RecordedEvent* recordedEvent) -> bool {
// Make sure that the whole event was read from the stream. // Make sure that the whole event was read from the stream.
if (!mCurrentMemReader.good()) { if (!mCurrentMemReader.good()) {
if (!CanSend()) { if (mIPDLClosed) {
// The other side has closed only warn about read failure. // The other side has closed only warn about read failure.
gfxWarning() << "Failed to read event type: " gfxWarning() << "Failed to read event type: "
<< recordedEvent->GetType(); << recordedEvent->GetType();
@@ -605,7 +602,7 @@ void CanvasTranslator::TranslateRecording() {
case _typeenum: { \ case _typeenum: { \
auto e = _class(mCurrentMemReader); \ auto e = _class(mCurrentMemReader); \
if (!mCurrentMemReader.good()) { \ if (!mCurrentMemReader.good()) { \
if (!CanSend()) { \ if (mIPDLClosed) { \
/* The other side has closed only warn about read failure. */ \ /* The other side has closed only warn about read failure. */ \
gfxWarning() << "Failed to read event type: " << _typeenum; \ gfxWarning() << "Failed to read event type: " << _typeenum; \
} else { \ } else { \
@@ -1120,6 +1117,10 @@ void CanvasTranslator::ClearTextureInfo() {
mRemoteTextureOwner->UnregisterAllTextureOwners(); mRemoteTextureOwner->UnregisterAllTextureOwners();
mRemoteTextureOwner = nullptr; mRemoteTextureOwner = nullptr;
} }
if (mTranslationTaskQueue) {
gfx::CanvasRenderThread::FinishShutdownWorkerTaskQueue(
mTranslationTaskQueue);
}
} }
already_AddRefed<gfx::SourceSurface> CanvasTranslator::LookupExternalSurface( already_AddRefed<gfx::SourceSurface> CanvasTranslator::LookupExternalSurface(

View File

@@ -75,14 +75,14 @@ class CanvasTranslator final : public gfx::InlineTranslator,
* @param aBufferSize size of buffers and the default size * @param aBufferSize size of buffers and the default size
* @param aReaderSem reading blocked semaphore for the CanvasEventRingBuffer * @param aReaderSem reading blocked semaphore for the CanvasEventRingBuffer
* @param aWriterSem writing blocked semaphore for the CanvasEventRingBuffer * @param aWriterSem writing blocked semaphore for the CanvasEventRingBuffer
* @param aUseIPDLThread if true, use the IPDL thread instead of the worker
* pool for translation requests
*/ */
ipc::IPCResult RecvInitTranslator( ipc::IPCResult RecvInitTranslator(TextureType aTextureType,
TextureType aTextureType, gfx::BackendType aBackendType, gfx::BackendType aBackendType,
Handle&& aReadHandle, nsTArray<Handle>&& aBufferHandles, Handle&& aReadHandle,
uint64_t aBufferSize, CrossProcessSemaphoreHandle&& aReaderSem, nsTArray<Handle>&& aBufferHandles,
CrossProcessSemaphoreHandle&& aWriterSem, bool aUseIPDLThread); uint64_t aBufferSize,
CrossProcessSemaphoreHandle&& aReaderSem,
CrossProcessSemaphoreHandle&& aWriterSem);
/** /**
* Restart the translation from a Stopped state. * Restart the translation from a Stopped state.
@@ -285,8 +285,6 @@ class CanvasTranslator final : public gfx::InlineTranslator,
bool ReadPendingEvent(EventType& aEventType); bool ReadPendingEvent(EventType& aEventType);
void FinishShutdown();
bool CheckDeactivated(); bool CheckDeactivated();
void Deactivate(); void Deactivate();
@@ -326,8 +324,8 @@ class CanvasTranslator final : public gfx::InlineTranslator,
void ClearCachedResources(); void ClearCachedResources();
RefPtr<TaskQueue> mTranslationTaskQueue; const RefPtr<TaskQueue> mTranslationTaskQueue;
RefPtr<SharedSurfacesHolder> mSharedSurfacesHolder; const RefPtr<SharedSurfacesHolder> mSharedSurfacesHolder;
#if defined(XP_WIN) #if defined(XP_WIN)
RefPtr<ID3D11Device> mDevice; RefPtr<ID3D11Device> mDevice;
#endif #endif
@@ -384,6 +382,7 @@ class CanvasTranslator final : public gfx::InlineTranslator,
UniquePtr<gfx::DataSourceSurface::ScopedMap> mPreparedMap; UniquePtr<gfx::DataSourceSurface::ScopedMap> mPreparedMap;
Atomic<bool> mDeactivated{false}; Atomic<bool> mDeactivated{false};
Atomic<bool> mBlocked{false}; Atomic<bool> mBlocked{false};
Atomic<bool> mIPDLClosed{false};
bool mIsInTransaction = false; bool mIsInTransaction = false;
bool mDeviceResetInProgress = false; bool mDeviceResetInProgress = false;
}; };

View File

@@ -31,15 +31,12 @@ parent:
* handles for the initial buffers for translation. aBufferSize is the size of * handles for the initial buffers for translation. aBufferSize is the size of
* each aBufferHandles' memory and the default size. aReaderSem and aWriterSem * each aBufferHandles' memory and the default size. aReaderSem and aWriterSem
* are handles for the semaphores to handle waiting on either side. * are handles for the semaphores to handle waiting on either side.
* aUseIPDLThread if true, use the IPDL thread instead of the worker pool for
* translation requests
*/ */
async InitTranslator(TextureType aTextureType, BackendType aBackendType, async InitTranslator(TextureType aTextureType, BackendType aBackendType,
Handle aHeaderHandle, Handle[] aBufferHandles, Handle aHeaderHandle, Handle[] aBufferHandles,
uint64_t aBufferSize, uint64_t aBufferSize,
CrossProcessSemaphoreHandle aReaderSem, CrossProcessSemaphoreHandle aReaderSem,
CrossProcessSemaphoreHandle aWriterSem, CrossProcessSemaphoreHandle aWriterSem);
bool aUseIPDLThread);
/** /**
* Restart the translation from a Stopped state. * Restart the translation from a Stopped state.