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

View File

@ -299,26 +299,26 @@ struct JSRuntime {
PRCondVar *stateChange; PRCondVar *stateChange;
/* /*
* State for sharing single-threaded scopes, once a second thread tries to * State for sharing single-threaded titles, once a second thread tries to
* lock a scope. The scopeSharingDone condvar is protected by rt->gcLock, * lock a title. The titleSharingDone condvar is protected by rt->gcLock
* to minimize number of locks taken in JS_EndRequest. * 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 * 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). * small set of threads (contexts).
*/ */
PRCondVar *scopeSharingDone; PRCondVar *titleSharingDone;
JSScope *scopeSharingTodo; JSTitle *titleSharingTodo;
/* /*
* Magic terminator for the rt->scopeSharingTodo linked list, threaded through * Magic terminator for the rt->titleSharingTodo linked list, threaded through
* scope->u.link. This hack allows us to test whether a scope is on the list * title->u.link. This hack allows us to test whether a title is on the list
* by asking whether scope->u.link is non-null. We use a large, likely bogus * 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) * pointer here to distinguish this value from any valid u.count (small int)
* value. * 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 * Lock serializing trapList and watchPointList accesses, and count of all
@ -427,13 +427,13 @@ struct JSRuntime {
jsrefcount nonInlineCalls; jsrefcount nonInlineCalls;
jsrefcount constructs; jsrefcount constructs;
/* Scope lock and property metering. */ /* Title lock and scope property metering. */
jsrefcount claimAttempts; jsrefcount claimAttempts;
jsrefcount claimedScopes; jsrefcount claimedTitles;
jsrefcount deadContexts; jsrefcount deadContexts;
jsrefcount deadlocksAvoided; jsrefcount deadlocksAvoided;
jsrefcount liveScopes; jsrefcount liveScopes;
jsrefcount sharedScopes; jsrefcount sharedTitles;
jsrefcount totalScopes; jsrefcount totalScopes;
jsrefcount liveScopeProps; jsrefcount liveScopeProps;
jsrefcount liveScopePropsPreSweep; jsrefcount liveScopePropsPreSweep;
@ -769,9 +769,9 @@ struct JSContext {
jsrefcount requestDepth; jsrefcount requestDepth;
/* Same as requestDepth but ignoring JS_SuspendRequest/JS_ResumeRequest */ /* Same as requestDepth but ignoring JS_SuspendRequest/JS_ResumeRequest */
jsrefcount outstandingRequests; jsrefcount outstandingRequests;
JSScope *scopeToShare; /* weak reference, see jslock.c */ JSTitle *titleToShare; /* weak reference, see jslock.c */
JSScope *lockedSealedScope; /* weak ref, for low-cost sealed JSTitle *lockedSealedTitle; /* weak ref, for low-cost sealed
scope locking */ title locking */
JSCList threadLinks; /* JSThread contextList linkage */ JSCList threadLinks; /* JSThread contextList linkage */
#define CX_FROM_THREAD_LINKS(tl) \ #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. * 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 extern JSBool
js_ValidContextPointer(JSRuntime *rt, JSContext *cx); js_ValidContextPointer(JSRuntime *rt, JSContext *cx);

View File

@ -316,70 +316,77 @@ js_unlog_scope(JSScope *scope)
* (i) rt->gcLock held * (i) rt->gcLock held
*/ */
static JSBool static JSBool
WillDeadlock(JSScope *scope, JSContext *cx) WillDeadlock(JSTitle *title, JSContext *cx)
{ {
JSContext *ownercx; JSContext *ownercx;
do { do {
ownercx = scope->ownercx; ownercx = title->ownercx;
if (ownercx == cx) { if (ownercx == cx) {
JS_RUNTIME_METER(cx->runtime, deadlocksAvoided); JS_RUNTIME_METER(cx->runtime, deadlocksAvoided);
return JS_TRUE; return JS_TRUE;
} }
} while (ownercx && (scope = ownercx->scopeToShare) != NULL); } while (ownercx && (title = ownercx->titleToShare) != NULL);
return JS_FALSE; 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 * 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 * from ClaimTitle, 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 * for title's lock, because its ownercx is waiting on a title owned by the
* calling cx. * calling cx.
* *
* (i) rt->gcLock held * (i) rt->gcLock held
*/ */
static void static void
ShareScope(JSContext *cx, JSScope *scope) ShareTitle(JSContext *cx, JSTitle *title)
{ {
JSRuntime *rt; JSRuntime *rt;
JSScope **todop; JSTitle **todop;
rt = cx->runtime; rt = cx->runtime;
if (scope->u.link) { if (title->u.link) {
for (todop = &rt->scopeSharingTodo; *todop != scope; for (todop = &rt->titleSharingTodo; *todop != title;
todop = &(*todop)->u.link) { todop = &(*todop)->u.link) {
JS_ASSERT(*todop != NO_SCOPE_SHARING_TODO); JS_ASSERT(*todop != NO_TITLE_SHARING_TODO);
} }
*todop = scope->u.link; *todop = title->u.link;
scope->u.link = NULL; /* null u.link for sanity ASAP */ title->u.link = NULL; /* null u.link for sanity ASAP */
JS_NOTIFY_ALL_CONDVAR(rt->scopeSharingDone); JS_NOTIFY_ALL_CONDVAR(rt->titleSharingDone);
} }
js_InitLock(&scope->lock); js_InitLock(&title->lock);
scope->u.count = 0; title->u.count = 0;
js_FinishSharingScope(cx, scope); 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 * 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 * 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 * their buffers can't be realloc'd any longer in js_ConcatStrings, and their
* members can't be modified by js_ConcatStrings, js_MinimizeDependentStrings, * members can't be modified by js_ConcatStrings, js_MinimizeDependentStrings,
* or js_UndependString. * or js_UndependString.
* *
* The last bit of work done by js_FinishSharingScope nulls scope->ownercx and * The last bit of work done by js_FinishSharingTitle nulls title->ownercx and
* updates rt->sharedScopes. * updates rt->sharedTitles.
*/ */
void void
js_FinishSharingScope(JSContext *cx, JSScope *scope) js_FinishSharingTitle(JSContext *cx, JSTitle *title)
{ {
JSObjectMap *map;
JSScope *scope;
JSObject *obj; JSObject *obj;
uint32 nslots, i; uint32 nslots, i;
jsval v; jsval v;
map = TITLE_TO_MAP(title);
if (!MAP_IS_NATIVE(map))
return;
scope = (JSScope *)map;
obj = scope->object; obj = scope->object;
nslots = LOCKED_OBJ_NSLOTS(obj); nslots = LOCKED_OBJ_NSLOTS(obj);
for (i = 0; i != nslots; ++i) { 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 */ title->ownercx = NULL; /* NB: set last, after lock init */
JS_RUNTIME_METER(cx->runtime, sharedScopes); JS_RUNTIME_METER(cx->runtime, sharedTitles);
} }
/* /*
* Given a scope with apparently non-null ownercx different from cx, try to * Given a title with apparently non-null ownercx different from cx, try to
* set ownercx to cx, claiming exclusive (single-threaded) ownership of scope. * set ownercx to cx, claiming exclusive (single-threaded) ownership of title.
* If we claim ownership, return true. Otherwise, we wait for ownercx to be * 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 * set to null (indicating that title is multi-threaded); or if waiting would
* deadlock, we set ownercx to null ourselves via ShareScope. In any case, * deadlock, we set ownercx to null ourselves via ShareTitle. In any case,
* once ownercx is null we return false. * once ownercx is null we return false.
*/ */
static JSBool static JSBool
ClaimScope(JSScope *scope, JSContext *cx) ClaimTitle(JSTitle *title, JSContext *cx)
{ {
JSRuntime *rt; JSRuntime *rt;
JSContext *ownercx; JSContext *ownercx;
@ -421,69 +428,69 @@ ClaimScope(JSScope *scope, JSContext *cx)
JS_LOCK_GC(rt); JS_LOCK_GC(rt);
/* Reload in case ownercx went away while we blocked on the lock. */ /* 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 * 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 * 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. * function call.
* *
* If scope->u.link is non-null, scope has already been inserted on * If title->u.link is non-null, title has already been inserted on
* the rt->scopeSharingTodo list, because another thread's context * the rt->titleSharingTodo list, because another thread's context
* already wanted to lock scope while ownercx was running a request. * already wanted to lock title while ownercx was running a request.
* We can't claim any scope whose u.link is non-null at this point, * 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 * 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) || (!js_ValidContextPointer(rt, ownercx) ||
!ownercx->requestDepth || !ownercx->requestDepth ||
ownercx->thread == cx->thread)) { ownercx->thread == cx->thread)) {
JS_ASSERT(scope->u.count == 0); JS_ASSERT(title->u.count == 0);
scope->ownercx = cx; title->ownercx = cx;
JS_UNLOCK_GC(rt); JS_UNLOCK_GC(rt);
JS_RUNTIME_METER(rt, claimedScopes); JS_RUNTIME_METER(rt, claimedTitles);
return JS_TRUE; return JS_TRUE;
} }
/* /*
* Avoid deadlock if scope's owner context is waiting on a scope that * Avoid deadlock if title's owner context is waiting on a title that
* we own, by revoking scope's ownership. This approach to deadlock * we own, by revoking title's ownership. This approach to deadlock
* avoidance works because the engine never nests scope locks. * avoidance works because the engine never nests title locks.
* *
* If cx could hold locks on ownercx->scopeToShare, or if ownercx * If cx could hold locks on ownercx->titleToShare, or if ownercx could
* could hold locks on scope, we would need to keep reentrancy counts * hold locks on title, we would need to keep reentrancy counts for all
* for all such "flyweight" (ownercx != NULL) locks, so that control * such "flyweight" (ownercx != NULL) locks, so that control would
* would unwind properly once these locks became "thin" or "fat". * unwind properly once these locks became "thin" or "fat". The engine
* The engine promotes a scope from exclusive to shared access only * promotes a title from exclusive to shared access only when locking,
* when locking, never when holding or unlocking. * 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 * 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. * a finalizer) that can't be claimed must become shared.
*/ */
if (rt->gcThread == cx->thread || if (rt->gcThread == cx->thread ||
(ownercx->scopeToShare && (ownercx->titleToShare &&
WillDeadlock(ownercx->scopeToShare, cx))) { WillDeadlock(ownercx->titleToShare, cx))) {
ShareScope(cx, scope); ShareTitle(cx, title);
break; break;
} }
/* /*
* Thanks to the non-zero NO_SCOPE_SHARING_TODO link terminator, we * Thanks to the non-zero NO_TITLE_SHARING_TODO link terminator, we
* can decide whether scope is on rt->scopeSharingTodo with a single * can decide whether title is on rt->titleSharingTodo with a single
* non-null test, and avoid double-insertion bugs. * non-null test, and avoid double-insertion bugs.
*/ */
if (!scope->u.link) { if (!title->u.link) {
scope->u.link = rt->scopeSharingTodo; title->u.link = rt->titleSharingTodo;
rt->scopeSharingTodo = scope; rt->titleSharingTodo = title;
js_HoldObjectMap(cx, &scope->map); 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 * saving and clearing cx->requestDepth so we don't deadlock if the
* GC needs to run on ownercx. * 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 * We know that some other thread's context owns title, which is now
* linked onto rt->scopeSharingTodo, awaiting the end of that other * linked onto rt->titleSharingTodo, awaiting the end of that other
* thread's request. So it is safe to wait on rt->scopeSharingDone. * thread's request. So it is safe to wait on rt->titleSharingDone.
*/ */
cx->scopeToShare = scope; cx->titleToShare = title;
stat = PR_WaitCondVar(rt->scopeSharingDone, PR_INTERVAL_NO_TIMEOUT); stat = PR_WaitCondVar(rt->titleSharingDone, PR_INTERVAL_NO_TIMEOUT);
JS_ASSERT(stat != PR_FAILURE); 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, * restoring cx->requestDepth. Same note as above for the inlined,
* specialized JS_SuspendRequest code: beware rt->gcThread. * 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 * 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). * in the inlined JS_ResumeRequest code immediately above).
* *
* Otherwise, the GC could easily deadlock with another thread that * 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 * 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). * course, have other, live objects sharing it).
*/ */
cx->scopeToShare = NULL; cx->titleToShare = NULL;
} }
JS_UNLOCK_GC(rt); JS_UNLOCK_GC(rt);
@ -551,6 +558,7 @@ js_GetSlotThreadSafe(JSContext *cx, JSObject *obj, uint32 slot)
{ {
jsval v; jsval v;
JSScope *scope; JSScope *scope;
JSTitle *title;
#ifndef NSPR_LOCK #ifndef NSPR_LOCK
JSThinLock *tl; JSThinLock *tl;
jsword me; jsword me;
@ -574,7 +582,8 @@ js_GetSlotThreadSafe(JSContext *cx, JSObject *obj, uint32 slot)
* and contention-free multi-threaded cases. * and contention-free multi-threaded cases.
*/ */
scope = OBJ_SCOPE(obj); scope = OBJ_SCOPE(obj);
JS_ASSERT(scope->ownercx != cx); title = &scope->title;
JS_ASSERT(title->ownercx != cx);
JS_ASSERT(slot < obj->map->freeslot); 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) || if (CX_THREAD_IS_RUNNING_GC(cx) ||
(SCOPE_IS_SEALED(scope) && scope->object == obj) || (SCOPE_IS_SEALED(scope) && scope->object == obj) ||
(scope->ownercx && ClaimScope(scope, cx))) { (title->ownercx && ClaimTitle(title, cx))) {
return STOBJ_GET_SLOT(obj, slot); return STOBJ_GET_SLOT(obj, slot);
} }
#ifndef NSPR_LOCK #ifndef NSPR_LOCK
tl = &scope->lock; tl = &title->lock;
me = CX_THINLOCK_ID(cx); me = CX_THINLOCK_ID(cx);
JS_ASSERT(CURRENT_THREAD_IS_ME(me)); JS_ASSERT(CURRENT_THREAD_IS_ME(me));
if (js_CompareAndSwap(&tl->owner, 0, 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); v = STOBJ_GET_SLOT(obj, slot);
if (!js_CompareAndSwap(&tl->owner, me, 0)) { if (!js_CompareAndSwap(&tl->owner, me, 0)) {
/* Assert that scope locks never revert to flyweight. */ /* Assert that scope locks never revert to flyweight. */
JS_ASSERT(scope->ownercx != cx); JS_ASSERT(title->ownercx != cx);
LOGIT(scope, '1'); LOGIT(scope, '1');
scope->u.count = 1; title->u.count = 1;
js_UnlockObj(cx, obj); js_UnlockObj(cx, obj);
} }
return v; 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 * object's scope (whose lock was not flyweight, else we wouldn't be here
* in the first place!). * in the first place!).
*/ */
scope = OBJ_SCOPE(obj); title = &OBJ_SCOPE(obj)->title;
if (scope->ownercx != cx) if (title->ownercx != cx)
js_UnlockScope(cx, scope); js_UnlockTitle(cx, title);
return v; return v;
} }
void void
js_SetSlotThreadSafe(JSContext *cx, JSObject *obj, uint32 slot, jsval v) js_SetSlotThreadSafe(JSContext *cx, JSObject *obj, uint32 slot, jsval v)
{ {
JSTitle *title;
JSScope *scope; JSScope *scope;
#ifndef NSPR_LOCK #ifndef NSPR_LOCK
JSThinLock *tl; JSThinLock *tl;
@ -667,7 +677,8 @@ js_SetSlotThreadSafe(JSContext *cx, JSObject *obj, uint32 slot, jsval v)
* and contention-free multi-threaded cases. * and contention-free multi-threaded cases.
*/ */
scope = OBJ_SCOPE(obj); scope = OBJ_SCOPE(obj);
JS_ASSERT(scope->ownercx != cx); title = &scope->title;
JS_ASSERT(title->ownercx != cx);
JS_ASSERT(slot < obj->map->freeslot); 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) || if (CX_THREAD_IS_RUNNING_GC(cx) ||
(SCOPE_IS_SEALED(scope) && scope->object == obj) || (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); LOCKED_OBJ_WRITE_BARRIER(cx, obj, slot, v);
return; return;
} }
#ifndef NSPR_LOCK #ifndef NSPR_LOCK
tl = &scope->lock; tl = &title->lock;
me = CX_THINLOCK_ID(cx); me = CX_THINLOCK_ID(cx);
JS_ASSERT(CURRENT_THREAD_IS_ME(me)); JS_ASSERT(CURRENT_THREAD_IS_ME(me));
if (js_CompareAndSwap(&tl->owner, 0, 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); LOCKED_OBJ_WRITE_BARRIER(cx, obj, slot, v);
if (!js_CompareAndSwap(&tl->owner, me, 0)) { if (!js_CompareAndSwap(&tl->owner, me, 0)) {
/* Assert that scope locks never revert to flyweight. */ /* Assert that scope locks never revert to flyweight. */
JS_ASSERT(scope->ownercx != cx); JS_ASSERT(title->ownercx != cx);
LOGIT(scope, '1'); LOGIT(scope, '1');
scope->u.count = 1; title->u.count = 1;
js_UnlockObj(cx, obj); js_UnlockObj(cx, obj);
} }
return; return;
@ -714,9 +725,9 @@ js_SetSlotThreadSafe(JSContext *cx, JSObject *obj, uint32 slot, jsval v)
/* /*
* Same drill as above, in js_GetSlotThreadSafe. * Same drill as above, in js_GetSlotThreadSafe.
*/ */
scope = OBJ_SCOPE(obj); title = &OBJ_SCOPE(obj)->title;
if (scope->ownercx != cx) if (title->ownercx != cx)
js_UnlockScope(cx, scope); js_UnlockTitle(cx, title);
} }
#ifndef NSPR_LOCK #ifndef NSPR_LOCK
@ -1059,50 +1070,50 @@ js_UnlockRuntime(JSRuntime *rt)
} }
void void
js_LockScope(JSContext *cx, JSScope *scope) js_LockTitle(JSContext *cx, JSTitle *title)
{ {
jsword me = CX_THINLOCK_ID(cx); jsword me = CX_THINLOCK_ID(cx);
JS_ASSERT(CURRENT_THREAD_IS_ME(me)); JS_ASSERT(CURRENT_THREAD_IS_ME(me));
JS_ASSERT(scope->ownercx != cx); JS_ASSERT(title->ownercx != cx);
if (CX_THREAD_IS_RUNNING_GC(cx)) if (CX_THREAD_IS_RUNNING_GC(cx))
return; return;
if (scope->ownercx && ClaimScope(scope, cx)) if (title->ownercx && ClaimTitle(title, cx))
return; return;
if (Thin_RemoveWait(ReadWord(scope->lock.owner)) == me) { if (Thin_RemoveWait(ReadWord(title->lock.owner)) == me) {
JS_ASSERT(scope->u.count > 0); JS_ASSERT(title->u.count > 0);
LOGIT(scope, '+'); LOGIT(scope, '+');
scope->u.count++; title->u.count++;
} else { } else {
JSThinLock *tl = &scope->lock; JSThinLock *tl = &title->lock;
JS_LOCK0(tl, me); JS_LOCK0(tl, me);
JS_ASSERT(scope->u.count == 0); JS_ASSERT(title->u.count == 0);
LOGIT(scope, '1'); LOGIT(scope, '1');
scope->u.count = 1; title->u.count = 1;
} }
} }
void void
js_UnlockScope(JSContext *cx, JSScope *scope) js_UnlockTitle(JSContext *cx, JSTitle *title)
{ {
jsword me = CX_THINLOCK_ID(cx); jsword me = CX_THINLOCK_ID(cx);
/* We hope compilers use me instead of reloading cx->thread in the macro. */ /* We hope compilers use me instead of reloading cx->thread in the macro. */
if (CX_THREAD_IS_RUNNING_GC(cx)) if (CX_THREAD_IS_RUNNING_GC(cx))
return; return;
if (cx->lockedSealedScope == scope) { if (cx->lockedSealedTitle == title) {
cx->lockedSealedScope = NULL; cx->lockedSealedTitle = NULL;
return; return;
} }
/* /*
* If scope->ownercx is not null, it's likely that two contexts not using * If title->ownercx is not null, it's likely that two contexts not using
* requests nested locks for scope. The first context, cx here, claimed * requests nested locks for title. The first context, cx here, claimed
* scope; the second, scope->ownercx here, re-claimed it because the first * 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 * 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. * 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. * for cx and return.
* *
* See http://bugzilla.mozilla.org/show_bug.cgi?id=229200 for a real world * 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 * to be the only thread that runs the GC) combined with multiple contexts
* per thread has led to such request-less nesting. * per thread has led to such request-less nesting.
*/ */
if (scope->ownercx) { if (title->ownercx) {
JS_ASSERT(scope->u.count == 0); JS_ASSERT(title->u.count == 0);
JS_ASSERT(scope->lock.owner == 0); JS_ASSERT(title->lock.owner == 0);
scope->ownercx = cx; title->ownercx = cx;
return; return;
} }
JS_ASSERT(scope->u.count > 0); JS_ASSERT(title->u.count > 0);
if (Thin_RemoveWait(ReadWord(scope->lock.owner)) != me) { if (Thin_RemoveWait(ReadWord(title->lock.owner)) != me) {
JS_ASSERT(0); /* unbalanced unlock */ JS_ASSERT(0); /* unbalanced unlock */
return; return;
} }
LOGIT(scope, '-'); LOGIT(scope, '-');
if (--scope->u.count == 0) { if (--title->u.count == 0) {
JSThinLock *tl = &scope->lock; JSThinLock *tl = &title->lock;
JS_UNLOCK0(tl, me); JS_UNLOCK0(tl, me);
} }
} }
/* /*
* NB: oldscope may be null if our caller is js_GetMutableScope and it just * NB: oldtitle may be null if our caller is js_GetMutableScope and it just
* dropped the last reference to oldscope. * dropped the last reference to oldtitle.
*/ */
void void
js_TransferScopeLock(JSContext *cx, JSScope *oldscope, JSScope *newscope) js_TransferTitle(JSContext *cx, JSTitle *oldtitle, JSTitle *newtitle)
{ {
jsword me; jsword me;
JSThinLock *tl; 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. * state update.
*/ */
if (!oldscope) if (!oldtitle)
return; 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, * 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. * was actually locked.
*/ */
if (CX_THREAD_IS_RUNNING_GC(cx)) if (CX_THREAD_IS_RUNNING_GC(cx))
return; 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 * scope of an object that owns that scope (the prototype or mutated obj
* for which OBJ_SCOPE(obj)->object == obj), and unlocking it. * for which OBJ_SCOPE(obj)->object == obj), and unlocking it.
*/ */
JS_ASSERT(cx->lockedSealedScope != newscope); JS_ASSERT(cx->lockedSealedTitle != newtitle);
if (cx->lockedSealedScope == oldscope) { if (cx->lockedSealedTitle == oldtitle) {
JS_ASSERT(newscope->ownercx == cx || JS_ASSERT(newtitle->ownercx == cx ||
(!newscope->ownercx && newscope->u.count == 1)); (!newtitle->ownercx && newtitle->u.count == 1));
cx->lockedSealedScope = NULL; cx->lockedSealedTitle = NULL;
return; return;
} }
/* /*
* If oldscope is single-threaded, there's nothing to do. * If oldtitle is single-threaded, there's nothing to do.
*/ */
if (oldscope->ownercx) { if (oldtitle->ownercx) {
JS_ASSERT(oldscope->ownercx == cx); JS_ASSERT(oldtitle->ownercx == cx);
JS_ASSERT(newscope->ownercx == cx || JS_ASSERT(newtitle->ownercx == cx ||
(!newscope->ownercx && newscope->u.count == 1)); (!newtitle->ownercx && newtitle->u.count == 1));
return; return;
} }
/* /*
* We transfer oldscope->u.count only if newscope is not single-threaded. * We transfer oldtitle->u.count only if newtitle is not single-threaded.
* Flow unwinds from here through some number of JS_UNLOCK_SCOPE and/or * Flow unwinds from here through some number of JS_UNLOCK_TITLE and/or
* JS_UNLOCK_OBJ macro calls, which will decrement newscope->u.count only * JS_UNLOCK_OBJ macro calls, which will decrement newtitle->u.count only
* if they find newscope->ownercx != cx. * if they find newtitle->ownercx != cx.
*/ */
if (newscope->ownercx != cx) { if (newtitle->ownercx != cx) {
JS_ASSERT(!newscope->ownercx); JS_ASSERT(!newtitle->ownercx);
newscope->u.count = oldscope->u.count; 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'); LOGIT(oldscope, '0');
oldscope->u.count = 0; oldtitle->u.count = 0;
tl = &oldscope->lock; tl = &oldtitle->lock;
me = CX_THINLOCK_ID(cx); me = CX_THINLOCK_ID(cx);
JS_UNLOCK0(tl, me); JS_UNLOCK0(tl, me);
} }
@ -1206,33 +1217,35 @@ void
js_LockObj(JSContext *cx, JSObject *obj) js_LockObj(JSContext *cx, JSObject *obj)
{ {
JSScope *scope; JSScope *scope;
JSTitle *title;
JS_ASSERT(OBJ_IS_NATIVE(obj)); JS_ASSERT(OBJ_IS_NATIVE(obj));
/* /*
* We must test whether the GC is calling and return without mutating any * We must test whether the GC is calling and return without mutating any
* state, especially cx->lockedSealedScope. Note asymmetry with respect to * 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)) if (CX_THREAD_IS_RUNNING_GC(cx))
return; return;
for (;;) { for (;;) {
scope = OBJ_SCOPE(obj); scope = OBJ_SCOPE(obj);
title = &scope->title;
if (SCOPE_IS_SEALED(scope) && scope->object == obj && if (SCOPE_IS_SEALED(scope) && scope->object == obj &&
!cx->lockedSealedScope) { !cx->lockedSealedTitle) {
cx->lockedSealedScope = scope; cx->lockedSealedTitle = title;
return; return;
} }
js_LockScope(cx, scope); js_LockTitle(cx, title);
/* If obj still has this scope, we're done. */ /* If obj still has this scope, we're done. */
if (scope == OBJ_SCOPE(obj)) if (scope == OBJ_SCOPE(obj))
return; return;
/* Lost a race with a mutator; retry with obj's new scope. */ /* 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_UnlockObj(JSContext *cx, JSObject *obj)
{ {
JS_ASSERT(OBJ_IS_NATIVE(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 #ifdef DEBUG
@ -1256,30 +1300,30 @@ js_IsObjLocked(JSContext *cx, JSObject *obj)
{ {
JSScope *scope = OBJ_SCOPE(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 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)) if (CX_THREAD_IS_RUNNING_GC(cx))
return JS_TRUE; return JS_TRUE;
/* Special case: locked object owning a sealed scope, see js_LockObj. */ /* Special case: locked object owning a sealed scope, see js_LockObj. */
if (cx->lockedSealedScope == scope) if (cx->lockedSealedTitle == title)
return JS_TRUE; 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. * a thin or fat lock to cope with shared (concurrent) ownership.
*/ */
if (scope->ownercx) { if (title->ownercx) {
JS_ASSERT(scope->ownercx == cx || scope->ownercx->thread == cx->thread); JS_ASSERT(title->ownercx == cx || title->ownercx->thread == cx->thread);
return JS_TRUE; return JS_TRUE;
} }
return js_CurrentThreadId() == return js_CurrentThreadId() ==
((JSThread *)Thin_RemoveWait(ReadWord(scope->lock.owner)))->id; ((JSThread *)Thin_RemoveWait(ReadWord(title->lock.owner)))->id;
} }
#endif /* DEBUG */ #endif /* DEBUG */

View File

@ -81,6 +81,31 @@ typedef struct JSFatLockTable {
JSFatLock *taken; JSFatLock *taken;
} JSFatLockTable; } 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. * Atomic increment and decrement for a reference counter, given jsrefcount *p.
* NB: jsrefcount is int32, aka PRInt32, so that pratom.h functions work. * NB: jsrefcount is int32, aka PRInt32, so that pratom.h functions work.
@ -122,17 +147,23 @@ JS_END_EXTERN_C
#include "jsscope.h" #include "jsscope.h"
JS_BEGIN_EXTERN_C JS_BEGIN_EXTERN_C
#ifdef JS_DEBUG_SCOPE_LOCKS #ifdef JS_DEBUG_TITLE_LOCKS
#define SET_OBJ_INFO(obj_,file_,line_) \ #define SET_OBJ_INFO(obj_, file_, line_) \
SET_SCOPE_INFO(OBJ_SCOPE(obj_),file_,line_) SET_SCOPE_INFO(OBJ_SCOPE(obj_), file_, line_)
#define SET_SCOPE_INFO(scope_,file_,line_) \ #define SET_SCOPE_INFO(scope_,file_,line_) \
((scope_)->ownercx ? (void)0 : \ do { \
(JS_ASSERT((0 < (scope_)->u.count && (scope_)->u.count <= 4) || \ JSTitle *title = &(scope_)->title; \
SCOPE_IS_SEALED(scope_)), \ jsrefcount count; \
(void)((scope_)->file[(scope_)->u.count-1] = (file_), \ if (title->ownercx) \
(scope_)->line[(scope_)->u.count-1] = (line_)))) 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 #endif
@ -146,52 +177,60 @@ JS_BEGIN_EXTERN_C
* are for optimizations above the JSObjectOps layer, under which object locks * are for optimizations above the JSObjectOps layer, under which object locks
* normally hide. * normally hide.
*/ */
#define JS_LOCK_OBJ(cx,obj) ((OBJ_SCOPE(obj)->ownercx == (cx)) \ #define JS_LOCK_OBJ(cx,obj) ((OBJ_SCOPE(obj)->title.ownercx == (cx)) \
? (void)0 \ ? (void)0 \
: (js_LockObj(cx, obj), \ : (js_LockObj(cx, obj), \
SET_OBJ_INFO(obj,__FILE__,__LINE__))) 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)) ? (void)0 : js_UnlockObj(cx, obj))
#define JS_LOCK_SCOPE(cx,scope) ((scope)->ownercx == (cx) ? (void)0 \ #define JS_LOCK_TITLE(cx,title) \
: (js_LockScope(cx, scope), \ ((title)->ownercx == (cx) ? (void)0 \
SET_SCOPE_INFO(scope,__FILE__,__LINE__))) : (js_LockTitle(cx, (title)), \
#define JS_UNLOCK_SCOPE(cx,scope) ((scope)->ownercx == (cx) ? (void)0 \ SET_TITLE_INFO(title,__FILE__,__LINE__)))
: js_UnlockScope(cx, scope))
#define JS_TRANSFER_SCOPE_LOCK(cx, scope, newscope) \ #define JS_UNLOCK_TITLE(cx,title) ((title)->ownercx == (cx) ? (void)0 \
js_TransferScopeLock(cx, scope, newscope) : 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_LockRuntime(JSRuntime *rt);
extern void js_UnlockRuntime(JSRuntime *rt); extern void js_UnlockRuntime(JSRuntime *rt);
extern void js_LockObj(JSContext *cx, JSObject *obj); extern void js_LockObj(JSContext *cx, JSObject *obj);
extern void js_UnlockObj(JSContext *cx, JSObject *obj); extern void js_UnlockObj(JSContext *cx, JSObject *obj);
extern void js_LockScope(JSContext *cx, JSScope *scope); extern void js_InitTitle(JSContext *cx, JSTitle *title);
extern void js_UnlockScope(JSContext *cx, JSScope *scope); 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 int js_SetupLocks(int,int);
extern void js_CleanupLocks(); extern void js_CleanupLocks();
extern void js_TransferScopeLock(JSContext *, JSScope *, JSScope *); extern void js_TransferTitle(JSContext *, JSTitle *, JSTitle *);
extern JS_FRIEND_API(jsval) extern JS_FRIEND_API(jsval)
js_GetSlotThreadSafe(JSContext *, JSObject *, uint32); js_GetSlotThreadSafe(JSContext *, JSObject *, uint32);
extern void js_SetSlotThreadSafe(JSContext *, JSObject *, uint32, jsval); extern void js_SetSlotThreadSafe(JSContext *, JSObject *, uint32, jsval);
extern void js_InitLock(JSThinLock *); extern void js_InitLock(JSThinLock *);
extern void js_FinishLock(JSThinLock *); extern void js_FinishLock(JSThinLock *);
extern void js_FinishSharingScope(JSContext *cx, JSScope *scope); extern void js_FinishSharingTitle(JSContext *cx, JSTitle *title);
#ifdef DEBUG #ifdef DEBUG
#define JS_IS_RUNTIME_LOCKED(rt) js_IsRuntimeLocked(rt) #define JS_IS_RUNTIME_LOCKED(rt) js_IsRuntimeLocked(rt)
#define JS_IS_OBJ_LOCKED(cx,obj) js_IsObjLocked(cx,obj) #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_IsRuntimeLocked(JSRuntime *rt);
extern JSBool js_IsObjLocked(JSContext *cx, JSObject *obj); 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 #else
#define JS_IS_RUNTIME_LOCKED(rt) 0 #define JS_IS_RUNTIME_LOCKED(rt) 0
#define JS_IS_OBJ_LOCKED(cx,obj) 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
#endif /* DEBUG */ #endif /* DEBUG */
@ -264,7 +303,7 @@ JS_BEGIN_EXTERN_C
#define JS_IS_RUNTIME_LOCKED(rt) 1 #define JS_IS_RUNTIME_LOCKED(rt) 1
#define JS_IS_OBJ_LOCKED(cx,obj) 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) #define JS_LOCK_VOID(cx, e) JS_LOCK_RUNTIME_VOID((cx)->runtime, e)
#endif /* !JS_THREADSAFE */ #endif /* !JS_THREADSAFE */
@ -291,8 +330,8 @@ JS_BEGIN_EXTERN_C
#ifndef SET_OBJ_INFO #ifndef SET_OBJ_INFO
#define SET_OBJ_INFO(obj,f,l) ((void)0) #define SET_OBJ_INFO(obj,f,l) ((void)0)
#endif #endif
#ifndef SET_SCOPE_INFO #ifndef SET_TITLE_INFO
#define SET_SCOPE_INFO(scope,f,l) ((void)0) #define SET_TITLE_INFO(title,f,l) ((void)0)
#endif #endif
JS_END_EXTERN_C JS_END_EXTERN_C

View File

@ -230,14 +230,14 @@ struct JSObject {
/* Thread-safe functions and wrapper macros for accessing slots in obj. */ /* Thread-safe functions and wrapper macros for accessing slots in obj. */
#define OBJ_GET_SLOT(cx,obj,slot) \ #define OBJ_GET_SLOT(cx,obj,slot) \
(OBJ_CHECK_SLOT(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) \ ? LOCKED_OBJ_GET_SLOT(obj, slot) \
: js_GetSlotThreadSafe(cx, obj, slot)) : js_GetSlotThreadSafe(cx, obj, slot))
#define OBJ_SET_SLOT(cx,obj,slot,value) \ #define OBJ_SET_SLOT(cx,obj,slot,value) \
JS_BEGIN_MACRO \ JS_BEGIN_MACRO \
OBJ_CHECK_SLOT(obj, slot); \ 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); \ LOCKED_OBJ_WRITE_BARRIER(cx, obj, slot, value); \
else \ else \
js_SetSlotThreadSafe(cx, obj, slot, value); \ js_SetSlotThreadSafe(cx, obj, slot, value); \

View File

@ -153,21 +153,8 @@ js_NewScope(JSContext *cx, jsrefcount nrefs, JSObjectOps *ops, JSClass *clasp,
InitMinimalScope(scope); InitMinimalScope(scope);
#ifdef JS_THREADSAFE #ifdef JS_THREADSAFE
scope->ownercx = cx; js_InitTitle(cx, &scope->title);
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;
#endif #endif
#endif
JS_RUNTIME_METER(cx->runtime, liveScopes); JS_RUNTIME_METER(cx->runtime, liveScopes);
JS_RUNTIME_METER(cx->runtime, totalScopes); JS_RUNTIME_METER(cx->runtime, totalScopes);
return scope; return scope;
@ -193,10 +180,7 @@ js_DestroyScope(JSContext *cx, JSScope *scope)
#endif #endif
#ifdef JS_THREADSAFE #ifdef JS_THREADSAFE
/* Scope must be single-threaded at this point, so set scope->ownercx. */ js_FinishTitle(cx, &scope->title);
JS_ASSERT(scope->u.count == 0);
scope->ownercx = cx;
js_FinishLock(&scope->lock);
#endif #endif
if (scope->table) if (scope->table)
JS_free(cx, scope->table); JS_free(cx, scope->table);

View File

@ -197,6 +197,9 @@ JS_BEGIN_EXTERN_C
struct JSScope { struct JSScope {
JSObjectMap map; /* base class state */ JSObjectMap map; /* base class state */
#ifdef JS_THREADSAFE
JSTitle title; /* lock state */
#endif
JSObject *object; /* object that owns this scope */ JSObject *object; /* object that owns this scope */
uint32 shape; /* property cache shape identifier */ uint32 shape; /* property cache shape identifier */
uint8 flags; /* flags, see below */ uint8 flags; /* flags, see below */
@ -206,20 +209,14 @@ struct JSScope {
uint32 removedCount; /* removed entry sentinels in table */ uint32 removedCount; /* removed entry sentinels in table */
JSScopeProperty **table; /* table of ptrs to shared tree nodes */ JSScopeProperty **table; /* table of ptrs to shared tree nodes */
JSScopeProperty *lastProp; /* pointer to last property added */ 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 OBJ_SCOPE(obj) ((JSScope *)(obj)->map)
#define SCOPE_GENERATE_PCTYPE(cx,scope) ((scope)->shape = js_GenerateShape(cx)) #define SCOPE_GENERATE_PCTYPE(cx,scope) ((scope)->shape = js_GenerateShape(cx))