From 66a725f097547b6813050bf5daaf61ddbde44486 Mon Sep 17 00:00:00 2001 From: "Olli.Pettay%helsinki.fi" Date: Fri, 21 Sep 2007 08:54:36 +0000 Subject: [PATCH] Bug 373462, Bug 385322, Better scheduling of cycle collection/gc, r=jst, sr=sicking, a=sicking git-svn-id: svn://10.0.0.236/trunk@236430 18797224-902f-48f8-a5cc-f745e15eee43 --- mozilla/content/base/src/nsXMLHttpRequest.cpp | 3 + mozilla/dom/src/base/nsJSEnvironment.cpp | 156 +++++++++++++++++- mozilla/dom/src/base/nsJSEnvironment.h | 18 ++ mozilla/dom/src/base/nsWindowRoot.cpp | 82 ++++++++- mozilla/xpcom/base/nsCycleCollector.cpp | 18 +- mozilla/xpcom/base/nsCycleCollector.h | 3 +- 6 files changed, 262 insertions(+), 18 deletions(-) diff --git a/mozilla/content/base/src/nsXMLHttpRequest.cpp b/mozilla/content/base/src/nsXMLHttpRequest.cpp index 01a99f5a759..40e3c9dca96 100644 --- a/mozilla/content/base/src/nsXMLHttpRequest.cpp +++ b/mozilla/content/base/src/nsXMLHttpRequest.cpp @@ -56,6 +56,7 @@ #include "prprf.h" #include "nsIDOMEventListener.h" #include "nsIJSContextStack.h" +#include "nsJSEnvironment.h" #include "nsIScriptSecurityManager.h" #include "nsWeakPtr.h" #include "nsICharsetAlias.h" @@ -1776,6 +1777,7 @@ nsXMLHttpRequest::RequestCompleted() ChangeState(XML_HTTP_REQUEST_OPENED); } + nsJSContext::MaybeCC(PR_FALSE); return rv; } @@ -2318,6 +2320,7 @@ nsXMLHttpRequest::Error(nsIDOMEvent* aEvent) NotifyEventListeners(errorEventListeners, event); } + nsJSContext::MaybeCC(PR_FALSE); return NS_OK; } diff --git a/mozilla/dom/src/base/nsJSEnvironment.cpp b/mozilla/dom/src/base/nsJSEnvironment.cpp index db881ffc935..3776230584c 100644 --- a/mozilla/dom/src/base/nsJSEnvironment.cpp +++ b/mozilla/dom/src/base/nsJSEnvironment.cpp @@ -154,8 +154,26 @@ static PRLogModuleInfo* gJSDiagnostics; #define JAVASCRIPT nsIProgrammingLanguage::JAVASCRIPT +// The max number of delayed cycle collects.. +#define NS_MAX_DELAYED_CCOLLECT 45 +// The max number of user interaction notifications in inactive state before +// we try to call cycle collector more aggressively. +#define NS_CC_SOFT_LIMIT_INACTIVE 6 +// The max number of user interaction notifications in active state before +// we try to call cycle collector more aggressively. +#define NS_CC_SOFT_LIMIT_ACTIVE 12 +// When higher probability MaybeCC is used, the number of sDelayedCCollectCount +// is multiplied with this number. +#define NS_PROBABILITY_MULTIPLIER 3 +// Cycle collector should never run more often than this value +#define NS_MIN_CC_INTERVAL 10000 // ms + // if you add statics here, add them to the list in nsJSRuntime::Startup +static PRUint32 sDelayedCCollectCount; +static PRUint32 sCCollectCount; +static PRTime sPreviousCCTime; +static PRBool sPreviousCCDidCollect; static nsITimer *sGCTimer; static PRBool sReadyForGC; @@ -199,6 +217,75 @@ static nsICollation *gCollation; static nsIUnicodeDecoder *gDecoder; +// nsUserActivityObserver observes user-interaction-active and +// user-interaction-inactive notifications. It counts the number of +// notifications and if the number is bigger than NS_CC_SOFT_LIMIT_ACTIVE +// (in case the current notification is user-interaction-active) or +// NS_CC_SOFT_LIMIT_INACTIVE (current notification is user-interaction-inactive) +// MaybeCC is called with aHigherParameter set to PR_TRUE, otherwise PR_FALSE. +// +// When moving from active state to inactive, nsJSContext::CC() is called +// unless the timer related to page load is active. + +class nsUserActivityObserver : public nsIObserver +{ +public: + nsUserActivityObserver() + : mUserActivityCounter(0), mOldCCollectCount(0), mUserIsActive(PR_FALSE) {} + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER +private: + PRUint32 mUserActivityCounter; + PRUint32 mOldCCollectCount; + PRBool mUserIsActive; +}; + +NS_IMPL_ISUPPORTS1(nsUserActivityObserver, nsIObserver) + +NS_IMETHODIMP +nsUserActivityObserver::Observe(nsISupports* aSubject, const char* aTopic, + const PRUnichar* aData) +{ + if (mOldCCollectCount != sCCollectCount) { + mOldCCollectCount = sCCollectCount; + // Cycle collector was called between user interaction notifications, so + // we can reset the counter. + mUserActivityCounter = 0; + } + PRBool higherProbability = PR_FALSE; + ++mUserActivityCounter; + if (!strcmp(aTopic, "user-interaction-inactive")) { +#ifdef DEBUG_smaug + printf("user-interaction-inactive\n"); +#endif + if (mUserIsActive) { + mUserIsActive = PR_FALSE; + if (!sGCTimer) { + nsJSContext::CC(); + return NS_OK; + } + } + higherProbability = (mUserActivityCounter > NS_CC_SOFT_LIMIT_INACTIVE); + } else if (!strcmp(aTopic, "user-interaction-active")) { +#ifdef DEBUG_smaug + printf("user-interaction-active\n"); +#endif + mUserIsActive = PR_TRUE; + higherProbability = (mUserActivityCounter > NS_CC_SOFT_LIMIT_ACTIVE); + } else if (!strcmp(aTopic, "xpcom-shutdown")) { + nsCOMPtr obs = + do_GetService("@mozilla.org/observer-service;1"); + if (obs) { + obs->RemoveObserver(this, "user-interaction-active"); + obs->RemoveObserver(this, "user-interaction-inactive"); + obs->RemoveObserver(this, "xpcom-shutdown"); + } + return NS_OK; + } + nsJSContext::MaybeCC(higherProbability); + return NS_OK; +} + /**************************************************************** ************************** AutoFree **************************** ****************************************************************/ @@ -3220,6 +3307,50 @@ nsJSContext::PreserveWrapper(nsIXPConnectWrappedNative *aWrapper) return nsDOMClassInfo::PreserveNodeWrapper(aWrapper); } +//static +void +nsJSContext::CC() +{ +#ifdef DEBUG_smaug + printf("Will run cycle collector\n"); +#endif + sPreviousCCTime = PR_Now(); + sDelayedCCollectCount = 0; + ++sCCollectCount; + // nsCycleCollector_collect() will run a ::JS_GC() indirectly, so + // we do not explicitly call ::JS_GC() here. + sPreviousCCDidCollect = nsCycleCollector_collect(); +#ifdef DEBUG_smaug + printf("%s\n", sPreviousCCDidCollect ? + "Cycle collector did collect nodes" : + "Cycle collector did not collect nodes"); +#endif +} + +//static +void +nsJSContext::MaybeCC(PRBool aHigherProbability) +{ + ++sDelayedCCollectCount; + // Increase the probability also if the previous call to cycle collector + // collected something. + if (aHigherProbability || sPreviousCCDidCollect) { + sDelayedCCollectCount *= NS_PROBABILITY_MULTIPLIER; + } + + if (!sGCTimer && (sDelayedCCollectCount > NS_MAX_DELAYED_CCOLLECT)) { + if ((PR_Now() - sPreviousCCTime) >= + PRTime(NS_MIN_CC_INTERVAL * PR_USEC_PER_MSEC)) { + nsJSContext::CC(); + } +#ifdef DEBUG_smaug + else { + printf("Running cycle collector was delayed: NS_MIN_CC_INTERVAL\n"); + } +#endif + } +} + NS_IMETHODIMP nsJSContext::Notify(nsITimer *timer) { @@ -3238,9 +3369,7 @@ nsJSContext::Notify(nsITimer *timer) // loading and move on as if they weren't. sPendingLoadCount = 0; - // nsCycleCollector_collect() will run a ::JS_GC() indirectly, - // so we do not explicitly call ::JS_GC() here. - nsCycleCollector_collect(); + nsJSContext::MaybeCC(PR_TRUE); } else { FireGCTimer(PR_TRUE); } @@ -3273,9 +3402,7 @@ nsJSContext::LoadEnd() NS_RELEASE(sGCTimer); sLoadInProgressGCTimer = PR_FALSE; - // nsCycleCollector_collect() will run a ::JS_GC() indirectly, so - // we do not explicitly call ::JS_GC() here. - nsCycleCollector_collect(); + nsJSContext::MaybeCC(PR_TRUE); } } @@ -3302,9 +3429,7 @@ nsJSContext::FireGCTimer(PRBool aLoadInProgress) // timer. sLoadInProgressGCTimer = PR_FALSE; - // nsCycleCollector_collect() will run a ::JS_GC() indirectly, so - // we do not explicitly call ::JS_GC() here. - nsCycleCollector_collect(); + nsJSContext::MaybeCC(PR_TRUE); return; } @@ -3413,6 +3538,10 @@ void nsJSRuntime::Startup() { // initialize all our statics, so that we can restart XPCOM + sDelayedCCollectCount = 0; + sCCollectCount = 0; + sPreviousCCTime = 0; + sPreviousCCDidCollect = PR_FALSE; sGCTimer = nsnull; sReadyForGC = PR_FALSE; sLoadInProgressGCTimer = PR_FALSE; @@ -3547,6 +3676,15 @@ nsJSRuntime::Init() MaxScriptRunTimePrefChangedCallback("dom.max_chrome_script_run_time", nsnull); + nsCOMPtr obs = + do_GetService("@mozilla.org/observer-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsIObserver* activityObserver = new nsUserActivityObserver(); + NS_ENSURE_TRUE(activityObserver, NS_ERROR_OUT_OF_MEMORY); + obs->AddObserver(activityObserver, "user-interaction-inactive", PR_FALSE); + obs->AddObserver(activityObserver, "user-interaction-active", PR_FALSE); + obs->AddObserver(activityObserver, "xpcom-shutdown", PR_FALSE); + rv = CallGetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &sSecurityManager); sIsInitialized = NS_SUCCEEDED(rv); diff --git a/mozilla/dom/src/base/nsJSEnvironment.h b/mozilla/dom/src/base/nsJSEnvironment.h index 01d0187f8da..2662e0fecb5 100644 --- a/mozilla/dom/src/base/nsJSEnvironment.h +++ b/mozilla/dom/src/base/nsJSEnvironment.h @@ -170,6 +170,24 @@ public: static void LoadStart(); static void LoadEnd(); + // CC does always call cycle collector and it also updates the counters + // that MaybeCC uses. + static void CC(); + + // MaybeCC calls cycle collector if certain conditions are fulfilled. + // The conditions are: + // - The timer related to page load (sGCTimer) must not be active. + // - At least NS_MIN_CC_INTERVAL milliseconds must have elapsed since the + // previous cycle collector call. + // - Certain number of MaybeCC calls have occurred. + // The number of needed MaybeCC calls depends on the aHigherProbability + // parameter. If the parameter is true, probability for calling cycle + // collector rises increasingly. If the parameter is all the time false, + // at least NS_MAX_DELAYED_CCOLLECT MaybeCC calls are needed. + // If the previous call to cycle collector did collect something, + // MaybeCC works effectively as if aHigherProbability was true. + static void MaybeCC(PRBool aHigherProbability); + protected: nsresult InitializeExternalClasses(); nsresult InitializeLiveConnectClasses(JSObject *aGlobalObj); diff --git a/mozilla/dom/src/base/nsWindowRoot.cpp b/mozilla/dom/src/base/nsWindowRoot.cpp index 3ffdd6b5953..aa30af094bd 100644 --- a/mozilla/dom/src/base/nsWindowRoot.cpp +++ b/mozilla/dom/src/base/nsWindowRoot.cpp @@ -54,10 +54,49 @@ #include "nsString.h" #include "nsEventDispatcher.h" #include "nsIProgrammingLanguage.h" - +#include "nsIObserverService.h" +#include "nsServiceManagerUtils.h" +#include "nsITimer.h" #include "nsCycleCollectionParticipant.h" +#define NS_USER_INTERACTION_INTERVAL 5000 // ms + static NS_DEFINE_CID(kEventListenerManagerCID, NS_EVENTLISTENERMANAGER_CID); +static PRUint32 gMouseOrKeyboardEventCounter = 0; +static PRUint32 gWindowRootCount = 0; +static nsITimer* gUserInteractionTimer = nsnull; +static nsITimerCallback* gUserInteractionTimerCallback = nsnull; + +class nsUITimerCallback : public nsITimerCallback +{ +public: + nsUITimerCallback() : mPreviousCount(0) {} + NS_DECL_ISUPPORTS + NS_DECL_NSITIMERCALLBACK +private: + PRUint32 mPreviousCount; +}; + +NS_IMPL_ISUPPORTS1(nsUITimerCallback, nsITimerCallback) + +// If aTimer is nsnull, this method always sends "user-interaction-inactive" +// notification. +NS_IMETHODIMP +nsUITimerCallback::Notify(nsITimer* aTimer) +{ + nsresult rv; + nsCOMPtr obs = + do_GetService("@mozilla.org/observer-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + if ((gMouseOrKeyboardEventCounter == mPreviousCount) || !aTimer) { + gMouseOrKeyboardEventCounter = 0; + obs->NotifyObservers(nsnull, "user-interaction-inactive", nsnull); + } else { + obs->NotifyObservers(nsnull, "user-interaction-active", nsnull); + } + mPreviousCount = gMouseOrKeyboardEventCounter; + return NS_OK; +} nsWindowRoot::nsWindowRoot(nsIDOMWindow* aWindow) { @@ -71,6 +110,20 @@ nsWindowRoot::nsWindowRoot(nsIDOMWindow* aWindow) AddEventListener(NS_LITERAL_STRING("focus"), focusListener, PR_TRUE); AddEventListener(NS_LITERAL_STRING("blur"), focusListener, PR_TRUE); mRefCnt.decr(static_cast(this)); + + if (gWindowRootCount == 0) { + gUserInteractionTimerCallback = new nsUITimerCallback(); + if (gUserInteractionTimerCallback) { + NS_ADDREF(gUserInteractionTimerCallback); + CallCreateInstance("@mozilla.org/timer;1", &gUserInteractionTimer); + if (gUserInteractionTimer) { + gUserInteractionTimer->InitWithCallback(gUserInteractionTimerCallback, + NS_USER_INTERACTION_INTERVAL, + nsITimer::TYPE_REPEATING_SLACK); + } + } + } + ++gWindowRootCount; } nsWindowRoot::~nsWindowRoot() @@ -78,6 +131,18 @@ nsWindowRoot::~nsWindowRoot() if (mListenerManager) { mListenerManager->Disconnect(); } + + --gWindowRootCount; + if (gWindowRootCount == 0) { + if (gUserInteractionTimerCallback) { + gUserInteractionTimerCallback->Notify(nsnull); + NS_RELEASE(gUserInteractionTimerCallback); + } + if (gUserInteractionTimer) { + gUserInteractionTimer->Cancel(); + NS_RELEASE(gUserInteractionTimer); + } + } } NS_IMPL_CYCLE_COLLECTION_2(nsWindowRoot, mListenerManager, mFocusController) @@ -247,6 +312,21 @@ nsWindowRoot::PreHandleEvent(nsEventChainPreVisitor& aVisitor) aVisitor.mForceContentDispatch = PR_TRUE; //FIXME! Bug 329119 // To keep mWindow alive aVisitor.mItemData = mWindow; + if (NS_IS_TRUSTED_EVENT(aVisitor.mEvent) && + ((aVisitor.mEvent->eventStructType == NS_MOUSE_EVENT && + static_cast(aVisitor.mEvent)->reason == + nsMouseEvent::eReal) || + aVisitor.mEvent->eventStructType == NS_MOUSE_SCROLL_EVENT || + aVisitor.mEvent->eventStructType == NS_KEY_EVENT)) { + if (gMouseOrKeyboardEventCounter == 0) { + nsCOMPtr obs = + do_GetService("@mozilla.org/observer-service;1"); + if (obs) { + obs->NotifyObservers(nsnull, "user-interaction-active", nsnull); + } + } + ++gMouseOrKeyboardEventCounter; + } return NS_OK; } diff --git a/mozilla/xpcom/base/nsCycleCollector.cpp b/mozilla/xpcom/base/nsCycleCollector.cpp index 3bfbcea446d..b3620415037 100644 --- a/mozilla/xpcom/base/nsCycleCollector.cpp +++ b/mozilla/xpcom/base/nsCycleCollector.cpp @@ -869,7 +869,7 @@ struct nsCycleCollector PRBool Forget(nsISupports *n); void Allocated(void *n, size_t sz); void Freed(void *n); - void Collect(PRUint32 aTryCollections = 1); + PRBool Collect(PRUint32 aTryCollections = 1); void Shutdown(); #ifdef DEBUG_CC @@ -2021,9 +2021,10 @@ nsCycleCollector::Freed(void *n) } #endif -void +PRBool nsCycleCollector::Collect(PRUint32 aTryCollections) { + PRBool didCollect = PR_FALSE; #if defined(DEBUG_CC) && !defined(__MINGW32__) if (!mParams.mDoNothing && mParams.mHookMalloc) InitMemHook(); @@ -2031,7 +2032,7 @@ nsCycleCollector::Collect(PRUint32 aTryCollections) // This can legitimately happen in a few cases. See bug 383651. if (mCollectionInProgress) - return; + return didCollect; #ifdef COLLECT_TIME_DEBUG printf("cc: Starting nsCycleCollector::Collect(%d)\n", aTryCollections); @@ -2168,8 +2169,11 @@ nsCycleCollector::Collect(PRUint32 aTryCollections) // mBuf.GetSize() == 0 check above), we should stop // repeating collections if we didn't collect anything // this time. - if (!collected) + if (!collected) { aTryCollections = 0; + } else { + didCollect = PR_TRUE; + } } #ifdef DEBUG_CC @@ -2194,6 +2198,7 @@ nsCycleCollector::Collect(PRUint32 aTryCollections) #ifdef DEBUG_CC ExplainLiveExpectedGarbage(); #endif + return didCollect; } void @@ -2594,11 +2599,10 @@ NS_CycleCollectorForget(nsISupports *n) } -void +PRBool nsCycleCollector_collect() { - if (sCollector) - sCollector->Collect(); + return sCollector ? sCollector->Collect() : PR_FALSE; } nsresult diff --git a/mozilla/xpcom/base/nsCycleCollector.h b/mozilla/xpcom/base/nsCycleCollector.h index 0809ee6dfda..ac8ceb5343e 100644 --- a/mozilla/xpcom/base/nsCycleCollector.h +++ b/mozilla/xpcom/base/nsCycleCollector.h @@ -66,7 +66,8 @@ struct nsCycleCollectionLanguageRuntime NS_COM void nsCycleCollector_suspectCurrent(nsISupports *n); // NS_COM PRBool nsCycleCollector_forget(nsISupports *n); nsresult nsCycleCollector_startup(); -NS_COM void nsCycleCollector_collect(); +// Returns PR_TRUE if some nodes were collected. +NS_COM PRBool nsCycleCollector_collect(); void nsCycleCollector_shutdown(); #ifdef DEBUG