bug 477627 - avoiding deadlocks in ClaimTitle. r=brendan

This commit is contained in:
Igor Bukanov 2009-04-16 00:01:24 +02:00
parent 30ebeb6d1d
commit 907592f765
7 changed files with 295 additions and 221 deletions

View File

@ -947,8 +947,6 @@ JS_EndRequest(JSContext *cx)
{
#ifdef JS_THREADSAFE
JSRuntime *rt;
JSTitle *title, **todop;
JSBool shared;
CHECK_REQUEST(cx);
JS_ASSERT(CURRENT_THREAD_IS_ME(cx->thread));
@ -961,34 +959,7 @@ JS_EndRequest(JSContext *cx)
cx->requestDepth = 0;
cx->outstandingRequests--;
/* 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 = 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.
* The waiting thread(s) must have been killed, after which the GC
* collected the object that held this scope. Unlikely, because it
* requires that the GC ran (e.g., from an operation callback)
* during this request, but possible.
*/
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 (shared)
JS_NOTIFY_ALL_CONDVAR(rt->titleSharingDone);
js_ShareWaitingTitles(cx);
js_RevokeGCLocalFreeLists(cx);
/* Give the GC a chance to run if this was the last request running. */

View File

@ -148,6 +148,7 @@ DestroyThread(JSThread *thread)
{
/* The thread must have zero contexts. */
JS_ASSERT(JS_CLIST_IS_EMPTY(&thread->contextList));
JS_ASSERT(!thread->titleToShare);
FinishThreadData(&thread->data);
free(thread);
}
@ -801,6 +802,79 @@ js_NextActiveContext(JSRuntime *rt, JSContext *cx)
#endif
}
#ifdef JS_THREADSAFE
uint32
js_CountThreadRequests(JSContext *cx)
{
JSCList *head, *link;
uint32 nrequests;
JS_ASSERT(CURRENT_THREAD_IS_ME(cx->thread));
head = &cx->thread->contextList;
nrequests = 0;
for (link = head->next; link != head; link = link->next) {
JSContext *acx = CX_FROM_THREAD_LINKS(link);
JS_ASSERT(acx->thread == cx->thread);
if (acx->requestDepth)
nrequests++;
}
return nrequests;
}
/*
* If the GC is running and we're called on another thread, wait for this GC
* activation to finish. We can safely wait here without fear of deadlock (in
* the case where we are called within a request on another thread's context)
* because the GC doesn't set rt->gcRunning until after it has waited for all
* active requests to end.
*
* We call here js_CurrentThreadId() after checking for rt->gcRunning to avoid
* expensive calls when the GC is not running.
*/
void
js_WaitForGC(JSRuntime *rt)
{
JS_ASSERT_IF(rt->gcRunning, rt->gcLevel > 0);
if (rt->gcRunning && rt->gcThread->id != js_CurrentThreadId()) {
do {
JS_AWAIT_GC_DONE(rt);
} while (rt->gcRunning);
}
}
uint32
js_DiscountRequestsForGC(JSContext *cx)
{
uint32 requestDebit;
JS_ASSERT(cx->thread);
JS_ASSERT(cx->runtime->gcThread != cx->thread);
requestDebit = js_CountThreadRequests(cx);
if (requestDebit != 0) {
JSRuntime *rt = cx->runtime;
JS_ASSERT(requestDebit <= rt->requestCount);
rt->requestCount -= requestDebit;
if (rt->requestCount == 0)
JS_NOTIFY_REQUEST_DONE(rt);
}
return requestDebit;
}
void
js_RecountRequestsAfterGC(JSRuntime *rt, uint32 requestDebit)
{
while (rt->gcLevel > 0) {
JS_ASSERT(rt->gcThread);
JS_AWAIT_GC_DONE(rt);
}
if (requestDebit != 0)
rt->requestCount += requestDebit;
}
#endif
static JSDHashNumber
resolving_HashKey(JSDHashTable *table, const void *ptr)
{

View File

@ -262,6 +262,9 @@ struct JSThread {
*/
uint32 gcMallocBytes;
/* Indicates that the thread is waiting in ClaimTitle from jslock.cpp. */
JSTitle *titleToShare;
JSThreadData data;
};
@ -957,7 +960,6 @@ struct JSContext {
jsrefcount requestDepth;
/* Same as requestDepth but ignoring JS_SuspendRequest/JS_ResumeRequest */
jsrefcount outstandingRequests;
JSTitle *titleToShare; /* weak reference, see jslock.c */
JSTitle *lockedSealedTitle; /* weak ref, for low-cost sealed
title locking */
JSCList threadLinks; /* JSThread contextList linkage */
@ -1209,6 +1211,47 @@ js_ContextIterator(JSRuntime *rt, JSBool unlocked, JSContext **iterp);
extern JS_FRIEND_API(JSContext *)
js_NextActiveContext(JSRuntime *, JSContext *);
#ifdef JS_THREADSAFE
/*
* Count the number of contexts entered requests on the current thread.
*/
uint32
js_CountThreadRequests(JSContext *cx);
/*
* This is a helper for code at can potentially run outside JS request to
* ensure that the GC is not running when the function returns.
*
* This function must be called with the GC lock held.
*/
extern void
js_WaitForGC(JSRuntime *rt);
/*
* If we're in one or more requests (possibly on more than one context)
* running on the current thread, indicate, temporarily, that all these
* requests are inactive so a possible GC can proceed on another thread.
* This function returns the number of discounted requests. The number must
* be passed later to js_ActivateRequestAfterGC to reactivate the requests.
*
* This function must be called with the GC lock held.
*/
uint32
js_DiscountRequestsForGC(JSContext *cx);
/*
* This function must be called with the GC lock held.
*/
void
js_RecountRequestsAfterGC(JSRuntime *rt, uint32 requestDebit);
#else /* !JS_THREADSAFE */
# define js_WaitForGC(rt) ((void) 0)
#endif
/*
* JSClass.resolve and watchpoint recursion damping machinery.
*/

View File

@ -3360,57 +3360,25 @@ js_GC(JSContext *cx, JSGCInvocationKind gckind)
rt->gcPoke = JS_FALSE;
#ifdef JS_THREADSAFE
JS_ASSERT(cx->thread->id == js_CurrentThreadId());
/* Bump gcLevel and return rather than nest on this thread. */
if (rt->gcThread == cx->thread) {
JS_ASSERT(rt->gcLevel > 0);
rt->gcLevel++;
METER_UPDATE_MAX(rt->gcStats.maxlevel, rt->gcLevel);
if (!(gckind & GC_LOCK_HELD))
JS_UNLOCK_GC(rt);
return;
}
/*
* If we're in one or more requests (possibly on more than one context)
* running on the current thread, indicate, temporarily, that all these
* requests are inactive.
* Check if the GC is already running on this or another thread and
* delegate the job to it.
*/
requestDebit = 0;
{
JSCList *head, *link;
/*
* Check all contexts on cx->thread->contextList for active requests,
* counting each such context against requestDebit.
*/
head = &cx->thread->contextList;
for (link = head->next; link != head; link = link->next) {
acx = CX_FROM_THREAD_LINKS(link);
JS_ASSERT(acx->thread == cx->thread);
if (acx->requestDepth)
requestDebit++;
}
}
if (requestDebit) {
JS_ASSERT(requestDebit <= rt->requestCount);
rt->requestCount -= requestDebit;
if (rt->requestCount == 0)
JS_NOTIFY_REQUEST_DONE(rt);
}
/* If another thread is already in GC, don't attempt GC; wait instead. */
if (rt->gcLevel > 0) {
JS_ASSERT(rt->gcThread);
/* Bump gcLevel to restart the current GC, so it finds new garbage. */
rt->gcLevel++;
METER_UPDATE_MAX(rt->gcStats.maxlevel, rt->gcLevel);
/* Wait for the other thread to finish, then resume our request. */
while (rt->gcLevel > 0)
JS_AWAIT_GC_DONE(rt);
if (requestDebit)
rt->requestCount += requestDebit;
/*
* If the GC runs on another thread, temporarily suspend the current
* request and wait until the GC is done.
*/
if (rt->gcThread != cx->thread) {
requestDebit = js_DiscountRequestsForGC(cx);
js_RecountRequestsAfterGC(rt, requestDebit);
}
if (!(gckind & GC_LOCK_HELD))
JS_UNLOCK_GC(rt);
return;
@ -3428,9 +3396,18 @@ js_GC(JSContext *cx, JSGCInvocationKind gckind)
*/
js_NudgeOtherContexts(cx);
/* Wait for all other requests to finish. */
/*
* Discount all the requests on the current thread from contributing
* to rt->requestCount before we wait for all other requests to finish.
* JS_NOTIFY_REQUEST_DONE, which will wake us up, is only called on
* rt->requestCount transitions to 0.
*/
requestDebit = js_CountThreadRequests(cx);
JS_ASSERT_IF(cx->requestDepth != 0, requestDebit >= 1);
rt->requestCount -= requestDebit;
while (rt->requestCount > 0)
JS_AWAIT_REQUEST_DONE(rt);
rt->requestCount += requestDebit;
#else /* !JS_THREADSAFE */
@ -3478,7 +3455,6 @@ js_GC(JSContext *cx, JSGCInvocationKind gckind)
rt->gcRunning = JS_FALSE;
#ifdef JS_THREADSAFE
rt->gcThread = NULL;
rt->requestCount += requestDebit;
#endif
gckind = GC_LOCK_HELD;
goto restart_at_beginning;
@ -3810,9 +3786,6 @@ out:
rt->gcRunning = JS_FALSE;
#ifdef JS_THREADSAFE
/* If we were invoked during a request, pay back the temporary debit. */
if (requestDebit)
rt->requestCount += requestDebit;
rt->gcThread = NULL;
JS_NOTIFY_GC_DONE(rt);
@ -3860,31 +3833,6 @@ out:
}
}
#ifdef JS_THREADSAFE
/*
* If the GC is running and we're called on another thread, wait for this GC
* activation to finish. We can safely wait here without fear of deadlock (in
* the case where we are called within a request on another thread's context)
* because the GC doesn't set rt->gcRunning until after it has waited for all
* active requests to end.
*
* We call here js_CurrentThreadId() after checking for rt->gcRunning to avoid
* expensive calls when the GC is not running.
*/
void
js_WaitForGC(JSRuntime *rt)
{
JS_ASSERT_IF(rt->gcRunning, rt->gcLevel > 0);
if (rt->gcRunning && rt->gcThread->id != js_CurrentThreadId()) {
do {
JS_AWAIT_GC_DONE(rt);
} while (rt->gcRunning);
}
}
#endif
void
js_UpdateMallocCounter(JSContext *cx, size_t nbytes)
{

View File

@ -284,20 +284,6 @@ typedef enum JSGCInvocationKind {
extern void
js_GC(JSContext *cx, JSGCInvocationKind gckind);
/*
* This function must be called with the GC lock held. It is a helper for code
* that can potentially run outside JS request to ensure that the GC is not
* running when the function returns.
*/
#ifdef JS_THREADSAFE
extern void
js_WaitForGC(JSRuntime *rt);
#else
# define js_WaitForGC(rt) ((void) 0)
#endif
/* Call this after succesful malloc of memory for GC-related things. */
extern void
js_UpdateMallocCounter(JSContext *cx, size_t nbytes);

View File

@ -362,28 +362,46 @@ js_unlog_title(JSTitle *title)
#endif /* DEBUG_SCOPE_COUNT */
/*
* Return true if scope's ownercx, or the ownercx of a single-threaded scope
* for which ownercx is waiting to become multi-threaded and shared, is cx.
* That condition implies deadlock in ClaimScope if cx's thread were to wait
* to share scope.
* Return true if we would deadlock waiting in ClaimTitle on
* rt->titleSharingDone until ownercx finishes its request and shares a title.
*
* (i) rt->gcLock held
*/
static JSBool
WillDeadlock(JSTitle *title, JSContext *cx)
static bool
WillDeadlock(JSContext *ownercx, JSThread *thread)
{
JSContext *ownercx;
JS_ASSERT(CURRENT_THREAD_IS_ME(thread));
JS_ASSERT(ownercx->thread != thread);
do {
ownercx = title->ownercx;
if (ownercx == cx) {
JS_RUNTIME_METER(cx->runtime, deadlocksAvoided);
return JS_TRUE;
for (;;) {
JS_ASSERT(ownercx->thread);
JS_ASSERT(ownercx->requestDepth > 0);
JSTitle *title = ownercx->thread->titleToShare;
if (!title || !title->ownercx) {
/*
* ownercx->thread doesn't wait or has just been notified that the
* title became shared.
*/
return false;
}
} while (ownercx && (title = ownercx->titleToShare) != NULL);
return JS_FALSE;
/*
* ownercx->thread is waiting in ClaimTitle for a context from some
* thread to finish its request. If that thread is the current thread,
* we would deadlock. Otherwise we must recursively check if that
* thread waits for the current thread.
*/
if (title->ownercx->thread == thread) {
JS_RUNTIME_METER(ownercx->runtime, deadlocksAvoided);
return true;
}
ownercx = title->ownercx;
}
}
static void
FinishSharingTitle(JSContext *cx, JSTitle *title);
/*
* 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
@ -409,26 +427,23 @@ ShareTitle(JSContext *cx, JSTitle *title)
title->u.link = NULL; /* null u.link for sanity ASAP */
JS_NOTIFY_ALL_CONDVAR(rt->titleSharingDone);
}
js_InitLock(&title->lock);
title->u.count = 0;
js_FinishSharingTitle(cx, title);
FinishSharingTitle(cx, title);
}
/*
* 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 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.
* FinishSharingTitle is the tail part of ShareTitle, split out to become a
* subroutine of js_ShareWaitingTitles too. The bulk of the work here involves
* making 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_UndependString or
* js_MinimizeDependentStrings.
*
* The last bit of work done by js_FinishSharingTitle nulls title->ownercx and
* updates rt->sharedTitles.
* The last bit of work done by this function nulls title->ownercx and updates
* rt->sharedTitles.
*/
void
js_FinishSharingTitle(JSContext *cx, JSTitle *title)
static void
FinishSharingTitle(JSContext *cx, JSTitle *title)
{
JSObjectMap *map;
JSScope *scope;
@ -436,6 +451,8 @@ js_FinishSharingTitle(JSContext *cx, JSTitle *title)
uint32 nslots, i;
jsval v;
js_InitLock(&title->lock);
title->u.count = 0; /* NULL may not pun as 0 */
map = TITLE_TO_MAP(title);
if (!MAP_IS_NATIVE(map))
return;
@ -483,14 +500,17 @@ js_NudgeOtherContexts(JSContext *cx)
* Notify all contexts that are currently in a request and execute on this
* specific thread.
*/
void
js_NudgeThread(JSContext *cx, JSThread *thread)
static void
NudgeThread(JSThread *thread)
{
JSRuntime *rt = cx->runtime;
JSContext *acx = NULL;
while ((acx = js_NextActiveContext(rt, acx)) != NULL) {
if (cx != acx && acx->thread == thread)
JSCList *link;
JSContext *acx;
link = &thread->contextList;
while ((link = link->next) != &thread->contextList) {
acx = CX_FROM_THREAD_LINKS(link);
JS_ASSERT(acx->thread == thread);
if (acx->requestDepth)
JS_TriggerOperationCallback(acx);
}
}
@ -508,8 +528,7 @@ ClaimTitle(JSTitle *title, JSContext *cx)
{
JSRuntime *rt;
JSContext *ownercx;
jsrefcount saveDepth;
PRStatus stat;
uint32 requestDebit;
rt = cx->runtime;
JS_RUNTIME_METER(rt, claimAttempts);
@ -527,15 +546,30 @@ ClaimTitle(JSTitle *title, JSContext *cx)
* 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->titleSharingDone).
* That context must still be in request and cannot be dead. We can
* claim it if its thread matches ours but only if cx itself is in a
* request.
*
* The latter check covers the case when the embedding triggers a call
* to js_GC on a cx outside a request while having ownercx running a
* request on the same thread, and then js_GC calls a mark hook or a
* finalizer accessing the title. In this case we cannot claim the
* title but must share it now as no title-sharing JS_EndRequest will
* follow.
*/
if (!title->u.link &&
(!js_ValidContextPointer(rt, ownercx) ||
!ownercx->requestDepth ||
ownercx->thread == cx->thread)) {
JS_ASSERT(title->u.count == 0);
bool canClaim;
if (title->u.link) {
JS_ASSERT(js_ValidContextPointer(rt, ownercx));
JS_ASSERT(ownercx->requestDepth > 0);
JS_ASSERT_IF(cx->requestDepth == 0, cx->thread == rt->gcThread);
canClaim = (ownercx->thread == cx->thread &&
cx->requestDepth > 0);
} else {
canClaim = (!js_ValidContextPointer(rt, ownercx) ||
!ownercx->requestDepth ||
ownercx->thread == cx->thread);
}
if (canClaim) {
title->ownercx = cx;
JS_UNLOCK_GC(rt);
JS_RUNTIME_METER(rt, claimedTitles);
@ -543,25 +577,25 @@ ClaimTitle(JSTitle *title, JSContext *cx)
}
/*
* 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.
* Avoid deadlock if title's owner thread is waiting on a title that
* the current thread owns, 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->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.
* If cx->thread could hold locks on ownercx->thread->titleToShare, or
* if ownercx->thread 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 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 title wanted by the GC (from
* a finalizer) that can't be claimed must become shared.
* a finalizer or a mark hook) that can't be claimed must become
* shared.
*/
if (rt->gcThread == cx->thread ||
(ownercx->titleToShare &&
WillDeadlock(ownercx->titleToShare, cx))) {
if (rt->gcThread == cx->thread || WillDeadlock(ownercx, cx->thread)) {
ShareTitle(cx, title);
break;
}
@ -578,56 +612,36 @@ ClaimTitle(JSTitle *title, JSContext *cx)
}
/*
* 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.
*
* Unlike JS_SuspendRequest and JS_EndRequest, we must take care not
* to decrement rt->requestCount if cx is active on the GC's thread,
* because the GC has already reduced rt->requestCount to exclude all
* such such contexts.
* Discount all the requests running on the current thread so a
* possible GC can proceed on another thread while we wait on
* rt->titleSharingDone.
*/
saveDepth = cx->requestDepth;
if (saveDepth) {
cx->requestDepth = 0;
if (rt->gcThread != cx->thread) {
JS_ASSERT(rt->requestCount > 0);
rt->requestCount--;
if (rt->requestCount == 0)
JS_NOTIFY_REQUEST_DONE(rt);
}
}
js_NudgeThread(cx, ownercx->thread);
requestDebit = js_DiscountRequestsForGC(cx);
/*
* 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.
* thread's request. So it is safe to wait on rt->titleSharingDone.
* But before waiting, we force the operation callback for that other
* thread so it can quickly suspend.
*/
cx->titleToShare = title;
stat = PR_WaitCondVar(rt->titleSharingDone, PR_INTERVAL_NO_TIMEOUT);
NudgeThread(ownercx->thread);
JS_ASSERT(!cx->thread->titleToShare);
cx->thread->titleToShare = title;
#ifdef DEBUG
PRStatus stat =
#endif
PR_WaitCondVar(rt->titleSharingDone, PR_INTERVAL_NO_TIMEOUT);
JS_ASSERT(stat != PR_FAILURE);
/*
* 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.
*/
if (saveDepth) {
if (rt->gcThread != cx->thread) {
while (rt->gcLevel > 0)
JS_AWAIT_GC_DONE(rt);
rt->requestCount++;
}
cx->requestDepth = saveDepth;
}
js_RecountRequestsAfterGC(rt, requestDebit);
/*
* Don't clear cx->titleToShare until after we're through waiting on
* Don't clear titleToShare until after we're through waiting on
* all condition variables protected by rt->gcLock -- that includes
* rt->titleSharingDone *and* rt->gcDone (hidden in JS_AWAIT_GC_DONE,
* in the inlined JS_ResumeRequest code immediately above).
* rt->titleSharingDone *and* rt->gcDone (hidden in the call to
* js_ActivateRequestAfterGC immediately above).
*
* Otherwise, the GC could easily deadlock with another thread that
* owns a title wanted by a finalizer. By keeping cx->titleToShare
@ -635,13 +649,46 @@ ClaimTitle(JSTitle *title, JSContext *cx)
* results in the finalized object's title being shared (it must, of
* course, have other, live objects sharing it).
*/
cx->titleToShare = NULL;
cx->thread->titleToShare = NULL;
}
JS_UNLOCK_GC(rt);
return JS_FALSE;
}
void
js_ShareWaitingTitles(JSContext *cx)
{
JSTitle *title, **todop;
bool shared;
/* See whether cx has any single-threaded titles to start sharing. */
todop = &cx->runtime->titleSharingTodo;
shared = false;
while ((title = *todop) != NO_TITLE_SHARING_TODO) {
if (title->ownercx != cx) {
todop = &title->u.link;
continue;
}
*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.
* The waiting thread(s) must have been killed, after which the GC
* collected the object that held this scope. Unlikely, because it
* requires that the GC ran (e.g., from an operation callback)
* during this request, but possible.
*/
if (js_DropObjectMap(cx, TITLE_TO_MAP(title), NULL)) {
FinishSharingTitle(cx, title); /* set ownercx = NULL */
shared = true;
}
}
if (shared)
JS_NOTIFY_ALL_CONDVAR(cx->runtime->titleSharingDone);
}
/* Exported to js.c, which calls it via OBJ_GET_* and JSVAL_IS_* macros. */
JS_FRIEND_API(jsval)
js_GetSlotThreadSafe(JSContext *cx, JSObject *obj, uint32 slot)

View File

@ -197,10 +197,15 @@ 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_FinishSharingTitle(JSContext *cx, JSTitle *title);
extern void js_NudgeOtherContexts(JSContext *cx);
extern void js_NudgeThread(JSContext *cx, JSThread *thread);
/*
* This function must be called with the GC lock held.
*/
extern void
js_ShareWaitingTitles(JSContext *cx);
extern void
js_NudgeOtherContexts(JSContext *cx);
#ifdef DEBUG