Test-landing (again) Bug 373462, bug 385322, Better scheduling of cycle collection/gc, r+sr=sicking,jst

This commit is contained in:
Olli.Pettay@helsinki.fi 2007-10-21 07:09:29 -07:00
parent 33e3d4f7e7
commit cad8f0b8c4
7 changed files with 292 additions and 22 deletions

View File

@ -56,6 +56,7 @@
#include "prprf.h" #include "prprf.h"
#include "nsIDOMEventListener.h" #include "nsIDOMEventListener.h"
#include "nsIJSContextStack.h" #include "nsIJSContextStack.h"
#include "nsJSEnvironment.h"
#include "nsIScriptSecurityManager.h" #include "nsIScriptSecurityManager.h"
#include "nsWeakPtr.h" #include "nsWeakPtr.h"
#include "nsICharsetAlias.h" #include "nsICharsetAlias.h"
@ -1778,6 +1779,7 @@ nsXMLHttpRequest::RequestCompleted()
ChangeState(XML_HTTP_REQUEST_OPENED); ChangeState(XML_HTTP_REQUEST_OPENED);
} }
nsJSContext::MaybeCC(PR_FALSE);
return rv; return rv;
} }
@ -2320,6 +2322,7 @@ nsXMLHttpRequest::Error(nsIDOMEvent* aEvent)
NotifyEventListeners(errorEventListeners, event); NotifyEventListeners(errorEventListeners, event);
} }
nsJSContext::MaybeCC(PR_FALSE);
return NS_OK; return NS_OK;
} }

View File

@ -136,6 +136,9 @@
#include "nsEventDispatcher.h" #include "nsEventDispatcher.h"
#include "nsPresShellIterator.h" #include "nsPresShellIterator.h"
#include "nsServiceManagerUtils.h"
#include "nsITimer.h"
#ifdef XP_MACOSX #ifdef XP_MACOSX
#include <Events.h> #include <Events.h>
#endif #endif
@ -144,6 +147,8 @@
//#define DEBUG_DOCSHELL_FOCUS //#define DEBUG_DOCSHELL_FOCUS
#endif #endif
#define NS_USER_INTERACTION_INTERVAL 5000 // ms
static NS_DEFINE_CID(kFrameTraversalCID, NS_FRAMETRAVERSAL_CID); static NS_DEFINE_CID(kFrameTraversalCID, NS_FRAMETRAVERSAL_CID);
@ -171,6 +176,41 @@ static PRUint32 sESMInstanceCount = 0;
static PRInt32 sChromeAccessModifier = 0, sContentAccessModifier = 0; static PRInt32 sChromeAccessModifier = 0, sContentAccessModifier = 0;
PRInt32 nsEventStateManager::sUserInputEventDepth = 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<nsIObserverService> 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 { enum {
MOUSE_SCROLL_N_LINES, MOUSE_SCROLL_N_LINES,
MOUSE_SCROLL_PAGE, MOUSE_SCROLL_PAGE,
@ -431,6 +471,18 @@ nsEventStateManager::nsEventStateManager()
mTabbedThroughDocument(PR_FALSE), mTabbedThroughDocument(PR_FALSE),
mAccessKeys(nsnull) 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; ++sESMInstanceCount;
} }
@ -509,6 +561,14 @@ nsEventStateManager::~nsEventStateManager()
if(sESMInstanceCount == 0) { if(sESMInstanceCount == 0) {
NS_IF_RELEASE(gLastFocusedContent); NS_IF_RELEASE(gLastFocusedContent);
NS_IF_RELEASE(gLastFocusedDocument); NS_IF_RELEASE(gLastFocusedDocument);
if (gUserInteractionTimerCallback) {
gUserInteractionTimerCallback->Notify(nsnull);
NS_RELEASE(gUserInteractionTimerCallback);
}
if (gUserInteractionTimer) {
gUserInteractionTimer->Cancel();
NS_RELEASE(gUserInteractionTimer);
}
} }
delete mAccessKeys; delete mAccessKeys;
@ -724,6 +784,21 @@ nsEventStateManager::PreHandleEvent(nsPresContext* aPresContext,
if (!mCurrentTarget) return NS_ERROR_NULL_POINTER; if (!mCurrentTarget) return NS_ERROR_NULL_POINTER;
} }
if (NS_IS_TRUSTED_EVENT(aEvent) &&
((aEvent->eventStructType == NS_MOUSE_EVENT &&
static_cast<nsMouseEvent*>(aEvent)->reason == nsMouseEvent::eReal) ||
aEvent->eventStructType == NS_MOUSE_SCROLL_EVENT ||
aEvent->eventStructType == NS_KEY_EVENT)) {
if (gMouseOrKeyboardEventCounter == 0) {
nsCOMPtr<nsIObserverService> obs =
do_GetService("@mozilla.org/observer-service;1");
if (obs) {
obs->NotifyObservers(nsnull, "user-interaction-active", nsnull);
}
}
++gMouseOrKeyboardEventCounter;
}
*aStatus = nsEventStatus_eIgnore; *aStatus = nsEventStatus_eIgnore;
nsMouseWheelTransaction::OnEvent(aEvent); nsMouseWheelTransaction::OnEvent(aEvent);

View File

@ -149,8 +149,26 @@ static PRLogModuleInfo* gJSDiagnostics;
#define JAVASCRIPT nsIProgrammingLanguage::JAVASCRIPT #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 // 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 nsITimer *sGCTimer;
static PRBool sReadyForGC; static PRBool sReadyForGC;
@ -194,6 +212,75 @@ static nsICollation *gCollation;
static nsIUnicodeDecoder *gDecoder; 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<nsIObserverService> 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 **************************** ************************** AutoFree ****************************
****************************************************************/ ****************************************************************/
@ -3171,6 +3258,80 @@ nsJSContext::PreserveWrapper(nsIXPConnectWrappedNative *aWrapper)
return nsDOMClassInfo::PreserveNodeWrapper(aWrapper); return nsDOMClassInfo::PreserveNodeWrapper(aWrapper);
} }
//static
void
nsJSContext::MaybeCCOrGC(nsIScriptContext* aContext)
{
if (!nsJSContext::MaybeCC(PR_TRUE)) {
nsCOMPtr<nsIScriptContext> context = aContext;
if (context) {
JSContext* cx = static_cast<JSContext*>(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 NS_IMETHODIMP
nsJSContext::Notify(nsITimer *timer) nsJSContext::Notify(nsITimer *timer)
{ {
@ -3189,9 +3350,7 @@ nsJSContext::Notify(nsITimer *timer)
// loading and move on as if they weren't. // loading and move on as if they weren't.
sPendingLoadCount = 0; sPendingLoadCount = 0;
// nsCycleCollector_collect() will run a ::JS_GC() indirectly, MaybeCCOrGC(this);
// so we do not explicitly call ::JS_GC() here.
nsCycleCollector_collect();
} else { } else {
FireGCTimer(PR_TRUE); FireGCTimer(PR_TRUE);
} }
@ -3210,7 +3369,7 @@ nsJSContext::LoadStart()
// static // static
void void
nsJSContext::LoadEnd() nsJSContext::LoadEnd(nsIScriptGlobalObject* aGlobalObject)
{ {
// sPendingLoadCount is not a well managed load counter (and doesn't // 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. // need to be), so make sure we don't make it wrap backwards here.
@ -3220,13 +3379,10 @@ nsJSContext::LoadEnd()
if (!sPendingLoadCount && sLoadInProgressGCTimer) { if (!sPendingLoadCount && sLoadInProgressGCTimer) {
sGCTimer->Cancel(); sGCTimer->Cancel();
NS_RELEASE(sGCTimer); NS_RELEASE(sGCTimer);
sLoadInProgressGCTimer = PR_FALSE; sLoadInProgressGCTimer = PR_FALSE;
// nsCycleCollector_collect() will run a ::JS_GC() indirectly, so MaybeCCOrGC(aGlobalObject ? aGlobalObject->GetContext() : nsnull);
// we do not explicitly call ::JS_GC() here.
nsCycleCollector_collect();
} }
} }
@ -3253,10 +3409,7 @@ nsJSContext::FireGCTimer(PRBool aLoadInProgress)
// timer. // timer.
sLoadInProgressGCTimer = PR_FALSE; sLoadInProgressGCTimer = PR_FALSE;
// nsCycleCollector_collect() will run a ::JS_GC() indirectly, so MaybeCCOrGC(this);
// we do not explicitly call ::JS_GC() here.
nsCycleCollector_collect();
return; return;
} }
@ -3364,6 +3517,10 @@ void
nsJSRuntime::Startup() nsJSRuntime::Startup()
{ {
// initialize all our statics, so that we can restart XPCOM // initialize all our statics, so that we can restart XPCOM
sDelayedCCollectCount = 0;
sCCollectCount = 0;
sPreviousCCTime = 0;
sPreviousCCDidCollect = PR_FALSE;
sGCTimer = nsnull; sGCTimer = nsnull;
sReadyForGC = PR_FALSE; sReadyForGC = PR_FALSE;
sLoadInProgressGCTimer = PR_FALSE; sLoadInProgressGCTimer = PR_FALSE;
@ -3482,6 +3639,15 @@ nsJSRuntime::Init()
MaxScriptRunTimePrefChangedCallback("dom.max_chrome_script_run_time", MaxScriptRunTimePrefChangedCallback("dom.max_chrome_script_run_time",
nsnull); nsnull);
nsCOMPtr<nsIObserverService> 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); rv = CallGetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &sSecurityManager);
sIsInitialized = NS_SUCCEEDED(rv); sIsInitialized = NS_SUCCEEDED(rv);

View File

@ -168,7 +168,26 @@ public:
NS_DECL_NSITIMERCALLBACK NS_DECL_NSITIMERCALLBACK
static void LoadStart(); 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: protected:
nsresult InitializeExternalClasses(); nsresult InitializeExternalClasses();
@ -190,6 +209,8 @@ protected:
nsresult JSObjectFromInterface(nsISupports *aSup, void *aScript, nsresult JSObjectFromInterface(nsISupports *aSup, void *aScript,
JSObject **aRet); JSObject **aRet);
static void MaybeCCOrGC(nsIScriptContext* aContext);
private: private:
JSContext *mContext; JSContext *mContext;
PRUint32 mNumEvaluations; PRUint32 mNumEvaluations;

View File

@ -993,7 +993,7 @@ DocumentViewerImpl::LoadComplete(nsresult aStatus)
mPresShell->UnsuppressPainting(); mPresShell->UnsuppressPainting();
} }
nsJSContext::LoadEnd(); nsJSContext::LoadEnd(mDocument ? mDocument->GetScriptGlobalObject() : nsnull);
#ifdef NS_PRINTING #ifdef NS_PRINTING
// Check to see if someone tried to print during the load // Check to see if someone tried to print during the load

View File

@ -869,7 +869,7 @@ struct nsCycleCollector
PRBool Forget(nsISupports *n); PRBool Forget(nsISupports *n);
void Allocated(void *n, size_t sz); void Allocated(void *n, size_t sz);
void Freed(void *n); void Freed(void *n);
void Collect(PRUint32 aTryCollections = 1); PRBool Collect(PRUint32 aTryCollections = 1);
void Shutdown(); void Shutdown();
#ifdef DEBUG_CC #ifdef DEBUG_CC
@ -2021,9 +2021,10 @@ nsCycleCollector::Freed(void *n)
} }
#endif #endif
void PRBool
nsCycleCollector::Collect(PRUint32 aTryCollections) nsCycleCollector::Collect(PRUint32 aTryCollections)
{ {
PRBool didCollect = PR_FALSE;
#if defined(DEBUG_CC) && !defined(__MINGW32__) #if defined(DEBUG_CC) && !defined(__MINGW32__)
if (!mParams.mDoNothing && mParams.mHookMalloc) if (!mParams.mDoNothing && mParams.mHookMalloc)
InitMemHook(); InitMemHook();
@ -2031,7 +2032,7 @@ nsCycleCollector::Collect(PRUint32 aTryCollections)
// This can legitimately happen in a few cases. See bug 383651. // This can legitimately happen in a few cases. See bug 383651.
if (mCollectionInProgress) if (mCollectionInProgress)
return; return didCollect;
#ifdef COLLECT_TIME_DEBUG #ifdef COLLECT_TIME_DEBUG
printf("cc: Starting nsCycleCollector::Collect(%d)\n", aTryCollections); printf("cc: Starting nsCycleCollector::Collect(%d)\n", aTryCollections);
@ -2168,8 +2169,11 @@ nsCycleCollector::Collect(PRUint32 aTryCollections)
// mBuf.GetSize() == 0 check above), we should stop // mBuf.GetSize() == 0 check above), we should stop
// repeating collections if we didn't collect anything // repeating collections if we didn't collect anything
// this time. // this time.
if (!collected) if (!collected) {
aTryCollections = 0; aTryCollections = 0;
} else {
didCollect = PR_TRUE;
}
} }
#ifdef DEBUG_CC #ifdef DEBUG_CC
@ -2194,6 +2198,7 @@ nsCycleCollector::Collect(PRUint32 aTryCollections)
#ifdef DEBUG_CC #ifdef DEBUG_CC
ExplainLiveExpectedGarbage(); ExplainLiveExpectedGarbage();
#endif #endif
return didCollect;
} }
void void
@ -2594,11 +2599,10 @@ NS_CycleCollectorForget(nsISupports *n)
} }
void PRBool
nsCycleCollector_collect() nsCycleCollector_collect()
{ {
if (sCollector) return sCollector ? sCollector->Collect() : PR_FALSE;
sCollector->Collect();
} }
nsresult nsresult

View File

@ -66,7 +66,8 @@ struct nsCycleCollectionLanguageRuntime
NS_COM void nsCycleCollector_suspectCurrent(nsISupports *n); NS_COM void nsCycleCollector_suspectCurrent(nsISupports *n);
// NS_COM PRBool nsCycleCollector_forget(nsISupports *n); // NS_COM PRBool nsCycleCollector_forget(nsISupports *n);
nsresult nsCycleCollector_startup(); nsresult nsCycleCollector_startup();
NS_COM void nsCycleCollector_collect(); // Returns PR_TRUE if some nodes were collected.
NS_COM PRBool nsCycleCollector_collect();
void nsCycleCollector_shutdown(); void nsCycleCollector_shutdown();
#ifdef DEBUG #ifdef DEBUG