Don't fatten a flyweight lock unnecessarily in JS_SetPrototype; misc. cleanups (63097, r=mccabe, sr=jband).

This commit is contained in:
brendan%mozilla.org 2000-12-20 22:36:01 +00:00
parent 1c49ac5fbf
commit 7b1d57a4dc
9 changed files with 129 additions and 60 deletions

View File

@ -1161,7 +1161,7 @@ DumpStats(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
return JS_FALSE;
bytes = JS_GetStringBytes(str);
if (strcmp(bytes, "arena") == 0) {
#ifdef ARENAMETER
#ifdef JS_ARENAMETER
JS_DumpArenaStats(stdout);
#endif
} else if (strcmp(bytes, "atom") == 0) {

View File

@ -799,9 +799,7 @@ JS_EndRequest(JSContext *cx)
scope->u.count = 0; /* don't assume NULL puns as 0 */
scope->ownercx = NULL; /* NB: set last, after lock init */
nshares++;
#ifdef DEBUG
JS_ATOMIC_INCREMENT(&rt->sharedScopes);
#endif
JS_RUNTIME_METER(rt, sharedScopes);
}
}
if (nshares)
@ -1968,6 +1966,7 @@ DefineProperty(JSContext *cx, JSObject *obj, const char *name, jsval value,
if (attrs & JSPROP_INDEX) {
id = INT_TO_JSVAL((jsint)name);
atom = NULL;
attrs &= ~JSPROP_INDEX;
} else {
atom = js_Atomize(cx, name, strlen(name), 0);
if (!atom)
@ -2054,7 +2053,12 @@ JS_DefineProperties(JSContext *cx, JSObject *obj, JSPropertySpec *ps)
if (prop) {
if (OBJ_IS_NATIVE(obj)) {
sprop = (JSScopeProperty *)prop;
#ifdef JS_DOUBLE_HASHING
sprop->attrs |= JSPROP_INDEX;
sprop->tinyid = ps->tinyid;
#else
sprop->id = INT_TO_JSVAL(ps->tinyid);
#endif
}
OBJ_DROP_PROPERTY(cx, obj, prop);
}
@ -2085,7 +2089,12 @@ JS_DefinePropertyWithTinyId(JSContext *cx, JSObject *obj, const char *name,
if (ok && prop) {
if (OBJ_IS_NATIVE(obj)) {
sprop = (JSScopeProperty *)prop;
#ifdef JS_DOUBLE_HASHING
sprop->attrs |= JSPROP_INDEX;
sprop->tinyid = tinyid;
#else
sprop->id = INT_TO_JSVAL(tinyid);
#endif
}
OBJ_DROP_PROPERTY(cx, obj, prop);
}
@ -2358,7 +2367,12 @@ JS_DefineUCPropertyWithTinyId(JSContext *cx, JSObject *obj,
if (ok && prop) {
if (OBJ_IS_NATIVE(obj)) {
sprop = (JSScopeProperty *)prop;
#ifdef JS_DOUBLE_HASHING
sprop->attrs |= JSPROP_INDEX;
sprop->tinyid = tinyid;
#else
sprop->id = INT_TO_JSVAL(tinyid);
#endif
}
OBJ_DROP_PROPERTY(cx, obj, prop);
}

View File

@ -18,7 +18,7 @@
* Copyright (C) 1998 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the
* terms of the GNU Public License (the "GPL"), in which case the
@ -255,9 +255,7 @@ js_LiveContext(JSRuntime *rt, JSContext *cx)
if (cl == &cx->links)
return JS_TRUE;
}
#ifdef DEBUG
JS_ATOMIC_INCREMENT(&rt->deadContexts);
#endif
JS_RUNTIME_METER(rt, deadContexts);
return JS_FALSE;
}
@ -312,7 +310,7 @@ ReportError(JSContext *cx, const char *message, JSErrorReport *reportp)
}
/*
* We don't post an exception in this case, since doing so runs into
* We don't post an exception in this case, since doing so runs into
* complications of pre-allocating an exception object which required
* running the Exception class initializer early etc.
* Instead we just invoke the errorReporter with an "Out Of Memory"
@ -354,7 +352,7 @@ js_ReportOutOfMemory(JSContext *cx, JSErrorCallback errorCallback)
onError = NULL;
}
}
if (onError)
(*onError)(cx, msg, &report);
}

View File

@ -18,7 +18,7 @@
* Copyright (C) 1998 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the
* terms of the GNU Public License (the "GPL"), in which case the
@ -157,6 +157,7 @@ struct JSRuntime {
/* Used to serialize cycle checks when setting __proto__ or __parent__. */
PRLock *setSlotLock;
JSScope *setSlotScope; /* deadlock avoidance, see jslock.c */
/*
* State for sharing single-threaded scopes, once a second thread tries to
@ -196,9 +197,23 @@ struct JSRuntime {
jsrefcount liveScopes;
jsrefcount sharedScopes;
jsrefcount totalScopes;
/* String instrumentation. */
jsrefcount liveStrings;
jsrefcount totalStrings;
double lengthSum;
double lengthSquaredSum;
#endif
};
#ifdef DEBUG
# define JS_RUNTIME_METER(rt, which) JS_ATOMIC_INCREMENT(&(rt)->which)
# define JS_RUNTIME_UNMETER(rt, which) JS_ATOMIC_DECREMENT(&(rt)->which)
#else
# define JS_RUNTIME_METER(rt, which) /* nothing */
# define JS_RUNTIME_UNMETER(rt, which) /* nothing */
#endif
#define JS_ENABLE_GC(rt) JS_ATOMIC_DECREMENT(&(rt)->gcDisabled);
#define JS_DISABLE_GC(rt) JS_ATOMIC_INCREMENT(&(rt)->gcDisabled);

View File

@ -18,7 +18,7 @@
* Copyright (C) 1998 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the
* terms of the GNU Public License (the "GPL"), in which case the
@ -565,12 +565,6 @@ ComputeThis(JSContext *cx, JSObject *thisp, JSStackFrame *fp)
return JS_TRUE;
}
#ifdef DEBUG
# define METER_INVOCATION(rt, which) JS_ATOMIC_INCREMENT(&(rt)->which)
#else
# define METER_INVOCATION(rt, which) /* nothing */
#endif
/*
* Find a function reference and its 'this' object implicit first parameter
* under argc arguments on cx's stack, and call the function. Push missing
@ -788,7 +782,7 @@ have_fun:
frame.varobj = fp->varobj;
frame.scopeChain = fp->scopeChain;
ok = native(cx, frame.thisp, argc, frame.argv, &frame.rval);
METER_INVOCATION(cx->runtime, nativeCalls);
JS_RUNTIME_METER(cx->runtime, nativeCalls);
} else if (script) {
/* Use parent scope so js_GetCallObject can find the right "Call". */
frame.scopeChain = parent;
@ -2298,7 +2292,7 @@ js_Interpret(JSContext *cx, jsval *result)
goto out;
}
obj = JSVAL_TO_OBJECT(rval);
METER_INVOCATION(rt, constructs);
JS_RUNTIME_METER(rt, constructs);
break;
case JSOP_DELNAME:
@ -2601,7 +2595,7 @@ js_Interpret(JSContext *cx, jsval *result)
pc = script->code;
endpc = pc + script->length;
inlineCallCount++;
METER_INVOCATION(rt, inlineCalls);
JS_RUNTIME_METER(rt, inlineCalls);
continue;
bad_inline_call:
@ -2614,7 +2608,7 @@ js_Interpret(JSContext *cx, jsval *result)
RESTORE_SP(fp);
if (!ok)
goto out;
METER_INVOCATION(rt, nonInlineCalls);
JS_RUNTIME_METER(rt, nonInlineCalls);
#if JS_HAS_LVALUE_RETURN
if (cx->rval2set) {
/*

View File

@ -246,12 +246,6 @@ js_unlog_scope(JSScope *scope)
#endif /* DEBUG_SCOPE_COUNT */
#ifdef DEBUG
# define METER_LOCK_EVENT(rt, which) JS_ATOMIC_INCREMENT(&(rt)->which)
#else
# define METER_LOCK_EVENT(rt, which) /* nothing */
#endif
/*
* Return true if scope's ownercx, or the ownercx of a single-threaded scope
* for which ownercx is waiting to become multi-threaded and shared, is cx.
@ -268,7 +262,7 @@ WillDeadlock(JSScope *scope, JSContext *cx)
do {
ownercx = scope->ownercx;
if (ownercx == cx) {
METER_LOCK_EVENT(cx->runtime, deadlocksAvoided);
JS_RUNTIME_METER(cx->runtime, deadlocksAvoided);
return JS_TRUE;
}
} while (ownercx && (scope = ownercx->scopeToShare) != NULL);
@ -276,8 +270,11 @@ WillDeadlock(JSScope *scope, JSContext *cx)
}
/*
* Make scope multi-threaded, i.e. shared its ownership among contexts in rt
* using a "thin" or (if necessary due to contention) "fat" lock.
* Make scope multi-threaded, i.e. share its ownership among contexts in rt
* using a "thin" or (if necessary due to contention) "fat" lock. Called only
* from ClaimScope, immediately below, when we detect deadlock were we to wait
* for scope's lock, because its ownercx is waiting on a scope owned by the
* calling cx.
*
* (i) rt->gcLock held
*/
@ -295,9 +292,32 @@ ShareScope(JSRuntime *rt, JSScope *scope)
JS_NOTIFY_ALL_CONDVAR(rt->scopeSharingDone);
}
js_InitLock(&scope->lock);
scope->u.count = 0;
if (scope == rt->setSlotScope) {
/*
* Nesting locks on another thread that's using scope->ownercx: give
* the held lock a reentrancy count of 1 and set its lock.owner field
* directly (no compare-and-swap needed while scope->ownercx is still
* non-null). See below in ClaimScope, before the ShareScope call,
* for more on why this is necessary.
*
* If NSPR_LOCK is defined, we cannot deadlock holding rt->gcLock and
* acquiring scope->lock.fat here, against another thread holding that
* fat lock and trying to grab rt->gcLock. This is because no other
* thread can attempt to acquire scope->lock.fat until scope->ownercx
* is null *and* our thread has released rt->gcLock, which interlocks
* scope->ownercx's transition to null against tests of that member
* in ClaimScope.
*/
scope->lock.owner = scope->ownercx->thread;
#ifdef NSPR_LOCK
JS_ACQUIRE_LOCK((JSLock*)scope->lock.fat);
#endif
scope->u.count = 1;
} else {
scope->u.count = 0;
}
scope->ownercx = NULL; /* NB: set last, after lock init */
METER_LOCK_EVENT(rt, sharedScopes);
JS_RUNTIME_METER(rt, sharedScopes);
}
/*
@ -317,7 +337,7 @@ ClaimScope(JSScope *scope, JSContext *cx)
PRStatus stat;
rt = cx->runtime;
METER_LOCK_EVENT(rt, claimAttempts);
JS_RUNTIME_METER(rt, claimAttempts);
JS_LOCK_GC(rt);
/* Reload in case ownercx went away while we blocked on the lock. */
@ -343,7 +363,7 @@ ClaimScope(JSScope *scope, JSContext *cx)
JS_ASSERT(scope->u.count == 0);
scope->ownercx = cx;
JS_UNLOCK_GC(rt);
METER_LOCK_EVENT(rt, claimedScopes);
JS_RUNTIME_METER(rt, claimedScopes);
return JS_TRUE;
}
@ -357,10 +377,18 @@ ClaimScope(JSScope *scope, JSContext *cx)
* could hold locks on scope, we would need to keep reentrancy counts
* for all such "flyweight" (ownercx != NULL) locks, so that control
* would unwind properly once these locks became "thin" or "fat".
* Apart from the js_SetProtoOrParent exception, the engine promotes
* a scope from exclusive to shared access only when locking, never
* when holding or unlocking.
*
* In the case of js_SetProtoOrParent, we ensure that the "outer"
* scope's lock is not flyweight before that function tries to hold
* the inner scope's lock.
* If ownercx's thread is calling js_SetProtoOrParent, trying to lock
* the inner scope (the scope of the object being set as the prototype
* of the outer object), ShareScope will find the outer object's scope
* at rt->setSlotScope. If it's the same as scope, we give it a lock
* held by ownercx's thread with reentrancy count of 1, then we return
* here and break. After that we unwind to js_[GS]etSlotThreadSafe or
* js_LockScope (our caller), where we wait on the newly-fattened lock
* until ownercx's thread unwinds from js_SetProtoOrParent.
*/
if (ownercx->scopeToShare &&
WillDeadlock(ownercx->scopeToShare, cx)) {
@ -891,19 +919,6 @@ js_UnlockRuntime(JSRuntime *rt)
PR_Unlock(rt->rtLock);
}
void
js_PromoteScopeLock(JSContext *cx, JSScope *scope)
{
JSRuntime *rt;
JS_ASSERT(cx == scope->ownercx);
rt = cx->runtime;
JS_LOCK_GC(rt);
ShareScope(rt, scope);
JS_UNLOCK_GC(rt);
js_LockScope(cx, scope);
}
void
js_LockScope(JSContext *cx, JSScope *scope)
{

View File

@ -18,7 +18,7 @@
* Copyright (C) 1998 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the
* terms of the GNU Public License (the "GPL"), in which case the
@ -152,7 +152,6 @@ extern void js_LockRuntime(JSRuntime *rt);
extern void js_UnlockRuntime(JSRuntime *rt);
extern void js_LockObj(JSContext *cx, JSObject *obj);
extern void js_UnlockObj(JSContext *cx, JSObject *obj);
extern void js_PromoteScopeLock(JSContext *cx, JSScope *scope);
extern void js_LockScope(JSContext *cx, JSScope *scope);
extern void js_UnlockScope(JSContext *cx, JSScope *scope);
extern int js_SetupLocks(int,int);

View File

@ -230,7 +230,6 @@ js_SetProtoOrParent(JSContext *cx, JSObject *obj, uint32 slot, JSObject *pobj)
* rt->setSlotLock < pobj's grand-proto-or-parent's scope lock;
* etc...
* (2) rt->setSlotLock < obj's scope lock < pobj's scope lock.
* rt->setSlotLock < obj's scope lock < rt->gcLock
*
* We avoid AB-BA deadlock by restricting obj from being on pobj's parent
* or proto chain (pobj may already be on obj's parent or proto chain; it
@ -275,14 +274,13 @@ js_SetProtoOrParent(JSContext *cx, JSObject *obj, uint32 slot, JSObject *pobj)
} else if (OBJ_IS_NATIVE(pobj) && OBJ_SCOPE(pobj) != scope) {
#ifdef JS_THREADSAFE
/*
* Avoid deadlock by never nesting a scope lock (for pobj, in
* this case) within a "flyweight" scope lock (for obj). Give
* scope a non-flyweight lock, allowing it to be shared among
* multiple threads. See ClaimScope in jslock.c.
* We are about to nest scope locks. Help jslock.c:ShareScope
* keep scope->u.count balanced for the JS_UNLOCK_SCOPE, while
* avoiding deadlock, by recording scope in rt->setSlotScope.
*/
if (scope->ownercx) {
JS_ASSERT(scope->ownercx == cx);
js_PromoteScopeLock(cx, scope);
JS_ASSERT(scope->ownercx == cx);
rt->setSlotScope = scope;
}
#endif
@ -293,6 +291,9 @@ js_SetProtoOrParent(JSContext *cx, JSObject *obj, uint32 slot, JSObject *pobj)
js_DropObjectMap(cx, &scope->map, obj);
JS_TRANSFER_SCOPE_LOCK(cx, scope, newscope);
scope = newscope;
#ifdef JS_THREADSAFE
rt->setSlotScope = NULL;
#endif
}
}
LOCKED_OBJ_SET_SLOT(obj, JSSLOT_PROTO, OBJECT_TO_JSVAL(pobj));

View File

@ -2298,9 +2298,41 @@ js_NewString(JSContext *cx, jschar *chars, size_t length, uintN gcflag)
return NULL;
str->length = length;
str->chars = chars;
#ifdef DEBUG
{
JSRuntime *rt = cx->runtime;
JS_RUNTIME_METER(rt, liveStrings);
JS_RUNTIME_METER(rt, totalStrings);
JS_LOCK_RUNTIME_VOID(rt,
(rt->lengthSum += (double)length,
rt->lengthSquaredSum += (double)length * (double)length));
}
#endif
return str;
}
#ifdef DEBUG
#include <math.h>
void printJSStringStats(JSRuntime *rt) {
double mean = 0., var = 0., sigma = 0.;
jsrefcount count = rt->totalStrings;
if (count > 0 && rt->lengthSum >= 0) {
mean = rt->lengthSum / count;
var = count * rt->lengthSquaredSum - rt->lengthSum * rt->lengthSum;
if (var < 0.0 || count <= 1)
var = 0.0;
else
var /= count * (count - 1);
/* Windows says sqrt(0.0) is "-1.#J" (?!) so we must test. */
sigma = (var != 0.) ? sqrt(var) : 0.;
}
fprintf(stderr, "%lu total strings, mean length %g (sigma %g)\n",
(unsigned long)count, mean, sigma);
}
#endif
JSString *
js_NewStringCopyN(JSContext *cx, const jschar *s, size_t n, uintN gcflag)
{
@ -2349,6 +2381,7 @@ js_FinalizeString(JSContext *cx, JSString *str)
JSHashNumber hash;
JSHashEntry *he, **hep;
JS_RUNTIME_UNMETER(cx->runtime, liveStrings);
if (str->chars) {
JS_free(cx, str->chars);
str->chars = NULL;