зеркало из https://github.com/mozilla/gecko-dev.git
Bug 416675: refactor JSScope locking for reuse on non-native objects. r+a=brendan.
This commit is contained in:
Родитель
7200901cae
Коммит
d168ad9a94
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
358
js/src/jslock.c
358
js/src/jslock.c
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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); \
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче