This is a follow-up to bug 1409249. There are a lot of places where our factory singleton constructors either don't correctly handle their returned references being released by the component manager, or do handle it, but in ways that are not obvious. This patch handles a few places where we can sometimes wind up with dangling singleton pointers, adds some explanatory comments and sanity check assertions, and replaces some uses of manual refcounting with StaticRefPtr and ClearOnShutdown. There are still some places where we may wind up with odd behavior if the first QI for a getService call fails. In those cases, we wind up destroying the first instance of a service that we create, and re-creating a new one later. MozReview-Commit-ID: ANYndvd7aZx
365 lines
9.2 KiB
C++
365 lines
9.2 KiB
C++
/* -*- 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 "WorkerDebuggerManager.h"
|
|
|
|
#include "nsISimpleEnumerator.h"
|
|
|
|
#include "mozilla/ClearOnShutdown.h"
|
|
#include "mozilla/StaticPtr.h"
|
|
|
|
#include "WorkerPrivate.h"
|
|
|
|
USING_WORKERS_NAMESPACE
|
|
|
|
namespace {
|
|
|
|
class RegisterDebuggerMainThreadRunnable final : public mozilla::Runnable
|
|
{
|
|
WorkerPrivate* mWorkerPrivate;
|
|
bool mNotifyListeners;
|
|
|
|
public:
|
|
RegisterDebuggerMainThreadRunnable(WorkerPrivate* aWorkerPrivate,
|
|
bool aNotifyListeners)
|
|
: mozilla::Runnable("RegisterDebuggerMainThreadRunnable")
|
|
, mWorkerPrivate(aWorkerPrivate)
|
|
, mNotifyListeners(aNotifyListeners)
|
|
{ }
|
|
|
|
private:
|
|
~RegisterDebuggerMainThreadRunnable()
|
|
{ }
|
|
|
|
NS_IMETHOD
|
|
Run() override
|
|
{
|
|
WorkerDebuggerManager* manager = WorkerDebuggerManager::Get();
|
|
MOZ_ASSERT(manager);
|
|
|
|
manager->RegisterDebuggerMainThread(mWorkerPrivate, mNotifyListeners);
|
|
return NS_OK;
|
|
}
|
|
};
|
|
|
|
class UnregisterDebuggerMainThreadRunnable final : public mozilla::Runnable
|
|
{
|
|
WorkerPrivate* mWorkerPrivate;
|
|
|
|
public:
|
|
explicit UnregisterDebuggerMainThreadRunnable(WorkerPrivate* aWorkerPrivate)
|
|
: mozilla::Runnable("UnregisterDebuggerMainThreadRunnable")
|
|
, mWorkerPrivate(aWorkerPrivate)
|
|
{ }
|
|
|
|
private:
|
|
~UnregisterDebuggerMainThreadRunnable()
|
|
{ }
|
|
|
|
NS_IMETHOD
|
|
Run() override
|
|
{
|
|
WorkerDebuggerManager* manager = WorkerDebuggerManager::Get();
|
|
MOZ_ASSERT(manager);
|
|
|
|
manager->UnregisterDebuggerMainThread(mWorkerPrivate);
|
|
return NS_OK;
|
|
}
|
|
};
|
|
|
|
static StaticRefPtr<WorkerDebuggerManager> gWorkerDebuggerManager;
|
|
|
|
} /* anonymous namespace */
|
|
|
|
BEGIN_WORKERS_NAMESPACE
|
|
|
|
class WorkerDebuggerEnumerator final : public nsISimpleEnumerator
|
|
{
|
|
nsTArray<RefPtr<WorkerDebugger>> mDebuggers;
|
|
uint32_t mIndex;
|
|
|
|
public:
|
|
explicit WorkerDebuggerEnumerator(
|
|
const nsTArray<RefPtr<WorkerDebugger>>& aDebuggers)
|
|
: mDebuggers(aDebuggers), mIndex(0)
|
|
{
|
|
}
|
|
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSISIMPLEENUMERATOR
|
|
|
|
private:
|
|
~WorkerDebuggerEnumerator() {}
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(WorkerDebuggerEnumerator, nsISimpleEnumerator);
|
|
|
|
NS_IMETHODIMP
|
|
WorkerDebuggerEnumerator::HasMoreElements(bool* aResult)
|
|
{
|
|
*aResult = mIndex < mDebuggers.Length();
|
|
return NS_OK;
|
|
};
|
|
|
|
NS_IMETHODIMP
|
|
WorkerDebuggerEnumerator::GetNext(nsISupports** aResult)
|
|
{
|
|
if (mIndex == mDebuggers.Length()) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
mDebuggers.ElementAt(mIndex++).forget(aResult);
|
|
return NS_OK;
|
|
};
|
|
|
|
WorkerDebuggerManager::WorkerDebuggerManager()
|
|
: mMutex("WorkerDebuggerManager::mMutex")
|
|
{
|
|
AssertIsOnMainThread();
|
|
}
|
|
|
|
WorkerDebuggerManager::~WorkerDebuggerManager()
|
|
{
|
|
AssertIsOnMainThread();
|
|
}
|
|
|
|
// static
|
|
already_AddRefed<WorkerDebuggerManager>
|
|
WorkerDebuggerManager::GetInstance()
|
|
{
|
|
RefPtr<WorkerDebuggerManager> manager = WorkerDebuggerManager::GetOrCreate();
|
|
return manager.forget();
|
|
}
|
|
|
|
// static
|
|
WorkerDebuggerManager*
|
|
WorkerDebuggerManager::GetOrCreate()
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
if (!gWorkerDebuggerManager) {
|
|
// The observer service now owns us until shutdown.
|
|
gWorkerDebuggerManager = new WorkerDebuggerManager();
|
|
if (NS_SUCCEEDED(gWorkerDebuggerManager->Init())) {
|
|
ClearOnShutdown(&gWorkerDebuggerManager);
|
|
} else {
|
|
NS_WARNING("Failed to initialize worker debugger manager!");
|
|
gWorkerDebuggerManager = nullptr;
|
|
}
|
|
}
|
|
|
|
return gWorkerDebuggerManager;
|
|
}
|
|
|
|
WorkerDebuggerManager*
|
|
WorkerDebuggerManager::Get()
|
|
{
|
|
MOZ_ASSERT(gWorkerDebuggerManager);
|
|
return gWorkerDebuggerManager;
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(WorkerDebuggerManager, nsIObserver, nsIWorkerDebuggerManager);
|
|
|
|
NS_IMETHODIMP
|
|
WorkerDebuggerManager::Observe(nsISupports* aSubject, const char* aTopic,
|
|
const char16_t* aData)
|
|
{
|
|
if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
|
|
Shutdown();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_NOTREACHED("Unknown observer topic!");
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
WorkerDebuggerManager::GetWorkerDebuggerEnumerator(
|
|
nsISimpleEnumerator** aResult)
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
RefPtr<WorkerDebuggerEnumerator> enumerator =
|
|
new WorkerDebuggerEnumerator(mDebuggers);
|
|
enumerator.forget(aResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
WorkerDebuggerManager::AddListener(nsIWorkerDebuggerManagerListener* aListener)
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
MutexAutoLock lock(mMutex);
|
|
|
|
if (mListeners.Contains(aListener)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
mListeners.AppendElement(aListener);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
WorkerDebuggerManager::RemoveListener(
|
|
nsIWorkerDebuggerManagerListener* aListener)
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
MutexAutoLock lock(mMutex);
|
|
|
|
if (!mListeners.Contains(aListener)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
mListeners.RemoveElement(aListener);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
WorkerDebuggerManager::Init()
|
|
{
|
|
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
|
|
NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE);
|
|
|
|
nsresult rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
WorkerDebuggerManager::Shutdown()
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
MutexAutoLock lock(mMutex);
|
|
|
|
mListeners.Clear();
|
|
}
|
|
|
|
void
|
|
WorkerDebuggerManager::RegisterDebugger(WorkerPrivate* aWorkerPrivate)
|
|
{
|
|
aWorkerPrivate->AssertIsOnParentThread();
|
|
|
|
if (NS_IsMainThread()) {
|
|
// When the parent thread is the main thread, it will always block until all
|
|
// register liseners have been called, since it cannot continue until the
|
|
// call to RegisterDebuggerMainThread returns.
|
|
//
|
|
// In this case, it is always safe to notify all listeners on the main
|
|
// thread, even if there were no listeners at the time this method was
|
|
// called, so we can always pass true for the value of aNotifyListeners.
|
|
// This avoids having to lock mMutex to check whether mListeners is empty.
|
|
RegisterDebuggerMainThread(aWorkerPrivate, true);
|
|
} else {
|
|
// We guarantee that if any register listeners are called, the worker does
|
|
// not start running until all register listeners have been called. To
|
|
// guarantee this, the parent thread should block until all register
|
|
// listeners have been called.
|
|
//
|
|
// However, to avoid overhead when the debugger is not being used, the
|
|
// parent thread will only block if there were any listeners at the time
|
|
// this method was called. As a result, we should not notify any listeners
|
|
// on the main thread if there were no listeners at the time this method was
|
|
// called, because the parent will not be blocking in that case.
|
|
bool hasListeners = false;
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
|
|
hasListeners = !mListeners.IsEmpty();
|
|
}
|
|
|
|
nsCOMPtr<nsIRunnable> runnable =
|
|
new RegisterDebuggerMainThreadRunnable(aWorkerPrivate, hasListeners);
|
|
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL));
|
|
|
|
if (hasListeners) {
|
|
aWorkerPrivate->WaitForIsDebuggerRegistered(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
WorkerDebuggerManager::UnregisterDebugger(WorkerPrivate* aWorkerPrivate)
|
|
{
|
|
aWorkerPrivate->AssertIsOnParentThread();
|
|
|
|
if (NS_IsMainThread()) {
|
|
UnregisterDebuggerMainThread(aWorkerPrivate);
|
|
} else {
|
|
nsCOMPtr<nsIRunnable> runnable =
|
|
new UnregisterDebuggerMainThreadRunnable(aWorkerPrivate);
|
|
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL));
|
|
|
|
aWorkerPrivate->WaitForIsDebuggerRegistered(false);
|
|
}
|
|
}
|
|
|
|
void
|
|
WorkerDebuggerManager::RegisterDebuggerMainThread(WorkerPrivate* aWorkerPrivate,
|
|
bool aNotifyListeners)
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
RefPtr<WorkerDebugger> debugger = new WorkerDebugger(aWorkerPrivate);
|
|
mDebuggers.AppendElement(debugger);
|
|
|
|
aWorkerPrivate->SetDebugger(debugger);
|
|
|
|
if (aNotifyListeners) {
|
|
nsTArray<nsCOMPtr<nsIWorkerDebuggerManagerListener>> listeners;
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
|
|
listeners = mListeners;
|
|
}
|
|
|
|
for (size_t index = 0; index < listeners.Length(); ++index) {
|
|
listeners[index]->OnRegister(debugger);
|
|
}
|
|
}
|
|
|
|
aWorkerPrivate->SetIsDebuggerRegistered(true);
|
|
}
|
|
|
|
void
|
|
WorkerDebuggerManager::UnregisterDebuggerMainThread(
|
|
WorkerPrivate* aWorkerPrivate)
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
// There is nothing to do here if the debugger was never succesfully
|
|
// registered. We need to check this on the main thread because the worker
|
|
// does not wait for the registration to complete if there were no listeners
|
|
// installed when it started.
|
|
if (!aWorkerPrivate->IsDebuggerRegistered()) {
|
|
return;
|
|
}
|
|
|
|
RefPtr<WorkerDebugger> debugger = aWorkerPrivate->Debugger();
|
|
mDebuggers.RemoveElement(debugger);
|
|
|
|
aWorkerPrivate->SetDebugger(nullptr);
|
|
|
|
nsTArray<nsCOMPtr<nsIWorkerDebuggerManagerListener>> listeners;
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
|
|
listeners = mListeners;
|
|
}
|
|
|
|
for (size_t index = 0; index < listeners.Length(); ++index) {
|
|
listeners[index]->OnUnregister(debugger);
|
|
}
|
|
|
|
debugger->Close();
|
|
aWorkerPrivate->SetIsDebuggerRegistered(false);
|
|
}
|
|
|
|
END_WORKERS_NAMESPACE
|