From ae040f9f0bc6842a72d3fe54b6bb1f1789ae4e61 Mon Sep 17 00:00:00 2001 From: "Olli.Pettay%helsinki.fi" Date: Sun, 21 Oct 2007 14:09:31 +0000 Subject: [PATCH] Test-landing (again) Bug 373462, bug 385322, Better scheduling of cycle collection/gc, r+sr=sicking,jst git-svn-id: svn://10.0.0.236/trunk@237971 18797224-902f-48f8-a5cc-f745e15eee43 --- mozilla/content/base/src/nsXMLHttpRequest.cpp | 3 + .../events/src/nsEventStateManager.cpp | 75 +++++++ mozilla/dom/src/base/nsJSEnvironment.cpp | 190 ++++++++++++++++-- mozilla/dom/src/base/nsJSEnvironment.h | 23 ++- mozilla/layout/base/nsDocumentViewer.cpp | 2 +- mozilla/xpcom/base/nsCycleCollector.cpp | 18 +- mozilla/xpcom/base/nsCycleCollector.h | 3 +- 7 files changed, 292 insertions(+), 22 deletions(-) diff --git a/mozilla/content/base/src/nsXMLHttpRequest.cpp b/mozilla/content/base/src/nsXMLHttpRequest.cpp index cc39b3a575a..68dc164b542 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" @@ -1778,6 +1779,7 @@ nsXMLHttpRequest::RequestCompleted() ChangeState(XML_HTTP_REQUEST_OPENED); } + nsJSContext::MaybeCC(PR_FALSE); return rv; } @@ -2320,6 +2322,7 @@ nsXMLHttpRequest::Error(nsIDOMEvent* aEvent) NotifyEventListeners(errorEventListeners, event); } + nsJSContext::MaybeCC(PR_FALSE); return NS_OK; } diff --git a/mozilla/content/events/src/nsEventStateManager.cpp b/mozilla/content/events/src/nsEventStateManager.cpp index aebc2d70981..c388bc2a747 100644 --- a/mozilla/content/events/src/nsEventStateManager.cpp +++ b/mozilla/content/events/src/nsEventStateManager.cpp @@ -136,6 +136,9 @@ #include "nsEventDispatcher.h" #include "nsPresShellIterator.h" +#include "nsServiceManagerUtils.h" +#include "nsITimer.h" + #ifdef XP_MACOSX #include #endif @@ -144,6 +147,8 @@ //#define DEBUG_DOCSHELL_FOCUS #endif +#define NS_USER_INTERACTION_INTERVAL 5000 // ms + static NS_DEFINE_CID(kFrameTraversalCID, NS_FRAMETRAVERSAL_CID); @@ -171,6 +176,41 @@ static PRUint32 sESMInstanceCount = 0; static PRInt32 sChromeAccessModifier = 0, sContentAccessModifier = 0; PRInt32 nsEventStateManager::sUserInputEventDepth = 0; +static PRUint32 gMouseOrKeyboardEventCounter = 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; +} + enum { MOUSE_SCROLL_N_LINES, MOUSE_SCROLL_PAGE, @@ -431,6 +471,18 @@ nsEventStateManager::nsEventStateManager() mTabbedThroughDocument(PR_FALSE), mAccessKeys(nsnull) { + if (sESMInstanceCount == 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); + } + } + } ++sESMInstanceCount; } @@ -509,6 +561,14 @@ nsEventStateManager::~nsEventStateManager() if(sESMInstanceCount == 0) { NS_IF_RELEASE(gLastFocusedContent); NS_IF_RELEASE(gLastFocusedDocument); + if (gUserInteractionTimerCallback) { + gUserInteractionTimerCallback->Notify(nsnull); + NS_RELEASE(gUserInteractionTimerCallback); + } + if (gUserInteractionTimer) { + gUserInteractionTimer->Cancel(); + NS_RELEASE(gUserInteractionTimer); + } } delete mAccessKeys; @@ -724,6 +784,21 @@ nsEventStateManager::PreHandleEvent(nsPresContext* aPresContext, if (!mCurrentTarget) return NS_ERROR_NULL_POINTER; } + if (NS_IS_TRUSTED_EVENT(aEvent) && + ((aEvent->eventStructType == NS_MOUSE_EVENT && + static_cast(aEvent)->reason == nsMouseEvent::eReal) || + aEvent->eventStructType == NS_MOUSE_SCROLL_EVENT || + aEvent->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; + } + *aStatus = nsEventStatus_eIgnore; nsMouseWheelTransaction::OnEvent(aEvent); diff --git a/mozilla/dom/src/base/nsJSEnvironment.cpp b/mozilla/dom/src/base/nsJSEnvironment.cpp index 2e76019c343..d4de07b1b9d 100644 --- a/mozilla/dom/src/base/nsJSEnvironment.cpp +++ b/mozilla/dom/src/base/nsJSEnvironment.cpp @@ -149,8 +149,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; @@ -194,6 +212,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 **************************** ****************************************************************/ @@ -3171,6 +3258,80 @@ nsJSContext::PreserveWrapper(nsIXPConnectWrappedNative *aWrapper) return nsDOMClassInfo::PreserveNodeWrapper(aWrapper); } +//static +void +nsJSContext::MaybeCCOrGC(nsIScriptContext* aContext) +{ + if (!nsJSContext::MaybeCC(PR_TRUE)) { + nsCOMPtr context = aContext; + if (context) { + JSContext* cx = static_cast(context->GetNativeContext()); + if (cx) { +#ifdef DEBUG_smaug + printf("Will call JS_GC\n"); +#endif + ::JS_GC(cx); +#ifdef DEBUG_smaug + printf("Did call JS_GC\n"); +#endif + } + } + } +} + +//static +void +nsJSContext::CC() +{ + sPreviousCCTime = PR_Now(); + sDelayedCCollectCount = 0; + ++sCCollectCount; +#ifdef DEBUG_smaug + printf("Will run cycle collector (%i)\n", sCCollectCount); +#endif + // nsCycleCollector_collect() will run a ::JS_GC() indirectly, so + // we do not explicitly call ::JS_GC() here. + PRBool firstRun = nsCycleCollector_collect(); +#ifdef DEBUG_smaug + printf("(1) %s\n", firstRun ? + "Cycle collector did collect nodes" : + "Cycle collector did not collect nodes"); +#endif + PRBool secondRun = nsCycleCollector_collect(); +#ifdef DEBUG_smaug + printf("(2) %s\n", secondRun ? + "Cycle collector did collect nodes" : + "Cycle collector did not collect nodes"); +#endif + sPreviousCCDidCollect = firstRun || secondRun; +} + +//static +PRBool +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(); + return PR_TRUE; + } +#ifdef DEBUG_smaug + else { + printf("Running cycle collector was delayed: NS_MIN_CC_INTERVAL\n"); + } +#endif + } + return PR_FALSE; +} + NS_IMETHODIMP nsJSContext::Notify(nsITimer *timer) { @@ -3189,9 +3350,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(); + MaybeCCOrGC(this); } else { FireGCTimer(PR_TRUE); } @@ -3210,7 +3369,7 @@ nsJSContext::LoadStart() // static void -nsJSContext::LoadEnd() +nsJSContext::LoadEnd(nsIScriptGlobalObject* aGlobalObject) { // sPendingLoadCount is not a well managed load counter (and doesn't // need to be), so make sure we don't make it wrap backwards here. @@ -3220,13 +3379,10 @@ nsJSContext::LoadEnd() if (!sPendingLoadCount && sLoadInProgressGCTimer) { sGCTimer->Cancel(); - 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(); + MaybeCCOrGC(aGlobalObject ? aGlobalObject->GetContext() : nsnull); } } @@ -3253,10 +3409,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(); - + MaybeCCOrGC(this); return; } @@ -3364,6 +3517,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; @@ -3482,6 +3639,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 c1cefb86a41..13e428dc956 100644 --- a/mozilla/dom/src/base/nsJSEnvironment.h +++ b/mozilla/dom/src/base/nsJSEnvironment.h @@ -168,7 +168,26 @@ public: NS_DECL_NSITIMERCALLBACK static void LoadStart(); - static void LoadEnd(); + static void LoadEnd(nsIScriptGlobalObject* aGlobalObject); + + // 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. + // @return PR_TRUE if cycle collector was called. + static PRBool MaybeCC(PRBool aHigherProbability); protected: nsresult InitializeExternalClasses(); @@ -190,6 +209,8 @@ protected: nsresult JSObjectFromInterface(nsISupports *aSup, void *aScript, JSObject **aRet); + static void MaybeCCOrGC(nsIScriptContext* aContext); + private: JSContext *mContext; PRUint32 mNumEvaluations; diff --git a/mozilla/layout/base/nsDocumentViewer.cpp b/mozilla/layout/base/nsDocumentViewer.cpp index 881d7ecbeb9..6d51df8f5bf 100644 --- a/mozilla/layout/base/nsDocumentViewer.cpp +++ b/mozilla/layout/base/nsDocumentViewer.cpp @@ -993,7 +993,7 @@ DocumentViewerImpl::LoadComplete(nsresult aStatus) mPresShell->UnsuppressPainting(); } - nsJSContext::LoadEnd(); + nsJSContext::LoadEnd(mDocument ? mDocument->GetScriptGlobalObject() : nsnull); #ifdef NS_PRINTING // Check to see if someone tried to print during the load 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