mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
bug 477627 - avoiding deadlocks in ClaimTitle. r=brendan
This commit is contained in:
parent
30ebeb6d1d
commit
907592f765
@ -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. */
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user