From cd8561b5b7dfde23094fc86e9b4dfdfc0d49f2cb Mon Sep 17 00:00:00 2001 From: "Olli.Pettay%helsinki.fi" Date: Mon, 22 Oct 2007 21:42:26 +0000 Subject: [PATCH] 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@237998 18797224-902f-48f8-a5cc-f745e15eee43 --- mozilla/content/base/src/nsXMLHttpRequest.cpp | 3 + .../events/src/nsEventStateManager.cpp | 79 ++++++++ mozilla/dom/src/base/nsJSEnvironment.cpp | 182 ++++++++++++++++-- mozilla/dom/src/base/nsJSEnvironment.h | 21 ++ mozilla/xpcom/base/nsCycleCollector.cpp | 18 +- mozilla/xpcom/base/nsCycleCollector.h | 3 +- 6 files changed, 287 insertions(+), 19 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..5b65c4c6496 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,25 @@ nsEventStateManager::PreHandleEvent(nsPresContext* aPresContext, if (!mCurrentTarget) return NS_ERROR_NULL_POINTER; } + // Do not take account NS_MOUSE_ENTER/EXIT so that loading a page + // when user is not active doesn't change the state to active. + if (NS_IS_TRUSTED_EVENT(aEvent) && + ((aEvent->eventStructType == NS_MOUSE_EVENT && + static_cast(aEvent)->reason == nsMouseEvent::eReal && + aEvent->message != NS_MOUSE_ENTER && + aEvent->message != NS_MOUSE_EXIT) || + 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..be31d9b688b 100644 --- a/mozilla/dom/src/base/nsJSEnvironment.cpp +++ b/mozilla/dom/src/base/nsJSEnvironment.cpp @@ -149,8 +149,27 @@ 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 PRBool sUserIsActive; +static PRTime sPreviousCCTime; +static PRBool sPreviousCCDidCollect; static nsITimer *sGCTimer; static PRBool sReadyForGC; @@ -194,6 +213,74 @@ 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) {} + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER +private: + PRUint32 mUserActivityCounter; + PRUint32 mOldCCollectCount; +}; + +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 (sUserIsActive) { + sUserIsActive = 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 + sUserIsActive = 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,73 @@ nsJSContext::PreserveWrapper(nsIXPConnectWrappedNative *aWrapper) return nsDOMClassInfo::PreserveNodeWrapper(aWrapper); } +//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 = PR_FALSE; + if (!sUserIsActive) { + 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; +} + +//static +void +nsJSContext::CCIfUserInactive() +{ + if (sUserIsActive) { + MaybeCC(PR_TRUE); + } else { + CC(); + } +} + NS_IMETHODIMP nsJSContext::Notify(nsITimer *timer) { @@ -3189,9 +3343,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(); + CCIfUserInactive(); } else { FireGCTimer(PR_TRUE); } @@ -3220,13 +3372,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(); + CCIfUserInactive(); } } @@ -3253,10 +3402,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(); - + CCIfUserInactive(); return; } @@ -3364,6 +3510,11 @@ void nsJSRuntime::Startup() { // initialize all our statics, so that we can restart XPCOM + sDelayedCCollectCount = 0; + sCCollectCount = 0; + sUserIsActive = PR_FALSE; + sPreviousCCTime = 0; + sPreviousCCDidCollect = PR_FALSE; sGCTimer = nsnull; sReadyForGC = PR_FALSE; sLoadInProgressGCTimer = PR_FALSE; @@ -3482,6 +3633,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..0c48c1a97c9 100644 --- a/mozilla/dom/src/base/nsJSEnvironment.h +++ b/mozilla/dom/src/base/nsJSEnvironment.h @@ -170,6 +170,27 @@ 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. + // @return PR_TRUE if cycle collector was called. + static PRBool MaybeCC(PRBool aHigherProbability); + + // Calls CC() if user is currently inactive, otherwise MaybeCC(PR_TRUE) + static void CCIfUserInactive(); protected: nsresult InitializeExternalClasses(); // aHolder should be holding our global object 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