Bug 416675: refactor JSScope locking for reuse on non-native objects. r+a=brendan.

This commit is contained in:
shaver@mozilla.org 2008-02-14 16:44:46 -08:00
parent 90f095319c
commit 7550157d62
7 changed files with 324 additions and 260 deletions

View File

@ -732,10 +732,10 @@ JS_NewRuntime(uint32 maxbytes)
rt->stateChange = JS_NEW_CONDVAR(rt->gcLock);
if (!rt->stateChange)
goto bad;
rt->scopeSharingDone = JS_NEW_CONDVAR(rt->gcLock);
if (!rt->scopeSharingDone)
rt->titleSharingDone = JS_NEW_CONDVAR(rt->gcLock);
if (!rt->titleSharingDone)
goto bad;
rt->scopeSharingTodo = NO_SCOPE_SHARING_TODO;
rt->titleSharingTodo = NO_TITLE_SHARING_TODO;
rt->debuggerLock = JS_NEW_LOCK();
if (!rt->debuggerLock)
goto bad;
@ -792,8 +792,8 @@ JS_DestroyRuntime(JSRuntime *rt)
JS_DESTROY_LOCK(rt->rtLock);
if (rt->stateChange)
JS_DESTROY_CONDVAR(rt->stateChange);
if (rt->scopeSharingDone)
JS_DESTROY_CONDVAR(rt->scopeSharingDone);
if (rt->titleSharingDone)
JS_DESTROY_CONDVAR(rt->titleSharingDone);
if (rt->debuggerLock)
JS_DESTROY_LOCK(rt->debuggerLock);
#else
@ -866,8 +866,8 @@ JS_EndRequest(JSContext *cx)
{
#ifdef JS_THREADSAFE
JSRuntime *rt;
JSScope *scope, **todop;
uintN nshares;
JSTitle *title, **todop;
JSBool shared;
CHECK_REQUEST(cx);
JS_ASSERT(cx->requestDepth > 0);
@ -879,16 +879,16 @@ JS_EndRequest(JSContext *cx)
cx->requestDepth = 0;
cx->outstandingRequests--;
/* See whether cx has any single-threaded scopes to start sharing. */
todop = &rt->scopeSharingTodo;
nshares = 0;
while ((scope = *todop) != NO_SCOPE_SHARING_TODO) {
if (scope->ownercx != cx) {
todop = &scope->u.link;
/* See whether cx has any single-threaded titles to start sharing. */
todop = &rt->titleSharingTodo;
shared = JS_FALSE;
while ((title = *todop) != NO_TITLE_SHARING_TODO) {
if (title->ownercx != cx) {
todop = &title->u.link;
continue;
}
*todop = scope->u.link;
scope->u.link = NULL; /* null u.link for sanity ASAP */
*todop = title->u.link;
title->u.link = NULL; /* null u.link for sanity ASAP */
/*
* If js_DropObjectMap returns null, we held the last ref to scope.
@ -897,15 +897,15 @@ JS_EndRequest(JSContext *cx)
* requires that the GC ran (e.g., from an operation callback)
* during this request, but possible.
*/
if (js_DropObjectMap(cx, &scope->map, NULL)) {
js_InitLock(&scope->lock);
scope->u.count = 0; /* NULL may not pun as 0 */
js_FinishSharingScope(cx, scope); /* set ownercx = NULL */
nshares++;
if (js_DropObjectMap(cx, TITLE_TO_MAP(title), NULL)) {
js_InitLock(&title->lock);
title->u.count = 0; /* NULL may not pun as 0 */
js_FinishSharingTitle(cx, title); /* set ownercx = NULL */
shared = JS_TRUE;
}
}
if (nshares)
JS_NOTIFY_ALL_CONDVAR(rt->scopeSharingDone);
if (shared)
JS_NOTIFY_ALL_CONDVAR(rt->titleSharingDone);
/* Give the GC a chance to run if this was the last request running. */
JS_ASSERT(rt->requestCount > 0);
@ -2943,10 +2943,10 @@ JS_SealObject(JSContext *cx, JSObject *obj, JSBool deep)
#if defined JS_THREADSAFE && defined DEBUG
/* Insist on scope being used exclusively by cx's thread. */
if (scope->ownercx != cx) {
if (scope->title.ownercx != cx) {
JS_LOCK_OBJ(cx, obj);
JS_ASSERT(OBJ_SCOPE(obj) == scope);
JS_ASSERT(scope->ownercx == cx);
JS_ASSERT(scope->title.ownercx == cx);
JS_UNLOCK_SCOPE(cx, scope);
}
#endif

View File

@ -299,26 +299,26 @@ struct JSRuntime {
PRCondVar *stateChange;
/*
* State for sharing single-threaded scopes, once a second thread tries to
* lock a scope. The scopeSharingDone condvar is protected by rt->gcLock,
* State for sharing single-threaded titles, once a second thread tries to
* lock a title. The titleSharingDone condvar is protected by rt->gcLock
* to minimize number of locks taken in JS_EndRequest.
*
* The scopeSharingTodo linked list is likewise "global" per runtime, not
* The titleSharingTodo linked list is likewise "global" per runtime, not
* one-list-per-context, to conserve space over all contexts, optimizing
* for the likely case that scopes become shared rarely, and among a very
* for the likely case that titles become shared rarely, and among a very
* small set of threads (contexts).
*/
PRCondVar *scopeSharingDone;
JSScope *scopeSharingTodo;
PRCondVar *titleSharingDone;
JSTitle *titleSharingTodo;
/*
* Magic terminator for the rt->scopeSharingTodo linked list, threaded through
* scope->u.link. This hack allows us to test whether a scope is on the list
* by asking whether scope->u.link is non-null. We use a large, likely bogus
* Magic terminator for the rt->titleSharingTodo linked list, threaded through
* title->u.link. This hack allows us to test whether a title is on the list
* by asking whether title->u.link is non-null. We use a large, likely bogus
* pointer here to distinguish this value from any valid u.count (small int)
* value.
*/
#define NO_SCOPE_SHARING_TODO ((JSScope *) 0xfeedbeef)
#define NO_TITLE_SHARING_TODO ((JSTitle *) 0xfeedbeef)
/*
* Lock serializing trapList and watchPointList accesses, and count of all
@ -427,13 +427,13 @@ struct JSRuntime {
jsrefcount nonInlineCalls;
jsrefcount constructs;
/* Scope lock and property metering. */
/* Title lock and scope property metering. */
jsrefcount claimAttempts;
jsrefcount claimedScopes;
jsrefcount claimedTitles;
jsrefcount deadContexts;
jsrefcount deadlocksAvoided;
jsrefcount liveScopes;
jsrefcount sharedScopes;
jsrefcount sharedTitles;
jsrefcount totalScopes;
jsrefcount liveScopeProps;
jsrefcount liveScopePropsPreSweep;
@ -769,9 +769,9 @@ struct JSContext {
jsrefcount requestDepth;
/* Same as requestDepth but ignoring JS_SuspendRequest/JS_ResumeRequest */
jsrefcount outstandingRequests;
JSScope *scopeToShare; /* weak reference, see jslock.c */
JSScope *lockedSealedScope; /* weak ref, for low-cost sealed
scope locking */
JSTitle *titleToShare; /* weak reference, see jslock.c */
JSTitle *lockedSealedTitle; /* weak ref, for low-cost sealed
title locking */
JSCList threadLinks; /* JSThread contextList linkage */
#define CX_FROM_THREAD_LINKS(tl) \
@ -897,7 +897,7 @@ js_DestroyContext(JSContext *cx, JSDestroyContextMode mode);
/*
* Return true if cx points to a context in rt->contextList, else return false.
* NB: the caller (see jslock.c:ClaimScope) must hold rt->gcLock.
* NB: the caller (see jslock.c:ClaimTitle) must hold rt->gcLock.
*/
extern JSBool
js_ValidContextPointer(JSRuntime *rt, JSContext *cx);

View File

@ -316,70 +316,77 @@ js_unlog_scope(JSScope *scope)
* (i) rt->gcLock held
*/
static JSBool
WillDeadlock(JSScope *scope, JSContext *cx)
WillDeadlock(JSTitle *title, JSContext *cx)
{
JSContext *ownercx;
do {
ownercx = scope->ownercx;
ownercx = title->ownercx;
if (ownercx == cx) {
JS_RUNTIME_METER(cx->runtime, deadlocksAvoided);
return JS_TRUE;
}
} while (ownercx && (scope = ownercx->scopeToShare) != NULL);
} while (ownercx && (title = ownercx->titleToShare) != NULL);
return JS_FALSE;
}
/*
* Make scope multi-threaded, i.e. share its ownership among contexts in rt
* Make title 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
* from ClaimTitle, immediately below, when we detect deadlock were we to wait
* for title's lock, because its ownercx is waiting on a title owned by the
* calling cx.
*
* (i) rt->gcLock held
*/
static void
ShareScope(JSContext *cx, JSScope *scope)
ShareTitle(JSContext *cx, JSTitle *title)
{
JSRuntime *rt;
JSScope **todop;
JSTitle **todop;
rt = cx->runtime;
if (scope->u.link) {
for (todop = &rt->scopeSharingTodo; *todop != scope;
if (title->u.link) {
for (todop = &rt->titleSharingTodo; *todop != title;
todop = &(*todop)->u.link) {
JS_ASSERT(*todop != NO_SCOPE_SHARING_TODO);
JS_ASSERT(*todop != NO_TITLE_SHARING_TODO);
}
*todop = scope->u.link;
scope->u.link = NULL; /* null u.link for sanity ASAP */
JS_NOTIFY_ALL_CONDVAR(rt->scopeSharingDone);
*todop = title->u.link;
title->u.link = NULL; /* null u.link for sanity ASAP */
JS_NOTIFY_ALL_CONDVAR(rt->titleSharingDone);
}
js_InitLock(&scope->lock);
scope->u.count = 0;
js_FinishSharingScope(cx, scope);
js_InitLock(&title->lock);
title->u.count = 0;
js_FinishSharingTitle(cx, title);
}
/*
* js_FinishSharingScope is the tail part of ShareScope, split out to become a
* js_FinishSharingTitle is the tail part of ShareTitle, split out to become a
* subroutine of JS_EndRequest too. The bulk of the work here involves making
* mutable strings in the scope's object's slots be immutable. We have to do
* mutable strings in the title's object's slots be immutable. We have to do
* this because such strings will soon be available to multiple threads, so
* their buffers can't be realloc'd any longer in js_ConcatStrings, and their
* members can't be modified by js_ConcatStrings, js_MinimizeDependentStrings,
* or js_UndependString.
*
* The last bit of work done by js_FinishSharingScope nulls scope->ownercx and
* updates rt->sharedScopes.
* The last bit of work done by js_FinishSharingTitle nulls title->ownercx and
* updates rt->sharedTitles.
*/
void
js_FinishSharingScope(JSContext *cx, JSScope *scope)
js_FinishSharingTitle(JSContext *cx, JSTitle *title)
{
JSObjectMap *map;
JSScope *scope;
JSObject *obj;
uint32 nslots, i;
jsval v;
map = TITLE_TO_MAP(title);
if (!MAP_IS_NATIVE(map))
return;
scope = (JSScope *)map;
obj = scope->object;
nslots = LOCKED_OBJ_NSLOTS(obj);
for (i = 0; i != nslots; ++i) {
@ -396,20 +403,20 @@ js_FinishSharingScope(JSContext *cx, JSScope *scope)
}
}
scope->ownercx = NULL; /* NB: set last, after lock init */
JS_RUNTIME_METER(cx->runtime, sharedScopes);
title->ownercx = NULL; /* NB: set last, after lock init */
JS_RUNTIME_METER(cx->runtime, sharedTitles);
}
/*
* Given a scope with apparently non-null ownercx different from cx, try to
* set ownercx to cx, claiming exclusive (single-threaded) ownership of scope.
* Given a title with apparently non-null ownercx different from cx, try to
* set ownercx to cx, claiming exclusive (single-threaded) ownership of title.
* If we claim ownership, return true. Otherwise, we wait for ownercx to be
* set to null (indicating that scope is multi-threaded); or if waiting would
* deadlock, we set ownercx to null ourselves via ShareScope. In any case,
* set to null (indicating that title is multi-threaded); or if waiting would
* deadlock, we set ownercx to null ourselves via ShareTitle. In any case,
* once ownercx is null we return false.
*/
static JSBool
ClaimScope(JSScope *scope, JSContext *cx)
ClaimTitle(JSTitle *title, JSContext *cx)
{
JSRuntime *rt;
JSContext *ownercx;
@ -421,69 +428,69 @@ ClaimScope(JSScope *scope, JSContext *cx)
JS_LOCK_GC(rt);
/* Reload in case ownercx went away while we blocked on the lock. */
while ((ownercx = scope->ownercx) != NULL) {
while ((ownercx = title->ownercx) != NULL) {
/*
* Avoid selflock if ownercx is dead, or is not running a request, or
* has the same thread as cx. Set scope->ownercx to cx so that the
* has the same thread as cx. Set title->ownercx to cx so that the
* matching JS_UNLOCK_SCOPE or JS_UNLOCK_OBJ macro call will take the
* fast path around the corresponding js_UnlockScope or js_UnlockObj
* fast path around the corresponding js_UnlockTitle or js_UnlockObj
* function call.
*
* If scope->u.link is non-null, scope has already been inserted on
* the rt->scopeSharingTodo list, because another thread's context
* already wanted to lock scope while ownercx was running a request.
* We can't claim any scope whose u.link is non-null at this point,
* If title->u.link is non-null, title has already been inserted on
* the rt->titleSharingTodo list, because another thread's context
* already wanted to lock title while ownercx was running a request.
* We can't claim any title whose u.link is non-null at this point,
* even if ownercx->requestDepth is 0 (see below where we suspend our
* request before waiting on rt->scopeSharingDone).
* request before waiting on rt->titleSharingDone).
*/
if (!scope->u.link &&
if (!title->u.link &&
(!js_ValidContextPointer(rt, ownercx) ||
!ownercx->requestDepth ||
ownercx->thread == cx->thread)) {
JS_ASSERT(scope->u.count == 0);
scope->ownercx = cx;
JS_ASSERT(title->u.count == 0);
title->ownercx = cx;
JS_UNLOCK_GC(rt);
JS_RUNTIME_METER(rt, claimedScopes);
JS_RUNTIME_METER(rt, claimedTitles);
return JS_TRUE;
}
/*
* Avoid deadlock if scope's owner context is waiting on a scope that
* we own, by revoking scope's ownership. This approach to deadlock
* avoidance works because the engine never nests scope locks.
* Avoid deadlock if title's owner context is waiting on a title that
* we own, by revoking title's ownership. This approach to deadlock
* avoidance works because the engine never nests title locks.
*
* If cx could hold locks on ownercx->scopeToShare, or if ownercx
* 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".
* The engine promotes a scope from exclusive to shared access only
* when locking, never when holding or unlocking.
* If cx could hold locks on ownercx->titleToShare, or if ownercx could
* hold locks on title, 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". The engine
* promotes a title from exclusive to shared access only when locking,
* never when holding or unlocking.
*
* Avoid deadlock before any of this scope/context cycle detection if
* Avoid deadlock before any of this title/context cycle detection if
* cx is on the active GC's thread, because in that case, no requests
* will run until the GC completes. Any scope wanted by the GC (from
* will run until the GC completes. Any title wanted by the GC (from
* a finalizer) that can't be claimed must become shared.
*/
if (rt->gcThread == cx->thread ||
(ownercx->scopeToShare &&
WillDeadlock(ownercx->scopeToShare, cx))) {
ShareScope(cx, scope);
(ownercx->titleToShare &&
WillDeadlock(ownercx->titleToShare, cx))) {
ShareTitle(cx, title);
break;
}
/*
* Thanks to the non-zero NO_SCOPE_SHARING_TODO link terminator, we
* can decide whether scope is on rt->scopeSharingTodo with a single
* Thanks to the non-zero NO_TITLE_SHARING_TODO link terminator, we
* can decide whether title is on rt->titleSharingTodo with a single
* non-null test, and avoid double-insertion bugs.
*/
if (!scope->u.link) {
scope->u.link = rt->scopeSharingTodo;
rt->scopeSharingTodo = scope;
js_HoldObjectMap(cx, &scope->map);
if (!title->u.link) {
title->u.link = rt->titleSharingTodo;
rt->titleSharingTodo = title;
js_HoldObjectMap(cx, TITLE_TO_MAP(title));
}
/*
* Inline JS_SuspendRequest before we wait on rt->scopeSharingDone,
* Inline JS_SuspendRequest before we wait on rt->titleSharingDone,
* saving and clearing cx->requestDepth so we don't deadlock if the
* GC needs to run on ownercx.
*
@ -504,16 +511,16 @@ ClaimScope(JSScope *scope, JSContext *cx)
}
/*
* We know that some other thread's context owns scope, which is now
* linked onto rt->scopeSharingTodo, awaiting the end of that other
* thread's request. So it is safe to wait on rt->scopeSharingDone.
* We know that some other thread's context owns title, which is now
* linked onto rt->titleSharingTodo, awaiting the end of that other
* thread's request. So it is safe to wait on rt->titleSharingDone.
*/
cx->scopeToShare = scope;
stat = PR_WaitCondVar(rt->scopeSharingDone, PR_INTERVAL_NO_TIMEOUT);
cx->titleToShare = title;
stat = PR_WaitCondVar(rt->titleSharingDone, PR_INTERVAL_NO_TIMEOUT);
JS_ASSERT(stat != PR_FAILURE);
/*
* Inline JS_ResumeRequest after waiting on rt->scopeSharingDone,
* Inline JS_ResumeRequest after waiting on rt->titleSharingDone,
* restoring cx->requestDepth. Same note as above for the inlined,
* specialized JS_SuspendRequest code: beware rt->gcThread.
*/
@ -527,18 +534,18 @@ ClaimScope(JSScope *scope, JSContext *cx)
}
/*
* Don't clear cx->scopeToShare until after we're through waiting on
* Don't clear cx->titleToShare until after we're through waiting on
* all condition variables protected by rt->gcLock -- that includes
* rt->scopeSharingDone *and* rt->gcDone (hidden in JS_AWAIT_GC_DONE,
* rt->titleSharingDone *and* rt->gcDone (hidden in JS_AWAIT_GC_DONE,
* in the inlined JS_ResumeRequest code immediately above).
*
* Otherwise, the GC could easily deadlock with another thread that
* owns a scope wanted by a finalizer. By keeping cx->scopeToShare
* owns a title wanted by a finalizer. By keeping cx->titleToShare
* set till here, we ensure that such deadlocks are detected, which
* results in the finalized object's scope being shared (it must, of
* results in the finalized object's title being shared (it must, of
* course, have other, live objects sharing it).
*/
cx->scopeToShare = NULL;
cx->titleToShare = NULL;
}
JS_UNLOCK_GC(rt);
@ -551,6 +558,7 @@ js_GetSlotThreadSafe(JSContext *cx, JSObject *obj, uint32 slot)
{
jsval v;
JSScope *scope;
JSTitle *title;
#ifndef NSPR_LOCK
JSThinLock *tl;
jsword me;
@ -574,7 +582,8 @@ js_GetSlotThreadSafe(JSContext *cx, JSObject *obj, uint32 slot)
* and contention-free multi-threaded cases.
*/
scope = OBJ_SCOPE(obj);
JS_ASSERT(scope->ownercx != cx);
title = &scope->title;
JS_ASSERT(title->ownercx != cx);
JS_ASSERT(slot < obj->map->freeslot);
/*
@ -585,12 +594,12 @@ js_GetSlotThreadSafe(JSContext *cx, JSObject *obj, uint32 slot)
*/
if (CX_THREAD_IS_RUNNING_GC(cx) ||
(SCOPE_IS_SEALED(scope) && scope->object == obj) ||
(scope->ownercx && ClaimScope(scope, cx))) {
(title->ownercx && ClaimTitle(title, cx))) {
return STOBJ_GET_SLOT(obj, slot);
}
#ifndef NSPR_LOCK
tl = &scope->lock;
tl = &title->lock;
me = CX_THINLOCK_ID(cx);
JS_ASSERT(CURRENT_THREAD_IS_ME(me));
if (js_CompareAndSwap(&tl->owner, 0, me)) {
@ -604,9 +613,9 @@ js_GetSlotThreadSafe(JSContext *cx, JSObject *obj, uint32 slot)
v = STOBJ_GET_SLOT(obj, slot);
if (!js_CompareAndSwap(&tl->owner, me, 0)) {
/* Assert that scope locks never revert to flyweight. */
JS_ASSERT(scope->ownercx != cx);
JS_ASSERT(title->ownercx != cx);
LOGIT(scope, '1');
scope->u.count = 1;
title->u.count = 1;
js_UnlockObj(cx, obj);
}
return v;
@ -631,15 +640,16 @@ js_GetSlotThreadSafe(JSContext *cx, JSObject *obj, uint32 slot)
* object's scope (whose lock was not flyweight, else we wouldn't be here
* in the first place!).
*/
scope = OBJ_SCOPE(obj);
if (scope->ownercx != cx)
js_UnlockScope(cx, scope);
title = &OBJ_SCOPE(obj)->title;
if (title->ownercx != cx)
js_UnlockTitle(cx, title);
return v;
}
void
js_SetSlotThreadSafe(JSContext *cx, JSObject *obj, uint32 slot, jsval v)
{
JSTitle *title;
JSScope *scope;
#ifndef NSPR_LOCK
JSThinLock *tl;
@ -667,7 +677,8 @@ js_SetSlotThreadSafe(JSContext *cx, JSObject *obj, uint32 slot, jsval v)
* and contention-free multi-threaded cases.
*/
scope = OBJ_SCOPE(obj);
JS_ASSERT(scope->ownercx != cx);
title = &scope->title;
JS_ASSERT(title->ownercx != cx);
JS_ASSERT(slot < obj->map->freeslot);
/*
@ -678,13 +689,13 @@ js_SetSlotThreadSafe(JSContext *cx, JSObject *obj, uint32 slot, jsval v)
*/
if (CX_THREAD_IS_RUNNING_GC(cx) ||
(SCOPE_IS_SEALED(scope) && scope->object == obj) ||
(scope->ownercx && ClaimScope(scope, cx))) {
(title->ownercx && ClaimTitle(title, cx))) {
LOCKED_OBJ_WRITE_BARRIER(cx, obj, slot, v);
return;
}
#ifndef NSPR_LOCK
tl = &scope->lock;
tl = &title->lock;
me = CX_THINLOCK_ID(cx);
JS_ASSERT(CURRENT_THREAD_IS_ME(me));
if (js_CompareAndSwap(&tl->owner, 0, me)) {
@ -692,9 +703,9 @@ js_SetSlotThreadSafe(JSContext *cx, JSObject *obj, uint32 slot, jsval v)
LOCKED_OBJ_WRITE_BARRIER(cx, obj, slot, v);
if (!js_CompareAndSwap(&tl->owner, me, 0)) {
/* Assert that scope locks never revert to flyweight. */
JS_ASSERT(scope->ownercx != cx);
JS_ASSERT(title->ownercx != cx);
LOGIT(scope, '1');
scope->u.count = 1;
title->u.count = 1;
js_UnlockObj(cx, obj);
}
return;
@ -714,9 +725,9 @@ js_SetSlotThreadSafe(JSContext *cx, JSObject *obj, uint32 slot, jsval v)
/*
* Same drill as above, in js_GetSlotThreadSafe.
*/
scope = OBJ_SCOPE(obj);
if (scope->ownercx != cx)
js_UnlockScope(cx, scope);
title = &OBJ_SCOPE(obj)->title;
if (title->ownercx != cx)
js_UnlockTitle(cx, title);
}
#ifndef NSPR_LOCK
@ -1059,50 +1070,50 @@ js_UnlockRuntime(JSRuntime *rt)
}
void
js_LockScope(JSContext *cx, JSScope *scope)
js_LockTitle(JSContext *cx, JSTitle *title)
{
jsword me = CX_THINLOCK_ID(cx);
JS_ASSERT(CURRENT_THREAD_IS_ME(me));
JS_ASSERT(scope->ownercx != cx);
JS_ASSERT(title->ownercx != cx);
if (CX_THREAD_IS_RUNNING_GC(cx))
return;
if (scope->ownercx && ClaimScope(scope, cx))
if (title->ownercx && ClaimTitle(title, cx))
return;
if (Thin_RemoveWait(ReadWord(scope->lock.owner)) == me) {
JS_ASSERT(scope->u.count > 0);
if (Thin_RemoveWait(ReadWord(title->lock.owner)) == me) {
JS_ASSERT(title->u.count > 0);
LOGIT(scope, '+');
scope->u.count++;
title->u.count++;
} else {
JSThinLock *tl = &scope->lock;
JSThinLock *tl = &title->lock;
JS_LOCK0(tl, me);
JS_ASSERT(scope->u.count == 0);
JS_ASSERT(title->u.count == 0);
LOGIT(scope, '1');
scope->u.count = 1;
title->u.count = 1;
}
}
void
js_UnlockScope(JSContext *cx, JSScope *scope)
js_UnlockTitle(JSContext *cx, JSTitle *title)
{
jsword me = CX_THINLOCK_ID(cx);
/* We hope compilers use me instead of reloading cx->thread in the macro. */
if (CX_THREAD_IS_RUNNING_GC(cx))
return;
if (cx->lockedSealedScope == scope) {
cx->lockedSealedScope = NULL;
if (cx->lockedSealedTitle == title) {
cx->lockedSealedTitle = NULL;
return;
}
/*
* If scope->ownercx is not null, it's likely that two contexts not using
* requests nested locks for scope. The first context, cx here, claimed
* scope; the second, scope->ownercx here, re-claimed it because the first
* If title->ownercx is not null, it's likely that two contexts not using
* requests nested locks for title. The first context, cx here, claimed
* title; the second, title->ownercx here, re-claimed it because the first
* was not in a request, or was on the same thread. We don't want to keep
* track of such nesting, because it penalizes the common non-nested case.
* Instead of asserting here and silently coping, we simply re-claim scope
* Instead of asserting here and silently coping, we simply re-claim title
* for cx and return.
*
* See http://bugzilla.mozilla.org/show_bug.cgi?id=229200 for a real world
@ -1110,94 +1121,94 @@ js_UnlockScope(JSContext *cx, JSScope *scope)
* to be the only thread that runs the GC) combined with multiple contexts
* per thread has led to such request-less nesting.
*/
if (scope->ownercx) {
JS_ASSERT(scope->u.count == 0);
JS_ASSERT(scope->lock.owner == 0);
scope->ownercx = cx;
if (title->ownercx) {
JS_ASSERT(title->u.count == 0);
JS_ASSERT(title->lock.owner == 0);
title->ownercx = cx;
return;
}
JS_ASSERT(scope->u.count > 0);
if (Thin_RemoveWait(ReadWord(scope->lock.owner)) != me) {
JS_ASSERT(title->u.count > 0);
if (Thin_RemoveWait(ReadWord(title->lock.owner)) != me) {
JS_ASSERT(0); /* unbalanced unlock */
return;
}
LOGIT(scope, '-');
if (--scope->u.count == 0) {
JSThinLock *tl = &scope->lock;
if (--title->u.count == 0) {
JSThinLock *tl = &title->lock;
JS_UNLOCK0(tl, me);
}
}
/*
* NB: oldscope may be null if our caller is js_GetMutableScope and it just
* dropped the last reference to oldscope.
* NB: oldtitle may be null if our caller is js_GetMutableScope and it just
* dropped the last reference to oldtitle.
*/
void
js_TransferScopeLock(JSContext *cx, JSScope *oldscope, JSScope *newscope)
js_TransferTitle(JSContext *cx, JSTitle *oldtitle, JSTitle *newtitle)
{
jsword me;
JSThinLock *tl;
JS_ASSERT(JS_IS_SCOPE_LOCKED(cx, newscope));
JS_ASSERT(JS_IS_TITLE_LOCKED(cx, newtitle));
/*
* If the last reference to oldscope went away, newscope needs no lock
* If the last reference to oldtitle went away, newtitle needs no lock
* state update.
*/
if (!oldscope)
if (!oldtitle)
return;
JS_ASSERT(JS_IS_SCOPE_LOCKED(cx, oldscope));
JS_ASSERT(JS_IS_TITLE_LOCKED(cx, oldtitle));
/*
* Special case in js_LockScope and js_UnlockScope for the GC calling
* Special case in js_LockTitle and js_UnlockTitle for the GC calling
* code that locks, unlocks, or mutates. Nothing to do in these cases,
* because scope and newscope were "locked" by the GC thread, so neither
* because title and newtitle were "locked" by the GC thread, so neither
* was actually locked.
*/
if (CX_THREAD_IS_RUNNING_GC(cx))
return;
/*
* Special case in js_LockObj and js_UnlockScope for locking the sealed
* Special case in js_LockObj and js_UnlockTitle for locking the sealed
* scope of an object that owns that scope (the prototype or mutated obj
* for which OBJ_SCOPE(obj)->object == obj), and unlocking it.
*/
JS_ASSERT(cx->lockedSealedScope != newscope);
if (cx->lockedSealedScope == oldscope) {
JS_ASSERT(newscope->ownercx == cx ||
(!newscope->ownercx && newscope->u.count == 1));
cx->lockedSealedScope = NULL;
JS_ASSERT(cx->lockedSealedTitle != newtitle);
if (cx->lockedSealedTitle == oldtitle) {
JS_ASSERT(newtitle->ownercx == cx ||
(!newtitle->ownercx && newtitle->u.count == 1));
cx->lockedSealedTitle = NULL;
return;
}
/*
* If oldscope is single-threaded, there's nothing to do.
* If oldtitle is single-threaded, there's nothing to do.
*/
if (oldscope->ownercx) {
JS_ASSERT(oldscope->ownercx == cx);
JS_ASSERT(newscope->ownercx == cx ||
(!newscope->ownercx && newscope->u.count == 1));
if (oldtitle->ownercx) {
JS_ASSERT(oldtitle->ownercx == cx);
JS_ASSERT(newtitle->ownercx == cx ||
(!newtitle->ownercx && newtitle->u.count == 1));
return;
}
/*
* We transfer oldscope->u.count only if newscope is not single-threaded.
* Flow unwinds from here through some number of JS_UNLOCK_SCOPE and/or
* JS_UNLOCK_OBJ macro calls, which will decrement newscope->u.count only
* if they find newscope->ownercx != cx.
* We transfer oldtitle->u.count only if newtitle is not single-threaded.
* Flow unwinds from here through some number of JS_UNLOCK_TITLE and/or
* JS_UNLOCK_OBJ macro calls, which will decrement newtitle->u.count only
* if they find newtitle->ownercx != cx.
*/
if (newscope->ownercx != cx) {
JS_ASSERT(!newscope->ownercx);
newscope->u.count = oldscope->u.count;
if (newtitle->ownercx != cx) {
JS_ASSERT(!newtitle->ownercx);
newtitle->u.count = oldtitle->u.count;
}
/*
* Reset oldscope's lock state so that it is completely unlocked.
* Reset oldtitle's lock state so that it is completely unlocked.
*/
LOGIT(oldscope, '0');
oldscope->u.count = 0;
tl = &oldscope->lock;
oldtitle->u.count = 0;
tl = &oldtitle->lock;
me = CX_THINLOCK_ID(cx);
JS_UNLOCK0(tl, me);
}
@ -1206,33 +1217,35 @@ void
js_LockObj(JSContext *cx, JSObject *obj)
{
JSScope *scope;
JSTitle *title;
JS_ASSERT(OBJ_IS_NATIVE(obj));
/*
* We must test whether the GC is calling and return without mutating any
* state, especially cx->lockedSealedScope. Note asymmetry with respect to
* js_UnlockObj, which is a thin-layer on top of js_UnlockScope.
* js_UnlockObj, which is a thin-layer on top of js_UnlockTitle.
*/
if (CX_THREAD_IS_RUNNING_GC(cx))
return;
for (;;) {
scope = OBJ_SCOPE(obj);
title = &scope->title;
if (SCOPE_IS_SEALED(scope) && scope->object == obj &&
!cx->lockedSealedScope) {
cx->lockedSealedScope = scope;
!cx->lockedSealedTitle) {
cx->lockedSealedTitle = title;
return;
}
js_LockScope(cx, scope);
js_LockTitle(cx, title);
/* If obj still has this scope, we're done. */
if (scope == OBJ_SCOPE(obj))
return;
/* Lost a race with a mutator; retry with obj's new scope. */
js_UnlockScope(cx, scope);
js_UnlockTitle(cx, title);
}
}
@ -1240,7 +1253,38 @@ void
js_UnlockObj(JSContext *cx, JSObject *obj)
{
JS_ASSERT(OBJ_IS_NATIVE(obj));
js_UnlockScope(cx, OBJ_SCOPE(obj));
js_UnlockTitle(cx, &OBJ_SCOPE(obj)->title);
}
void
js_InitTitle(JSContext *cx, JSTitle *title)
{
#ifdef JS_THREADSAFE
title->ownercx = cx;
memset(&title->lock, 0, sizeof title->lock);
/*
* Set u.link = NULL, not u.count = 0, in case the target architecture's
* null pointer has a non-zero integer representation.
*/
title->u.link = NULL;
#ifdef JS_DEBUG_TITLE_LOCKS
title->file[0] = title->file[1] = title->file[2] = title->file[3] = NULL;
title->line[0] = title->line[1] = title->line[2] = title->line[3] = 0;
#endif
#endif
}
void
js_FinishTitle(JSContext *cx, JSTitle *title)
{
#ifdef JS_THREADSAFE
/* Title must be single-threaded at this point, so set ownercx. */
JS_ASSERT(title->u.count == 0);
title->ownercx = cx;
js_FinishLock(&title->lock);
#endif
}
#ifdef DEBUG
@ -1256,30 +1300,30 @@ js_IsObjLocked(JSContext *cx, JSObject *obj)
{
JSScope *scope = OBJ_SCOPE(obj);
return MAP_IS_NATIVE(&scope->map) && js_IsScopeLocked(cx, scope);
return MAP_IS_NATIVE(&scope->map) && js_IsTitleLocked(cx, &scope->title);
}
JSBool
js_IsScopeLocked(JSContext *cx, JSScope *scope)
js_IsTitleLocked(JSContext *cx, JSTitle *title)
{
/* Special case: the GC locking any object's scope, see js_LockScope. */
/* Special case: the GC locking any object's title, see js_LockTitle. */
if (CX_THREAD_IS_RUNNING_GC(cx))
return JS_TRUE;
/* Special case: locked object owning a sealed scope, see js_LockObj. */
if (cx->lockedSealedScope == scope)
if (cx->lockedSealedTitle == title)
return JS_TRUE;
/*
* General case: the scope is either exclusively owned (by cx), or it has
* General case: the title is either exclusively owned (by cx), or it has
* a thin or fat lock to cope with shared (concurrent) ownership.
*/
if (scope->ownercx) {
JS_ASSERT(scope->ownercx == cx || scope->ownercx->thread == cx->thread);
if (title->ownercx) {
JS_ASSERT(title->ownercx == cx || title->ownercx->thread == cx->thread);
return JS_TRUE;
}
return js_CurrentThreadId() ==
((JSThread *)Thin_RemoveWait(ReadWord(scope->lock.owner)))->id;
((JSThread *)Thin_RemoveWait(ReadWord(title->lock.owner)))->id;
}
#endif /* DEBUG */

View File

@ -81,6 +81,31 @@ typedef struct JSFatLockTable {
JSFatLock *taken;
} JSFatLockTable;
typedef struct JSTitle JSTitle;
struct JSTitle {
JSContext *ownercx; /* creating context, NULL if shared */
JSThinLock lock; /* binary semaphore protecting title */
union { /* union lockful and lock-free state: */
jsrefcount count; /* lock entry count for reentrancy */
JSTitle *link; /* next link in rt->titleSharingTodo */
} u;
#ifdef JS_DEBUG_SCOPE_LOCKS
const char *file[4]; /* file where lock was (re-)taken */
unsigned int line[4]; /* line where lock was (re-)taken */
#endif
};
/*
* Title structures must be immediately preceded by JSObjectMap structures for
* maps that use titles for threadsafety. This is enforced by assertion in
* jsscope.h; see bug 408416 for future remedies to this somewhat fragile
* architecture.
*/
#define TITLE_TO_MAP(title) \
((JSObjectMap *)((char *)(title) - sizeof(JSObjectMap)))
/*
* Atomic increment and decrement for a reference counter, given jsrefcount *p.
* NB: jsrefcount is int32, aka PRInt32, so that pratom.h functions work.
@ -122,17 +147,23 @@ JS_END_EXTERN_C
#include "jsscope.h"
JS_BEGIN_EXTERN_C
#ifdef JS_DEBUG_SCOPE_LOCKS
#ifdef JS_DEBUG_TITLE_LOCKS
#define SET_OBJ_INFO(obj_,file_,line_) \
SET_SCOPE_INFO(OBJ_SCOPE(obj_),file_,line_)
#define SET_OBJ_INFO(obj_, file_, line_) \
SET_SCOPE_INFO(OBJ_SCOPE(obj_), file_, line_)
#define SET_SCOPE_INFO(scope_,file_,line_) \
((scope_)->ownercx ? (void)0 : \
(JS_ASSERT((0 < (scope_)->u.count && (scope_)->u.count <= 4) || \
SCOPE_IS_SEALED(scope_)), \
(void)((scope_)->file[(scope_)->u.count-1] = (file_), \
(scope_)->line[(scope_)->u.count-1] = (line_))))
#define SET_SCOPE_INFO(scope_,file_,line_) \
do { \
JSTitle *title = &(scope_)->title; \
jsrefcount count; \
if (title->ownercx) \
break; \
count = title->u.count; \
JS_ASSERT((0 < count && count <= 4) || \
SCOPE_IS_SEALED(scope_))); \
title->file[count - 1] = (file_); \
title->line[line - 1] = (line_); \
} while (0)
#endif
@ -146,52 +177,60 @@ JS_BEGIN_EXTERN_C
* are for optimizations above the JSObjectOps layer, under which object locks
* normally hide.
*/
#define JS_LOCK_OBJ(cx,obj) ((OBJ_SCOPE(obj)->ownercx == (cx)) \
? (void)0 \
: (js_LockObj(cx, obj), \
#define JS_LOCK_OBJ(cx,obj) ((OBJ_SCOPE(obj)->title.ownercx == (cx)) \
? (void)0 \
: (js_LockObj(cx, obj), \
SET_OBJ_INFO(obj,__FILE__,__LINE__)))
#define JS_UNLOCK_OBJ(cx,obj) ((OBJ_SCOPE(obj)->ownercx == (cx)) \
#define JS_UNLOCK_OBJ(cx,obj) ((OBJ_SCOPE(obj)->title.ownercx == (cx)) \
? (void)0 : js_UnlockObj(cx, obj))
#define JS_LOCK_SCOPE(cx,scope) ((scope)->ownercx == (cx) ? (void)0 \
: (js_LockScope(cx, scope), \
SET_SCOPE_INFO(scope,__FILE__,__LINE__)))
#define JS_UNLOCK_SCOPE(cx,scope) ((scope)->ownercx == (cx) ? (void)0 \
: js_UnlockScope(cx, scope))
#define JS_TRANSFER_SCOPE_LOCK(cx, scope, newscope) \
js_TransferScopeLock(cx, scope, newscope)
#define JS_LOCK_TITLE(cx,title) \
((title)->ownercx == (cx) ? (void)0 \
: (js_LockTitle(cx, (title)), \
SET_TITLE_INFO(title,__FILE__,__LINE__)))
#define JS_UNLOCK_TITLE(cx,title) ((title)->ownercx == (cx) ? (void)0 \
: js_UnlockTitle(cx, title))
#define JS_LOCK_SCOPE(cx,scope) JS_LOCK_TITLE(cx,&(scope)->title)
#define JS_UNLOCK_SCOPE(cx,scope) JS_UNLOCK_TITLE(cx,&(scope)->title)
#define JS_TRANSFER_SCOPE_LOCK(cx, scope, newscope) \
js_TransferTitle(cx, &scope->title, &newscope->title)
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_LockScope(JSContext *cx, JSScope *scope);
extern void js_UnlockScope(JSContext *cx, JSScope *scope);
extern void js_InitTitle(JSContext *cx, JSTitle *title);
extern void js_FinishTitle(JSContext *cx, JSTitle *title);
extern void js_LockTitle(JSContext *cx, JSTitle *title);
extern void js_UnlockTitle(JSContext *cx, JSTitle *title);
extern int js_SetupLocks(int,int);
extern void js_CleanupLocks();
extern void js_TransferScopeLock(JSContext *, JSScope *, JSScope *);
extern void js_TransferTitle(JSContext *, JSTitle *, JSTitle *);
extern JS_FRIEND_API(jsval)
js_GetSlotThreadSafe(JSContext *, JSObject *, uint32);
extern void js_SetSlotThreadSafe(JSContext *, JSObject *, uint32, jsval);
extern void js_InitLock(JSThinLock *);
extern void js_FinishLock(JSThinLock *);
extern void js_FinishSharingScope(JSContext *cx, JSScope *scope);
extern void js_FinishSharingTitle(JSContext *cx, JSTitle *title);
#ifdef DEBUG
#define JS_IS_RUNTIME_LOCKED(rt) js_IsRuntimeLocked(rt)
#define JS_IS_OBJ_LOCKED(cx,obj) js_IsObjLocked(cx,obj)
#define JS_IS_SCOPE_LOCKED(cx,scope) js_IsScopeLocked(cx,scope)
#define JS_IS_TITLE_LOCKED(cx,title) js_IsTitleLocked(cx,title)
extern JSBool js_IsRuntimeLocked(JSRuntime *rt);
extern JSBool js_IsObjLocked(JSContext *cx, JSObject *obj);
extern JSBool js_IsScopeLocked(JSContext *cx, JSScope *scope);
extern JSBool js_IsTitleLocked(JSContext *cx, JSTitle *title);
#else
#define JS_IS_RUNTIME_LOCKED(rt) 0
#define JS_IS_OBJ_LOCKED(cx,obj) 1
#define JS_IS_SCOPE_LOCKED(cx,scope) 1
#define JS_IS_TITLE_LOCKED(cx,title) 1
#endif /* DEBUG */
@ -264,7 +303,7 @@ JS_BEGIN_EXTERN_C
#define JS_IS_RUNTIME_LOCKED(rt) 1
#define JS_IS_OBJ_LOCKED(cx,obj) 1
#define JS_IS_SCOPE_LOCKED(cx,scope) 1
#define JS_IS_TITLE_LOCKED(cx,title) 1
#define JS_LOCK_VOID(cx, e) JS_LOCK_RUNTIME_VOID((cx)->runtime, e)
#endif /* !JS_THREADSAFE */
@ -291,8 +330,8 @@ JS_BEGIN_EXTERN_C
#ifndef SET_OBJ_INFO
#define SET_OBJ_INFO(obj,f,l) ((void)0)
#endif
#ifndef SET_SCOPE_INFO
#define SET_SCOPE_INFO(scope,f,l) ((void)0)
#ifndef SET_TITLE_INFO
#define SET_TITLE_INFO(title,f,l) ((void)0)
#endif
JS_END_EXTERN_C

View File

@ -230,14 +230,14 @@ struct JSObject {
/* Thread-safe functions and wrapper macros for accessing slots in obj. */
#define OBJ_GET_SLOT(cx,obj,slot) \
(OBJ_CHECK_SLOT(obj, slot), \
(OBJ_IS_NATIVE(obj) && OBJ_SCOPE(obj)->ownercx == cx) \
(OBJ_IS_NATIVE(obj) && OBJ_SCOPE(obj)->title.ownercx == cx) \
? LOCKED_OBJ_GET_SLOT(obj, slot) \
: js_GetSlotThreadSafe(cx, obj, slot))
#define OBJ_SET_SLOT(cx,obj,slot,value) \
JS_BEGIN_MACRO \
OBJ_CHECK_SLOT(obj, slot); \
if (OBJ_IS_NATIVE(obj) && OBJ_SCOPE(obj)->ownercx == cx) \
if (OBJ_IS_NATIVE(obj) && OBJ_SCOPE(obj)->title.ownercx == cx) \
LOCKED_OBJ_WRITE_BARRIER(cx, obj, slot, value); \
else \
js_SetSlotThreadSafe(cx, obj, slot, value); \

View File

@ -153,21 +153,8 @@ js_NewScope(JSContext *cx, jsrefcount nrefs, JSObjectOps *ops, JSClass *clasp,
InitMinimalScope(scope);
#ifdef JS_THREADSAFE
scope->ownercx = cx;
memset(&scope->lock, 0, sizeof scope->lock);
/*
* Set u.link = NULL, not u.count = 0, in case the target architecture's
* null pointer has a non-zero integer representation.
*/
scope->u.link = NULL;
#ifdef JS_DEBUG_SCOPE_LOCKS
scope->file[0] = scope->file[1] = scope->file[2] = scope->file[3] = NULL;
scope->line[0] = scope->line[1] = scope->line[2] = scope->line[3] = 0;
js_InitTitle(cx, &scope->title);
#endif
#endif
JS_RUNTIME_METER(cx->runtime, liveScopes);
JS_RUNTIME_METER(cx->runtime, totalScopes);
return scope;
@ -193,10 +180,7 @@ js_DestroyScope(JSContext *cx, JSScope *scope)
#endif
#ifdef JS_THREADSAFE
/* Scope must be single-threaded at this point, so set scope->ownercx. */
JS_ASSERT(scope->u.count == 0);
scope->ownercx = cx;
js_FinishLock(&scope->lock);
js_FinishTitle(cx, &scope->title);
#endif
if (scope->table)
JS_free(cx, scope->table);

View File

@ -197,6 +197,9 @@ JS_BEGIN_EXTERN_C
struct JSScope {
JSObjectMap map; /* base class state */
#ifdef JS_THREADSAFE
JSTitle title; /* lock state */
#endif
JSObject *object; /* object that owns this scope */
uint32 shape; /* property cache shape identifier */
uint8 flags; /* flags, see below */
@ -206,20 +209,14 @@ struct JSScope {
uint32 removedCount; /* removed entry sentinels in table */
JSScopeProperty **table; /* table of ptrs to shared tree nodes */
JSScopeProperty *lastProp; /* pointer to last property added */
#ifdef JS_THREADSAFE
JSContext *ownercx; /* creating context, NULL if shared */
JSThinLock lock; /* binary semaphore protecting scope */
union { /* union lockful and lock-free state: */
jsrefcount count; /* lock entry count for reentrancy */
JSScope *link; /* next link in rt->scopeSharingTodo */
} u;
#ifdef JS_DEBUG_SCOPE_LOCKS
const char *file[4]; /* file where lock was (re-)taken */
unsigned int line[4]; /* line where lock was (re-)taken */
#endif
#endif
};
#ifdef JS_THREADSAFE
JS_STATIC_ASSERT(offsetof(JSScope, title) == sizeof(JSObjectMap));
#endif
#define JS_IS_SCOPE_LOCKED(cx, scope) JS_IS_TITLE_LOCKED(cx, &(scope)->title)
#define OBJ_SCOPE(obj) ((JSScope *)(obj)->map)
#define SCOPE_GENERATE_PCTYPE(cx,scope) ((scope)->shape = js_GenerateShape(cx))