gecko/xpcom/ds/nsObserverService.cpp
Nathan Froyd ae0f2861dd Bug 919672 - lower the suspect referent count for the observer service's memory reporter; r=njn,wchen
I have a couple score (hundreds?) of open tabs and have been noticing of late
that the observer service has 4k+ observers for such a session.  I was a bit
surprised by this, and lowered the suspect referent count a bit to see if I
could determine what was going on.

There's nothing particularly unusual with my setup, but I'd like to propose
that the count be lowered somewhat anyway.  I think it's useful for about:memory
to provide as much information as possible about what might be going on.  I've
also tried to update the explanatory text to indicate that merely having high
counts is not necessarily symptomatic of a leak.
2013-09-23 15:09:26 -04:00

360 lines
11 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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 "prlog.h"
#include "nsAutoPtr.h"
#include "nsIObserverService.h"
#include "nsIObserver.h"
#include "nsObserverService.h"
#include "nsObserverList.h"
#include "nsThreadUtils.h"
#include "nsEnumeratorUtils.h"
#include "nsIMemoryReporter.h"
#include "mozilla/net/NeckoCommon.h"
#include "mozilla/Services.h"
#define NOTIFY_GLOBAL_OBSERVERS
#if defined(PR_LOGGING)
// Log module for nsObserverService logging...
//
// To enable logging (see prlog.h for full details):
//
// set NSPR_LOG_MODULES=ObserverService:5
// set NSPR_LOG_FILE=nspr.log
//
// this enables PR_LOG_DEBUG level information and places all output in
// the file nspr.log
static PRLogModuleInfo*
GetObserverServiceLog()
{
static PRLogModuleInfo *sLog;
if (!sLog)
sLog = PR_NewLogModule("ObserverService");
return sLog;
}
#define LOG(x) PR_LOG(GetObserverServiceLog(), PR_LOG_DEBUG, x)
#else
#define LOG(x)
#endif /* PR_LOGGING */
namespace mozilla {
class ObserverServiceReporter MOZ_FINAL : public nsIMemoryReporter
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIMEMORYREPORTER
protected:
static const size_t kSuspectReferentCount = 100;
static PLDHashOperator CountReferents(nsObserverList* aObserverList,
void* aClosure);
};
NS_IMPL_ISUPPORTS1(ObserverServiceReporter, nsIMemoryReporter)
NS_IMETHODIMP
ObserverServiceReporter::GetName(nsACString& aName)
{
aName.AssignLiteral("observer-service");
return NS_OK;
}
struct SuspectObserver {
SuspectObserver(const char* aTopic, size_t aReferentCount)
: topic(aTopic), referentCount(aReferentCount) {}
const char* topic;
size_t referentCount;
};
struct ObserverServiceReferentCount {
ObserverServiceReferentCount()
: numStrong(0), numWeakAlive(0), numWeakDead(0) {}
size_t numStrong;
size_t numWeakAlive;
size_t numWeakDead;
nsTArray<SuspectObserver> suspectObservers;
};
PLDHashOperator
ObserverServiceReporter::CountReferents(nsObserverList* aObserverList,
void* aClosure)
{
if (!aObserverList) {
return PL_DHASH_NEXT;
}
ObserverServiceReferentCount* referentCount =
static_cast<ObserverServiceReferentCount*>(aClosure);
size_t numStrong = 0;
size_t numWeakAlive = 0;
size_t numWeakDead = 0;
nsTArray<ObserverRef>& observers = aObserverList->mObservers;
for (uint32_t i = 0; i < observers.Length(); i++) {
if (observers[i].isWeakRef) {
nsCOMPtr<nsIObserver> observerRef(
do_QueryReferent(observers[i].asWeak()));
if (observerRef) {
numWeakAlive++;
} else {
numWeakDead++;
}
} else {
numStrong++;
}
}
referentCount->numStrong += numStrong;
referentCount->numWeakAlive += numWeakAlive;
referentCount->numWeakDead += numWeakDead;
// Keep track of topics that have a suspiciously large number
// of referents (symptom of leaks).
size_t total = numStrong + numWeakAlive + numWeakDead;
if (total > kSuspectReferentCount) {
SuspectObserver suspect(aObserverList->GetKey(), total);
referentCount->suspectObservers.AppendElement(suspect);
}
return PL_DHASH_NEXT;
}
NS_IMETHODIMP
ObserverServiceReporter::CollectReports(nsIMemoryReporterCallback* cb,
nsISupports* aClosure)
{
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
nsObserverService* service = static_cast<nsObserverService*>(os.get());
if (!service) {
return NS_OK;
}
ObserverServiceReferentCount referentCount;
service->mObserverTopicTable.EnumerateEntries(CountReferents,
&referentCount);
nsresult rv;
for (uint32_t i = 0; i < referentCount.suspectObservers.Length(); i++) {
SuspectObserver& suspect = referentCount.suspectObservers[i];
nsPrintfCString suspectPath("observer-service-suspect/"
"referent(topic=%s)",
suspect.topic);
rv = cb->Callback(/* process */ EmptyCString(),
suspectPath,
nsIMemoryReporter::KIND_OTHER,
nsIMemoryReporter::UNITS_COUNT,
suspect.referentCount,
NS_LITERAL_CSTRING("A topic with a suspiciously large number of "
"referents. This may be symptomatic of a leak "
"if the number of referents is high with "
"respect to the number of windows."),
aClosure);
NS_ENSURE_SUCCESS(rv, rv);
}
rv = cb->Callback(/* process */ EmptyCString(),
NS_LITERAL_CSTRING("observer-service/referent/strong"),
nsIMemoryReporter::KIND_OTHER,
nsIMemoryReporter::UNITS_COUNT,
referentCount.numStrong,
NS_LITERAL_CSTRING("The number of strong references held by the "
"observer service."),
aClosure);
NS_ENSURE_SUCCESS(rv, rv);
rv = cb->Callback(/* process */ EmptyCString(),
NS_LITERAL_CSTRING("observer-service/referent/weak/alive"),
nsIMemoryReporter::KIND_OTHER,
nsIMemoryReporter::UNITS_COUNT,
referentCount.numWeakAlive,
NS_LITERAL_CSTRING("The number of weak references held by the "
"observer service that are still alive."),
aClosure);
NS_ENSURE_SUCCESS(rv, rv);
rv = cb->Callback(/* process */ EmptyCString(),
NS_LITERAL_CSTRING("observer-service/referent/weak/dead"),
nsIMemoryReporter::KIND_OTHER,
nsIMemoryReporter::UNITS_COUNT,
referentCount.numWeakDead,
NS_LITERAL_CSTRING("The number of weak references held by the "
"observer service that are dead."),
aClosure);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
} // namespace mozilla
using namespace mozilla;
////////////////////////////////////////////////////////////////////////////////
// nsObserverService Implementation
NS_IMPL_ISUPPORTS2(nsObserverService, nsIObserverService, nsObserverService)
nsObserverService::nsObserverService() :
mShuttingDown(false), mReporter(nullptr)
{
}
nsObserverService::~nsObserverService(void)
{
Shutdown();
}
void
nsObserverService::RegisterReporter()
{
mReporter = new ObserverServiceReporter();
NS_RegisterMemoryReporter(mReporter);
}
void
nsObserverService::Shutdown()
{
if (mReporter) {
NS_UnregisterMemoryReporter(mReporter);
}
mShuttingDown = true;
mObserverTopicTable.Clear();
}
nsresult
nsObserverService::Create(nsISupports* outer, const nsIID& aIID, void* *aInstancePtr)
{
LOG(("nsObserverService::Create()"));
nsRefPtr<nsObserverService> os = new nsObserverService();
if (!os)
return NS_ERROR_OUT_OF_MEMORY;
// The memory reporter can not be immediately registered here because
// the nsMemoryReporterManager may attempt to get the nsObserverService
// during initialization, causing a recursive GetService.
nsRefPtr<nsRunnableMethod<nsObserverService> > registerRunnable =
NS_NewRunnableMethod(os, &nsObserverService::RegisterReporter);
NS_DispatchToCurrentThread(registerRunnable);
return os->QueryInterface(aIID, aInstancePtr);
}
#define NS_ENSURE_VALIDCALL \
if (!NS_IsMainThread()) { \
NS_ERROR("Using observer service off the main thread!"); \
return NS_ERROR_UNEXPECTED; \
} \
if (mShuttingDown) { \
NS_ERROR("Using observer service after XPCOM shutdown!"); \
return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; \
}
NS_IMETHODIMP
nsObserverService::AddObserver(nsIObserver* anObserver, const char* aTopic,
bool ownsWeak)
{
LOG(("nsObserverService::AddObserver(%p: %s)",
(void*) anObserver, aTopic));
NS_ENSURE_VALIDCALL
NS_ENSURE_ARG(anObserver && aTopic);
if (mozilla::net::IsNeckoChild() && !strncmp(aTopic, "http-on-", 8)) {
return NS_ERROR_NOT_IMPLEMENTED;
}
nsObserverList *observerList = mObserverTopicTable.PutEntry(aTopic);
if (!observerList)
return NS_ERROR_OUT_OF_MEMORY;
return observerList->AddObserver(anObserver, ownsWeak);
}
NS_IMETHODIMP
nsObserverService::RemoveObserver(nsIObserver* anObserver, const char* aTopic)
{
LOG(("nsObserverService::RemoveObserver(%p: %s)",
(void*) anObserver, aTopic));
NS_ENSURE_VALIDCALL
NS_ENSURE_ARG(anObserver && aTopic);
nsObserverList *observerList = mObserverTopicTable.GetEntry(aTopic);
if (!observerList)
return NS_ERROR_FAILURE;
/* This death grip is to protect against stupid consumers who call
RemoveObserver from their Destructor, see bug 485834/bug 325392. */
nsCOMPtr<nsIObserver> kungFuDeathGrip(anObserver);
return observerList->RemoveObserver(anObserver);
}
NS_IMETHODIMP
nsObserverService::EnumerateObservers(const char* aTopic,
nsISimpleEnumerator** anEnumerator)
{
NS_ENSURE_VALIDCALL
NS_ENSURE_ARG(aTopic && anEnumerator);
nsObserverList *observerList = mObserverTopicTable.GetEntry(aTopic);
if (!observerList)
return NS_NewEmptyEnumerator(anEnumerator);
return observerList->GetObserverList(anEnumerator);
}
// Enumerate observers of aTopic and call Observe on each.
NS_IMETHODIMP nsObserverService::NotifyObservers(nsISupports *aSubject,
const char *aTopic,
const PRUnichar *someData)
{
LOG(("nsObserverService::NotifyObservers(%s)", aTopic));
NS_ENSURE_VALIDCALL
NS_ENSURE_ARG(aTopic);
nsObserverList *observerList = mObserverTopicTable.GetEntry(aTopic);
if (observerList)
observerList->NotifyObservers(aSubject, aTopic, someData);
#ifdef NOTIFY_GLOBAL_OBSERVERS
observerList = mObserverTopicTable.GetEntry("*");
if (observerList)
observerList->NotifyObservers(aSubject, aTopic, someData);
#endif
return NS_OK;
}
static PLDHashOperator
UnmarkGrayObserverEntry(nsObserverList* aObserverList, void* aClosure)
{
if (aObserverList) {
aObserverList->UnmarkGrayStrongObservers();
}
return PL_DHASH_NEXT;
}
NS_IMETHODIMP
nsObserverService::UnmarkGrayStrongObservers()
{
NS_ENSURE_VALIDCALL
mObserverTopicTable.EnumerateEntries(UnmarkGrayObserverEntry, nullptr);
return NS_OK;
}