Substitute operation counting with a watchdog thread (477187, r=brendan/mrbkap/jst, sr=brendan/jst).

This commit is contained in:
Andreas Gal 2009-02-09 18:20:50 -08:00
parent 6ee38d80d3
commit 86e19efcc6
27 changed files with 605 additions and 642 deletions

View File

@ -854,9 +854,6 @@ PrintWinCodebase(nsGlobalWindow *win)
}
#endif
// The accumulated operation weight before we call MaybeGC
const PRUint32 MAYBE_GC_OPERATION_WEIGHT = 5000 * JS_OPERATION_WEIGHT_BASE;
static void
MaybeGC(JSContext *cx)
{
@ -927,7 +924,7 @@ nsJSContext::DOMOperationCallback(JSContext *cx)
nsJSContext::CC();
// never prevent system scripts from running
if (! ::JS_IsSystemObject(cx, ::JS_GetGlobalObject(cx))) {
if (!::JS_IsSystemObject(cx, ::JS_GetGlobalObject(cx))) {
// lets see if CC() did anything, if not, cancel the script.
mem->IsLowMemory(&lowMemory);
@ -988,7 +985,7 @@ nsJSContext::DOMOperationCallback(JSContext *cx)
// Check if we should offer the option to debug
JSStackFrame* fp = ::JS_GetScriptedCaller(cx, NULL);
PRBool debugPossible = (fp != nsnull &&
PRBool debugPossible = (fp != nsnull && cx->debugHooks &&
cx->debugHooks->debuggerHandler != nsnull);
#ifdef MOZ_JSDEBUGGER
// Get the debugger service if necessary.
@ -1097,11 +1094,16 @@ nsJSContext::DOMOperationCallback(JSContext *cx)
if (debugPossible)
buttonFlags += nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_2;
// Null out the operation callback while we're re-entering JS here.
::JS_SetOperationCallback(cx, nsnull);
// Open the dialog.
rv = prompt->ConfirmEx(title, msg, buttonFlags, stopButton, waitButton,
debugButton, neverShowDlg, &neverShowDlgChk,
&buttonPressed);
::JS_SetOperationCallback(cx, DOMOperationCallback);
if (NS_FAILED(rv) || (buttonPressed == 1)) {
// Allow the script to continue running
@ -1249,8 +1251,7 @@ nsJSContext::nsJSContext(JSRuntime *aRuntime) : mGCOnDestruction(PR_TRUE)
JSOptionChangedCallback,
this);
::JS_SetOperationCallback(mContext, DOMOperationCallback,
MAYBE_GC_OPERATION_WEIGHT);
::JS_SetOperationCallback(mContext, DOMOperationCallback);
static JSLocaleCallbacks localeCallbacks =
{
@ -1302,9 +1303,6 @@ nsJSContext::Unlink()
// Clear our entry in the JSContext, bugzilla bug 66413
::JS_SetContextPrivate(mContext, nsnull);
// Clear the operation callback, bugzilla bug 238218
::JS_ClearOperationCallback(mContext);
// Unregister our "javascript.options.*" pref-changed callback.
nsContentUtils::UnregisterPrefCallback(js_options_dot_str,
JSOptionChangedCallback,

View File

@ -105,10 +105,6 @@ PR_STATIC_ASSERT(THREADPOOL_MAX_THREADS >= THREADPOOL_IDLE_THREADS);
PR_STATIC_ASSERT(THREADPOOL_THREAD_CAP >= THREADPOOL_MAX_THREADS);
// The number of times our JS operation callback will be called before yielding
// the thread
#define CALLBACK_YIELD_THRESHOLD 100
// A "bad" value for the NSPR TLS functions.
#define BAD_TLS_INDEX (PRUintn)-1
@ -326,7 +322,7 @@ public:
// Tell the worker which context it will be using
if (mWorker->SetGlobalForContext(cx)) {
RunQueue();
RunQueue(cx);
// Remove the global object from the context so that it might be garbage
// collected.
@ -348,9 +344,8 @@ public:
protected:
void RunQueue() {
JSContext* cx = (JSContext*)PR_GetThreadPrivate(gJSContextIndex);
NS_ASSERTION(cx, "nsDOMThreadService didn't give us a context!");
void RunQueue(JSContext* aCx) {
PRBool operationCallbackTriggered = PR_FALSE;
while (1) {
nsCOMPtr<nsIRunnable> runnable;
@ -372,8 +367,17 @@ protected:
}
}
if (!operationCallbackTriggered) {
// Make sure that our operation callback is set to run before starting.
// That way we are sure to suspend this worker if needed.
JS_TriggerOperationCallback(aCx);
// Only need to do this the first time.
operationCallbackTriggered = PR_TRUE;
}
// Clear out any old cruft hanging around in the regexp statics.
JS_ClearRegExpStatics(cx);
JS_ClearRegExpStatics(aCx);
runnable->Run();
}
@ -403,7 +407,7 @@ DOMWorkerOperationCallback(JSContext* aCx)
PRBool extraThreadAllowed = PR_FALSE;
jsrefcount suspendDepth = 0;
while (1) {
do {
// Kill execution if we're canceled.
if (worker->IsCanceled()) {
LOG(("Forcefully killing JS for worker [0x%p]",
@ -417,7 +421,7 @@ DOMWorkerOperationCallback(JSContext* aCx)
}
// Kill exectuion of the currently running JS.
return PR_FALSE;
return JS_FALSE;
}
// Break out if we're not suspended.
@ -428,7 +432,7 @@ DOMWorkerOperationCallback(JSContext* aCx)
}
JS_ResumeRequest(aCx, suspendDepth);
}
break;
return JS_TRUE;
}
if (!wasSuspended) {
@ -436,7 +440,7 @@ DOMWorkerOperationCallback(JSContext* aCx)
// the worker was canceled since we checked above.
if (worker->IsCanceled()) {
NS_WARNING("Tried to suspend on a pool that has gone away");
return PR_FALSE;
return JS_FALSE;
}
pool = worker->Pool();
@ -457,20 +461,10 @@ DOMWorkerOperationCallback(JSContext* aCx)
nsAutoMonitor mon(pool->Monitor());
mon.Wait();
}
} while (1);
// Since only one thread can access a context at once we don't have to worry
// about atomically incrementing this counter
if (++worker->mCallbackCount >= CALLBACK_YIELD_THRESHOLD) {
// Must call this so that GC can happen on the main thread!
JS_YieldRequest(aCx);
// Start the counter over.
worker->mCallbackCount = 0;
}
// Continue execution.
return JS_TRUE;
NS_NOTREACHED("Shouldn't get here!");
return JS_FALSE;
}
void
@ -633,6 +627,9 @@ nsDOMThreadService::Init()
success = mPools.Init();
NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
success = mJSContexts.SetCapacity(THREADPOOL_THREAD_CAP);
NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
nsCOMPtr<nsIJSRuntimeService>
runtimeSvc(do_GetService("@mozilla.org/js/xpc/RuntimeService;1"));
NS_ENSURE_TRUE(runtimeSvc, NS_ERROR_FAILURE);
@ -838,8 +835,7 @@ nsDOMThreadService::CreateJSContext()
JS_SetErrorReporter(cx, DOMWorkerErrorReporter);
JS_SetOperationCallback(cx, DOMWorkerOperationCallback,
100 * JS_OPERATION_WEIGHT_BASE);
JS_SetOperationCallback(cx, DOMWorkerOperationCallback);
static JSSecurityCallbacks securityCallbacks = {
nsDOMWorkerSecurityManager::JSCheckAccess,
@ -872,6 +868,8 @@ nsDOMThreadService::CreateJSContext()
JS_SetThreadStackLimit(cx, stackLimit);
JS_SetScriptStackQuota(cx, 100*1024*1024);
JS_SetOptions(cx, JS_GetOptions(cx) | JSOPTION_JIT | JSOPTION_ANONFUNFIX);
return cx.forget();
}
@ -893,6 +891,23 @@ nsDOMThreadService::GetPoolForGlobal(nsIScriptGlobalObject* aGlobalObject,
return pool.forget();
}
void
nsDOMThreadService::TriggerOperationCallbackForPool(nsDOMWorkerPool* aPool)
{
nsAutoMonitor mon(mMonitor);
// See if we need to trigger the operation callback on any currently running
// contexts.
PRUint32 contextCount = mJSContexts.Length();
for (PRUint32 index = 0; index < contextCount; index++) {
JSContext*& cx = mJSContexts[index];
nsDOMWorker* worker = (nsDOMWorker*)JS_GetContextPrivate(cx);
if (worker && worker->Pool() == aPool) {
JS_TriggerOperationCallback(cx);
}
}
}
void
nsDOMThreadService::CancelWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject)
{
@ -901,6 +916,7 @@ nsDOMThreadService::CancelWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject)
nsRefPtr<nsDOMWorkerPool> pool = GetPoolForGlobal(aGlobalObject, PR_TRUE);
if (pool) {
pool->Cancel();
TriggerOperationCallbackForPool(pool);
}
}
@ -912,6 +928,7 @@ nsDOMThreadService::SuspendWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject
nsRefPtr<nsDOMWorkerPool> pool = GetPoolForGlobal(aGlobalObject, PR_FALSE);
if (pool) {
pool->Suspend();
TriggerOperationCallbackForPool(pool);
}
}
@ -923,6 +940,7 @@ nsDOMThreadService::ResumeWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject)
nsRefPtr<nsDOMWorkerPool> pool = GetPoolForGlobal(aGlobalObject, PR_FALSE);
if (pool) {
pool->Resume();
TriggerOperationCallbackForPool(pool);
}
}
@ -968,6 +986,17 @@ nsDOMThreadService::ChangeThreadPoolMaxThreads(PRInt16 aDelta)
rv = mThreadPool->SetThreadLimit((PRUint32)newThreadCount);
NS_ENSURE_SUCCESS(rv, rv);
// If we're allowing an extra thread then post a dummy event to the thread
// pool so that any pending workers can get started. The thread pool doesn't
// do this on its own like it probably should...
if (aDelta == 1) {
nsCOMPtr<nsIRunnable> dummy(new nsRunnable());
if (dummy) {
rv = mThreadPool->Dispatch(dummy, NS_DISPATCH_NORMAL);
NS_ENSURE_SUCCESS(rv, rv);
}
}
return NS_OK;
}
@ -1068,6 +1097,16 @@ nsDOMThreadService::OnThreadCreated()
nsContentUtils::XPConnect()->ReleaseJSContext(cx, PR_TRUE);
return NS_ERROR_FAILURE;
}
nsAutoMonitor mon(mMonitor);
#ifdef DEBUG
JSContext** newContext =
#endif
mJSContexts.AppendElement(cx);
// We ensure the capacity of this array in Init.
NS_ASSERTION(newContext, "Should never fail!");
}
// Make sure that XPConnect knows about this context.
@ -1087,6 +1126,11 @@ nsDOMThreadService::OnThreadShuttingDown()
JSContext* cx = (JSContext*)PR_GetThreadPrivate(gJSContextIndex);
NS_WARN_IF_FALSE(cx, "Thread died with no context?");
if (cx) {
{
nsAutoMonitor mon(mMonitor);
mJSContexts.RemoveElement(cx);
}
JSContext* pushedCx;
gThreadJSContextStack->Pop(&pushedCx);
NS_ASSERTION(pushedCx == cx, "Popped the wrong context!");

View File

@ -134,6 +134,8 @@ private:
GetPoolForGlobal(nsIScriptGlobalObject* aGlobalObject,
PRBool aRemove);
void TriggerOperationCallbackForPool(nsDOMWorkerPool* aPool);
void NoteEmptyPool(nsDOMWorkerPool* aPool);
void TimeoutReady(nsDOMWorkerTimeout* aTimeout);
@ -159,6 +161,10 @@ private:
// A map from nsDOMWorkerThread to nsDOMWorkerRunnable.
nsRefPtrHashtable<nsVoidPtrHashKey, nsDOMWorkerRunnable> mWorkersInProgress;
// A list of active JSContexts that we've created. Always protected with
// mMonitor.
nsTArray<JSContext*> mJSContexts;
nsString mAppName;
nsString mAppVersion;
nsString mPlatform;

View File

@ -951,7 +951,6 @@ nsDOMWorker::nsDOMWorker(nsDOMWorker* aParent,
nsIXPConnectWrappedNative* aParentWN)
: mParent(aParent),
mParentWN(aParentWN),
mCallbackCount(0),
mLock(nsnull),
mInnerScope(nsnull),
mGlobal(NULL),

View File

@ -217,8 +217,6 @@ private:
nsDOMWorker* mParent;
nsCOMPtr<nsIXPConnectWrappedNative> mParentWN;
PRUint32 mCallbackCount;
PRLock* mLock;
nsRefPtr<nsDOMWorkerMessageHandler> mInnerHandler;

View File

@ -164,21 +164,22 @@ nsDOMWorkerPool::Cancel()
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(!mCanceled, "Canceled more than once!");
nsAutoTArray<nsDOMWorker*, 10> workers;
{
nsAutoMonitor mon(mMonitor);
mCanceled = PR_TRUE;
nsAutoTArray<nsDOMWorker*, 10> workers;
GetWorkers(workers);
}
PRUint32 count = workers.Length();
if (count) {
for (PRUint32 index = 0; index < count; index++) {
workers[index]->Cancel();
}
mon.NotifyAll();
PRUint32 count = workers.Length();
if (count) {
for (PRUint32 index = 0; index < count; index++) {
workers[index]->Cancel();
}
nsAutoMonitor mon(mMonitor);
mon.NotifyAll();
}
mParentGlobal = nsnull;

View File

@ -1870,7 +1870,6 @@ JS_malloc(JSContext *cx, size_t nbytes)
void *p;
JS_ASSERT(nbytes != 0);
JS_COUNT_OPERATION(cx, JSOW_ALLOCATION);
if (nbytes == 0)
nbytes = 1;
@ -1887,7 +1886,6 @@ JS_malloc(JSContext *cx, size_t nbytes)
JS_PUBLIC_API(void *)
JS_realloc(JSContext *cx, void *p, size_t nbytes)
{
JS_COUNT_OPERATION(cx, JSOW_ALLOCATION);
p = realloc(p, nbytes);
if (!p)
JS_ReportOutOfMemory(cx);
@ -5316,94 +5314,33 @@ JS_CallFunctionValue(JSContext *cx, JSObject *obj, jsval fval, uintN argc,
return ok;
}
JS_PUBLIC_API(void)
JS_SetOperationCallback(JSContext *cx, JSOperationCallback callback,
uint32 operationLimit)
JS_PUBLIC_API(JSOperationCallback)
JS_SetOperationCallback(JSContext *cx, JSOperationCallback callback)
{
JS_SetOperationCallbackFunction(cx, callback);
JS_SetOperationLimit(cx, operationLimit);
}
JS_PUBLIC_API(void)
JS_ClearOperationCallback(JSContext *cx)
{
JS_SetOperationCallbackFunction(cx, NULL);
JS_SetOperationLimit(cx, JS_MAX_OPERATION_LIMIT);
}
JS_PUBLIC_API(void)
JS_SetOperationLimit(JSContext *cx, uint32 operationLimit)
{
/* Mixed operation and branch callbacks are not supported. */
JS_ASSERT(!cx->branchCallbackWasSet);
JS_ASSERT(operationLimit <= JS_MAX_OPERATION_LIMIT);
JS_ASSERT(operationLimit > 0);
cx->operationCount = (int32) operationLimit;
cx->operationLimit = operationLimit;
}
JS_PUBLIC_API(uint32)
JS_GetOperationLimit(JSContext *cx)
{
JS_ASSERT(!cx->branchCallbackWasSet);
/*
* cx->operationLimit is initialized to JS_MAX_OPERATION_LIMIT + 1 to
* detect for optimizations if the embedding has ever set it.
*/
JS_ASSERT(cx->operationLimit <= JS_MAX_OPERATION_LIMIT + 1);
return JS_MIN(cx->operationLimit, JS_MAX_OPERATION_LIMIT);
}
JS_PUBLIC_API(JSBranchCallback)
JS_SetBranchCallback(JSContext *cx, JSBranchCallback cb)
{
JSBranchCallback oldcb;
if (!cx->branchCallbackWasSet) {
#ifdef DEBUG
if (cx->operationCallback) {
fprintf(stderr,
"JS API usage error: call to JS_SetOperationCallback is followed by\n"
"invocation of deprecated JS_SetBranchCallback\n");
JS_ASSERT(0);
}
#endif
cx->branchCallbackWasSet = 1;
oldcb = NULL;
} else {
oldcb = (JSBranchCallback) cx->operationCallback;
}
if (cb) {
cx->operationCount = JSOW_SCRIPT_JUMP;
cx->operationLimit = JSOW_SCRIPT_JUMP;
cx->operationCallback = (JSOperationCallback) cb;
} else {
cx->operationCallback = NULL;
}
return oldcb;
}
JS_PUBLIC_API(void)
JS_SetOperationCallbackFunction(JSContext *cx, JSOperationCallback callback)
{
/* Mixed operation and branch callbacks are not supported. */
JS_ASSERT(!cx->branchCallbackWasSet);
#ifdef JS_THREADSAFE
JS_ASSERT(CURRENT_THREAD_IS_ME(cx->thread));
#endif
JSOperationCallback old = cx->operationCallback;
cx->operationCallback = callback;
return old;
}
JS_PUBLIC_API(JSOperationCallback)
JS_GetOperationCallback(JSContext *cx)
{
JS_ASSERT(!cx->branchCallbackWasSet);
return cx->operationCallback;
}
JS_PUBLIC_API(void)
JS_TriggerOperationCallback(JSContext *cx)
{
cx->operationCount = 0;
/*
* Use JS_ATOMIC_SET in the hope that it will make sure the write
* will become immediately visible to other processors polling
* cx->operationCallbackFlag. Note that we only care about
* visibility here, not read/write ordering.
*/
JS_ATOMIC_SET(&cx->operationCallbackFlag, 1);
}
JS_PUBLIC_API(JSBool)

View File

@ -629,15 +629,6 @@ JS_StringToVersion(const char *string);
not backward compatible with
the comment-hiding hack used
in HTML script tags. */
#define JSOPTION_NATIVE_BRANCH_CALLBACK \
JS_BIT(7) /* the branch callback set by
JS_SetBranchCallback may be
called with a null script
parameter, by native code
that loops intensively.
Deprecated, use
JS_SetOperationCallback
instead */
#define JSOPTION_DONT_REPORT_UNCAUGHT \
JS_BIT(8) /* When returning from the
outermost API call, prevent
@ -2264,68 +2255,31 @@ JS_CallFunctionValue(JSContext *cx, JSObject *obj, jsval fval, uintN argc,
jsval *argv, jsval *rval);
/*
* The maximum value of the operation limit to pass to JS_SetOperationCallback
* and JS_SetOperationLimit.
* These functions allow setting an operation callback that will be called
* from the thread the context is associated with some time after any thread
* triggered the callback using JS_TriggerOperationCallback(cx).
*
* In a threadsafe build the engine internally triggers operation callbacks
* under certain circumstances (i.e. GC and title transfer) to force the
* context to yield its current request, which the engine always
* automatically does immediately prior to calling the callback function.
* The embedding should thus not rely on callbacks being triggered through
* the external API only.
*
* Important note: Additional callbacks can occur inside the callback handler
* if it re-enters the JS engine. The embedding must ensure that the callback
* is disconnected before attempting such re-entry.
*/
#define JS_MAX_OPERATION_LIMIT ((uint32) 0x7FFFFFFF - (uint32) 1)
#define JS_OPERATION_WEIGHT_BASE 4096
extern JS_PUBLIC_API(void)
JS_SetOperationCallbackFunction(JSContext *cx, JSOperationCallback callback);
extern JS_PUBLIC_API(JSOperationCallback)
JS_SetOperationCallback(JSContext *cx, JSOperationCallback callback);
extern JS_PUBLIC_API(JSOperationCallback)
JS_GetOperationCallback(JSContext *cx);
/*
* Force a call to operation callback at some later moment. The function can be
* called from an arbitrary thread for any context.
*/
extern JS_PUBLIC_API(void)
JS_TriggerOperationCallback(JSContext *cx);
/*
* Set the limit for the internal operation counter. The engine calls the
* operation callback When the limit is reached.
*
* When operationLimit is JS_OPERATION_WEIGHT_BASE, the callback will be
* called at least after each backward jump in the interpreter. To minimize
* the overhead of the callback invocation we suggest at least
*
* 100 * JS_OPERATION_WEIGHT_BASE
*
* as a value for operationLimit.
*/
extern JS_PUBLIC_API(void)
JS_SetOperationLimit(JSContext *cx, uint32 operationLimit);
/*
* Get the operation limit associated with the operation callback. This API
* function may be called only when the result of JS_GetOperationCallback(cx)
* is not null.
*/
extern JS_PUBLIC_API(uint32)
JS_GetOperationLimit(JSContext *cx);
extern JS_PUBLIC_API(void)
JS_SetOperationCallback(JSContext *cx, JSOperationCallback callback,
uint32 operationLimit);
extern JS_PUBLIC_API(void)
JS_ClearOperationCallback(JSContext *cx);
/*
* Note well: JS_SetBranchCallback is deprecated. It is similar to
*
* JS_SetOperationCallback(cx, callback, 4096, NULL);
*
* except that the callback will not be called from a long-running native
* function when JSOPTION_NATIVE_BRANCH_CALLBACK is not set and the top-most
* frame is native.
*/
extern JS_PUBLIC_API(JSBranchCallback)
JS_SetBranchCallback(JSContext *cx, JSBranchCallback cb);
extern JS_PUBLIC_API(JSBool)
JS_IsRunning(JSContext *cx);

View File

@ -587,7 +587,7 @@ array_length_setter(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
} else if (oldlen - newlen < (1 << 24)) {
do {
--oldlen;
if (!JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) ||
if (!JS_CHECK_OPERATION_LIMIT(cx) ||
!DeleteArrayElement(cx, obj, oldlen)) {
return JS_FALSE;
}
@ -608,7 +608,7 @@ array_length_setter(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
JS_PUSH_TEMP_ROOT_OBJECT(cx, iter, &tvr);
gap = oldlen - newlen;
for (;;) {
ok = (JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) &&
ok = (JS_CHECK_OPERATION_LIMIT(cx) &&
JS_NextProperty(cx, iter, &id));
if (!ok)
break;
@ -1326,7 +1326,7 @@ array_join_sub(JSContext *cx, JSObject *obj, enum ArrayToStringOp op,
/* Use rval to locally root each element value as we loop and convert. */
for (index = 0; index < length; index++) {
ok = (JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) &&
ok = (JS_CHECK_OPERATION_LIMIT(cx) &&
GetArrayElement(cx, obj, index, &hole, rval));
if (!ok)
goto done;
@ -1379,7 +1379,6 @@ array_join_sub(JSContext *cx, JSObject *obj, enum ArrayToStringOp op,
goto done;
}
growth *= sizeof(jschar);
JS_COUNT_OPERATION(cx, JSOW_ALLOCATION);
if (!chars) {
chars = (jschar *) malloc(growth);
if (!chars)
@ -1500,7 +1499,7 @@ InitArrayElements(JSContext *cx, JSObject *obj, jsuint start, jsuint end,
}
while (start != end) {
if (!JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) ||
if (!JS_CHECK_OPERATION_LIMIT(cx) ||
!SetArrayElement(cx, obj, start++, *vector++)) {
return JS_FALSE;
}
@ -1598,7 +1597,7 @@ array_reverse(JSContext *cx, uintN argc, jsval *vp)
JS_PUSH_SINGLE_TEMP_ROOT(cx, JSVAL_NULL, &tvr);
half = len / 2;
for (i = 0; i < half; i++) {
ok = JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) &&
ok = JS_CHECK_OPERATION_LIMIT(cx) &&
GetArrayElement(cx, obj, i, &hole, &tvr.u.value) &&
GetArrayElement(cx, obj, len - i - 1, &hole2, vp) &&
SetOrDeleteArrayElement(cx, obj, len - i - 1, hole, tvr.u.value) &&
@ -1783,7 +1782,7 @@ sort_compare(void *arg, const void *a, const void *b, int *result)
JS_ASSERT(!JSVAL_IS_VOID(av));
JS_ASSERT(!JSVAL_IS_VOID(bv));
if (!JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP))
if (!JS_CHECK_OPERATION_LIMIT(cx))
return JS_FALSE;
invokevp = ca->elemroot;
@ -1821,7 +1820,7 @@ sort_compare_strings(void *arg, const void *a, const void *b, int *result)
JS_ASSERT(JSVAL_IS_STRING(av));
JS_ASSERT(JSVAL_IS_STRING(bv));
if (!JS_CHECK_OPERATION_LIMIT((JSContext *)arg, JSOW_JUMP))
if (!JS_CHECK_OPERATION_LIMIT((JSContext *)arg))
return JS_FALSE;
*result = (int) js_CompareStrings(JSVAL_TO_STRING(av), JSVAL_TO_STRING(bv));
@ -1915,7 +1914,7 @@ array_sort(JSContext *cx, uintN argc, jsval *vp)
newlen = 0;
all_strings = JS_TRUE;
for (i = 0; i < len; i++) {
ok = JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP);
ok = JS_CHECK_OPERATION_LIMIT(cx);
if (!ok)
goto out;
@ -2001,7 +2000,7 @@ array_sort(JSContext *cx, uintN argc, jsval *vp)
i = newlen;
do {
--i;
ok = JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP);
ok = JS_CHECK_OPERATION_LIMIT(cx);
if (!ok)
goto out;
v = vec[i];
@ -2080,7 +2079,7 @@ array_sort(JSContext *cx, uintN argc, jsval *vp)
/* Set undefs that sorted after the rest of elements. */
while (undefs != 0) {
--undefs;
if (!JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) ||
if (!JS_CHECK_OPERATION_LIMIT(cx) ||
!SetArrayElement(cx, obj, newlen++, JSVAL_VOID)) {
return JS_FALSE;
}
@ -2088,7 +2087,7 @@ array_sort(JSContext *cx, uintN argc, jsval *vp)
/* Re-create any holes that sorted to the end of the array. */
while (len > newlen) {
if (!JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) ||
if (!JS_CHECK_OPERATION_LIMIT(cx) ||
!DeleteArrayElement(cx, obj, --len)) {
return JS_FALSE;
}
@ -2284,7 +2283,7 @@ array_shift(JSContext *cx, uintN argc, jsval *vp)
ok = JS_TRUE;
JS_PUSH_SINGLE_TEMP_ROOT(cx, JSVAL_NULL, &tvr);
for (i = 0; i != length; i++) {
ok = JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) &&
ok = JS_CHECK_OPERATION_LIMIT(cx) &&
GetArrayElement(cx, obj, i + 1, &hole, &tvr.u.value) &&
SetOrDeleteArrayElement(cx, obj, i, hole, tvr.u.value);
if (!ok)
@ -2322,7 +2321,7 @@ array_unshift(JSContext *cx, uintN argc, jsval *vp)
JS_PUSH_SINGLE_TEMP_ROOT(cx, JSVAL_NULL, &tvr);
do {
--last;
ok = JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) &&
ok = JS_CHECK_OPERATION_LIMIT(cx) &&
GetArrayElement(cx, obj, last, &hole, &tvr.u.value) &&
SetOrDeleteArrayElement(cx, obj, last + argc, hole,
tvr.u.value);
@ -2419,7 +2418,7 @@ array_splice(JSContext *cx, uintN argc, jsval *vp)
/* If there are elements to remove, put them into the return value. */
if (count > 0) {
for (last = begin; last < end; last++) {
ok = JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) &&
ok = JS_CHECK_OPERATION_LIMIT(cx) &&
GetArrayElement(cx, obj, last, &hole, &tvr.u.value);
if (!ok)
goto out;
@ -2443,7 +2442,7 @@ array_splice(JSContext *cx, uintN argc, jsval *vp)
last = length;
/* (uint) end could be 0, so can't use vanilla >= test */
while (last-- > end) {
ok = JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) &&
ok = JS_CHECK_OPERATION_LIMIT(cx) &&
GetArrayElement(cx, obj, last, &hole, &tvr.u.value) &&
SetOrDeleteArrayElement(cx, obj, last + delta, hole,
tvr.u.value);
@ -2454,7 +2453,7 @@ array_splice(JSContext *cx, uintN argc, jsval *vp)
} else if (argc < count) {
delta = count - (jsuint)argc;
for (last = end; last < length; last++) {
ok = JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) &&
ok = JS_CHECK_OPERATION_LIMIT(cx) &&
GetArrayElement(cx, obj, last, &hole, &tvr.u.value) &&
SetOrDeleteArrayElement(cx, obj, last - delta, hole,
tvr.u.value);
@ -2531,7 +2530,7 @@ array_concat(JSContext *cx, uintN argc, jsval *vp)
/* Loop over [0, argc] to concat args into nobj, expanding all Arrays. */
for (i = 0; i <= argc; i++) {
ok = JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP);
ok = JS_CHECK_OPERATION_LIMIT(cx);
if (!ok)
goto out;
v = argv[i];
@ -2552,7 +2551,7 @@ array_concat(JSContext *cx, uintN argc, jsval *vp)
if (!ok)
goto out;
for (slot = 0; slot < alength; slot++) {
ok = JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) &&
ok = JS_CHECK_OPERATION_LIMIT(cx) &&
GetArrayElement(cx, aobj, slot, &hole,
&tvr.u.value);
if (!ok)
@ -2658,7 +2657,7 @@ array_slice(JSContext *cx, uintN argc, jsval *vp)
JS_PUSH_SINGLE_TEMP_ROOT(cx, JSVAL_NULL, &tvr);
for (slot = begin; slot < end; slot++) {
ok = JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) &&
ok = JS_CHECK_OPERATION_LIMIT(cx) &&
GetArrayElement(cx, obj, slot, &hole, &tvr.u.value);
if (!ok)
goto out;
@ -2730,7 +2729,7 @@ array_indexOfHelper(JSContext *cx, JSBool isLast, uintN argc, jsval *vp)
}
for (;;) {
if (!JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) ||
if (!JS_CHECK_OPERATION_LIMIT(cx) ||
!GetArrayElement(cx, obj, (jsuint)i, &hole, vp)) {
return JS_FALSE;
}
@ -2879,7 +2878,7 @@ array_extra(JSContext *cx, ArrayExtraMode mode, uintN argc, jsval *vp)
invokevp = elemroot + 1;
for (i = start; i != end; i += step) {
ok = JS_CHECK_OPERATION_LIMIT(cx, JSOW_JUMP) &&
ok = JS_CHECK_OPERATION_LIMIT(cx) &&
GetArrayElement(cx, obj, i, &hole, elemroot);
if (!ok)
goto out;

View File

@ -79,9 +79,6 @@
static PRUintn threadTPIndex;
static JSBool tpIndexInited = JS_FALSE;
static void
InitOperationLimit(JSContext *cx);
JS_BEGIN_EXTERN_C
JSBool
js_InitThreadPrivateIndex(void (*ptr)(void *))
@ -243,7 +240,6 @@ js_NewContext(JSRuntime *rt, size_t stackChunkSize)
memset(cx, 0, sizeof *cx);
cx->runtime = rt;
js_InitOperationLimit(cx);
cx->debugHooks = &rt->globalDebugHooks;
#if JS_STACK_GROWTH_DIRECTION > 0
cx->stackLimit = (jsuword)-1;
@ -590,6 +586,21 @@ js_ContextIterator(JSRuntime *rt, JSBool unlocked, JSContext **iterp)
return cx;
}
JS_FRIEND_API(JSContext *)
js_NextActiveContext(JSRuntime *rt, JSContext *cx)
{
JSContext *iter = cx;
#ifdef JS_THREADSAFE
while ((cx = js_ContextIterator(rt, JS_FALSE, &iter)) != NULL) {
if (cx->requestDepth)
break;
}
return cx;
#else
return js_ContextIterator(rt, JS_FALSE, &iter);
#endif
}
static JSDHashNumber
resolving_HashKey(JSDHashTable *table, const void *ptr)
{
@ -1413,31 +1424,37 @@ js_GetErrorMessage(void *userRef, const char *locale, const uintN errorNumber)
}
JSBool
js_ResetOperationCount(JSContext *cx)
js_InvokeOperationCallback(JSContext *cx)
{
JSScript *script;
JSStackFrame *fp;
JS_ASSERT(cx->operationCallbackFlag);
/*
* Reset the callback flag first, then yield. If another thread is racing
* us here we will accumulate another callback request which will be
* serviced at the next opportunity.
*/
cx->operationCallbackFlag = 0;
JS_ASSERT(cx->operationCount <= 0);
JS_ASSERT(cx->operationLimit > 0);
/*
* We automatically yield the current context every time the operation
* callback is hit since we might be called as a result of an impending
* GC, which would deadlock if we do not yield. Operation callbacks
* are supposed to happen rarely (seconds, not milliseconds) so it is
* acceptable to yield at every callback.
*/
#ifdef JS_THREADSAFE
JS_YieldRequest(cx);
#endif
cx->operationCount = (int32) cx->operationLimit;
JSOperationCallback cb = cx->operationCallback;
if (cb) {
if (!cx->branchCallbackWasSet)
return cb(cx);
/*
* Invoke the deprecated branch callback. It may be called only when
* the top-most frame is scripted or JSOPTION_NATIVE_BRANCH_CALLBACK
* is set.
*/
fp = js_GetTopStackFrame(cx);
script = fp ? fp->script : NULL;
if (script || JS_HAS_OPTION(cx, JSOPTION_NATIVE_BRANCH_CALLBACK))
return ((JSBranchCallback) cb)(cx, script);
}
return JS_TRUE;
/*
* Important: Additional callbacks can occur inside the callback handler
* if it re-enters the JS engine. The embedding must ensure that the
* callback is disconnected before attempting such re-entry.
*/
return !cb || cb(cx);
}
#ifndef JS_TRACER

View File

@ -837,10 +837,10 @@ JS_STATIC_ASSERT(sizeof(JSTempValueUnion) == sizeof(void *));
struct JSContext {
/*
* Operation count. It is declared as the first field in the struct to
* ensure the fastest possible access.
* If this flag is set, we were asked to call back the operation callback
* as soon as possible.
*/
volatile int32 operationCount;
volatile jsint operationCallbackFlag;
/* JSRuntime contextList linkage. */
JSCList link;
@ -950,12 +950,7 @@ struct JSContext {
/* Per-context optional error reporter. */
JSErrorReporter errorReporter;
/*
* Flag indicating that operationCallback stores the deprecated branch
* callback.
*/
uint32 branchCallbackWasSet : 1;
uint32 operationLimit : 31;
/* Branch callback. */
JSOperationCallback operationCallback;
/* Interpreter activation count. */
@ -1202,6 +1197,14 @@ js_ContextFromLinkField(JSCList *link)
extern JSContext *
js_ContextIterator(JSRuntime *rt, JSBool unlocked, JSContext **iterp);
/*
* Iterate through contexts with active requests. The caller must be holding
* rt->gcLock in case of a thread-safe build, or otherwise guarantee that the
* context list is not alternated asynchroniously.
*/
extern JS_FRIEND_API(JSContext *)
js_NextActiveContext(JSRuntime *, JSContext *);
/*
* JSClass.resolve and watchpoint recursion damping machinery.
*/
@ -1353,72 +1356,19 @@ extern JSErrorFormatString js_ErrorFormatString[JSErr_Limit];
#endif
/*
* Update the operation counter according to the given weight and call the
* operation callback when we reach the operation limit. To make this
* frequently executed macro faster we decrease the counter from
* JSContext.operationLimit and compare against zero to check the limit.
*
* If the operation callback flag was set, call the operation callback.
* This macro can run the full GC. Return true if it is OK to continue and
* false otherwise.
*/
#define JS_CHECK_OPERATION_LIMIT(cx, weight) \
(JS_CHECK_OPERATION_WEIGHT(weight), \
(((cx)->operationCount -= (weight)) > 0 || js_ResetOperationCount(cx)))
#define JS_CHECK_OPERATION_LIMIT(cx) \
(!(cx)->operationCallbackFlag || js_InvokeOperationCallback(cx))
/*
* A version of JS_CHECK_OPERATION_LIMIT that just updates the operation count
* without calling the operation callback or any other API. This macro resets
* the count to 0 when it becomes negative to prevent a wrap-around when the
* macro is called repeatably.
*/
#define JS_COUNT_OPERATION(cx, weight) \
((void)(JS_CHECK_OPERATION_WEIGHT(weight), \
(cx)->operationCount = ((cx)->operationCount > 0) \
? (cx)->operationCount - (weight) \
: 0))
/*
* The implementation of the above macros assumes that subtracting weights
* twice from a positive number does not wrap-around INT32_MIN.
*/
#define JS_CHECK_OPERATION_WEIGHT(weight) \
(JS_ASSERT((uint32) (weight) > 0), \
JS_ASSERT((uint32) (weight) < JS_BIT(30)))
/* Relative operations weights. */
#define JSOW_JUMP 1
#define JSOW_ALLOCATION 100
#define JSOW_LOOKUP_PROPERTY 5
#define JSOW_GET_PROPERTY 10
#define JSOW_SET_PROPERTY 20
#define JSOW_NEW_PROPERTY 200
#define JSOW_DELETE_PROPERTY 30
#define JSOW_ENTER_SHARP JS_OPERATION_WEIGHT_BASE
#define JSOW_SCRIPT_JUMP JS_OPERATION_WEIGHT_BASE
/*
* Reset the operation count and call the operation callback assuming that the
* operation limit is reached.
* Invoke the operation callback and return false if the current execution
* is to be terminated.
*/
extern JSBool
js_ResetOperationCount(JSContext *cx);
static JS_INLINE void
js_InitOperationLimit(JSContext *cx)
{
/*
* Set the limit to 1 + max to detect if JS_SetOperationLimit() was ever
* called.
*/
cx->operationCount = (int32) JS_MAX_OPERATION_LIMIT + 1;
cx->operationLimit = JS_MAX_OPERATION_LIMIT + 1;
}
static JS_INLINE JSBool
js_HasOperationLimit(JSContext *cx)
{
return cx->operationLimit <= JS_MAX_OPERATION_LIMIT;
}
js_InvokeOperationCallback(JSContext *cx);
/*
* Get the current cx->fp, first lazily instantiating stack frames if needed.

View File

@ -2478,3 +2478,21 @@ js_DateGetMsecSinceEpoch(JSContext *cx, JSObject *obj)
return 0;
return utctime;
}
#ifdef JS_THREADSAFE
#include "prinrval.h"
uint32
js_IntervalNow()
{
return uint32(PR_IntervalToMilliseconds(PR_IntervalNow()));
}
#else /* !JS_THREADSAFE */
uint32
js_IntervalNow()
{
return uint32(PRMJ_Now() / PRMJ_USEC_PER_MSEC);
}
#endif

View File

@ -119,6 +119,11 @@ js_DateSetSeconds(JSContext *cx, JSObject *obj, int seconds);
extern JS_FRIEND_API(jsdouble)
js_DateGetMsecSinceEpoch(JSContext *cx, JSObject *obj);
typedef uint32 JSIntervalTime;
JSIntervalTime
js_IntervalNow();
JS_END_EXTERN_C
#endif /* jsdate_h___ */

View File

@ -2025,7 +2025,6 @@ testReservedObjects:
if (gcLocked)
JS_UNLOCK_GC(rt);
#endif
JS_COUNT_OPERATION(cx, JSOW_ALLOCATION);
return thing;
fail:
@ -2173,7 +2172,6 @@ RefillDoubleFreeList(JSContext *cx)
} while (bit != 0);
}
JS_ASSERT(list);
JS_COUNT_OPERATION(cx, JSOW_ALLOCATION * JS_BITS_PER_WORD);
/*
* We delegate assigning cx->doubleFreeList to js_NewDoubleInRootedValue as
@ -3312,7 +3310,7 @@ js_GC(JSContext *cx, JSGCInvocationKind gckind)
#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);
@ -3383,6 +3381,14 @@ js_GC(JSContext *cx, JSGCInvocationKind gckind)
rt->gcLevel = 1;
rt->gcThread = cx->thread;
/*
* Notify all operation callbacks, which will give them a chance to
* yield their current request. Contexts that are not currently
* executing will perform their callback at some later point,
* which then will be unnecessary, but harmless.
*/
js_NudgeOtherContexts(cx);
/* Wait for all other requests to finish. */
while (rt->requestCount > 0)
JS_AWAIT_REQUEST_DONE(rt);

View File

@ -54,6 +54,7 @@
#include "jsatom.h"
#include "jsbool.h"
#include "jscntxt.h"
#include "jsdate.h"
#include "jsversion.h"
#include "jsdbgapi.h"
#include "jsfun.h"
@ -2684,7 +2685,7 @@ js_Interpret(JSContext *cx)
*/
#define CHECK_BRANCH() \
JS_BEGIN_MACRO \
if (!JS_CHECK_OPERATION_LIMIT(cx, JSOW_SCRIPT_JUMP)) \
if (!JS_CHECK_OPERATION_LIMIT(cx)) \
goto error; \
JS_END_MACRO

View File

@ -458,6 +458,38 @@ js_FinishSharingTitle(JSContext *cx, JSTitle *title)
JS_RUNTIME_METER(cx->runtime, sharedTitles);
}
/*
* Notify all contexts that are currently in a request, which will give them a
* chance to yield their current request.
*/
void
js_NudgeOtherContexts(JSContext *cx)
{
JSRuntime *rt = cx->runtime;
JSContext *acx = NULL;
while ((acx = js_NextActiveContext(rt, acx)) != NULL) {
if (cx != acx)
JS_TriggerOperationCallback(acx);
}
}
/*
* Notify all contexts that are currently in a request and execute on this
* specific thread.
*/
void
js_NudgeThread(JSContext *cx, JSThread *thread)
{
JSRuntime *rt = cx->runtime;
JSContext *acx = NULL;
while ((acx = js_NextActiveContext(rt, acx)) != NULL) {
if (cx != acx && cx->thread == thread)
JS_TriggerOperationCallback(acx);
}
}
/*
* Given a title with apparently non-null ownercx different from cx, try to
* set ownercx to cx, claiming exclusive (single-threaded) ownership of title.
@ -561,6 +593,8 @@ ClaimTitle(JSTitle *title, JSContext *cx)
}
}
js_NudgeThread(cx, ownercx->thread);
/*
* We know that some other thread's context owns title, which is now
* linked onto rt->titleSharingTodo, awaiting the end of that other

View File

@ -119,6 +119,7 @@ struct JSTitle {
#define JS_ATOMIC_INCREMENT(p) PR_AtomicIncrement((PRInt32 *)(p))
#define JS_ATOMIC_DECREMENT(p) PR_AtomicDecrement((PRInt32 *)(p))
#define JS_ATOMIC_ADD(p,v) PR_AtomicAdd((PRInt32 *)(p), (PRInt32)(v))
#define JS_ATOMIC_SET(p,v) PR_AtomicSet((PRInt32 *)(p), (PRInt32)(v))
#define js_CurrentThreadId() (jsword)PR_GetCurrentThread()
#define JS_NEW_LOCK() PR_NewLock()
@ -198,6 +199,9 @@ 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);
#ifdef DEBUG
#define JS_IS_RUNTIME_LOCKED(rt) js_IsRuntimeLocked(rt)
@ -237,6 +241,7 @@ extern void js_SetScopeInfo(JSScope *scope, const char *file, int line);
#define JS_ATOMIC_INCREMENT(p) (++*(p))
#define JS_ATOMIC_DECREMENT(p) (--*(p))
#define JS_ATOMIC_ADD(p,v) (*(p) += (v))
#define JS_ATOMIC_SET(p,v) (*(p) = (v))
#define JS_CurrentThreadId() 0
#define JS_NEW_LOCK() NULL

View File

@ -463,7 +463,7 @@ js_EnterSharpObject(JSContext *cx, JSObject *obj, JSIdArray **idap,
char buf[20];
size_t len;
if (!JS_CHECK_OPERATION_LIMIT(cx, JSOW_ENTER_SHARP))
if (!JS_CHECK_OPERATION_LIMIT(cx))
return NULL;
/* Set to null in case we return an early error. */
@ -3574,7 +3574,6 @@ js_LookupPropertyWithFlags(JSContext *cx, JSObject *obj, jsid id, uintN flags,
/* Convert string indices to integers if appropriate. */
CHECK_FOR_STRING_INDEX(id);
JS_COUNT_OPERATION(cx, JSOW_LOOKUP_PROPERTY);
/* Search scopes starting with obj and following the prototype link. */
start = obj;
@ -3963,7 +3962,6 @@ js_GetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, jsval *vp,
JS_ASSERT_IF(entryp, !JS_ON_TRACE(cx));
/* Convert string indices to integers if appropriate. */
CHECK_FOR_STRING_INDEX(id);
JS_COUNT_OPERATION(cx, JSOW_GET_PROPERTY);
shape = OBJ_SHAPE(obj);
protoIndex = js_LookupPropertyWithFlags(cx, obj, id, cx->resolveFlags,
@ -4064,7 +4062,6 @@ js_SetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, jsval *vp,
/* Convert string indices to integers if appropriate. */
CHECK_FOR_STRING_INDEX(id);
JS_COUNT_OPERATION(cx, JSOW_SET_PROPERTY);
/*
* We peek at OBJ_SCOPE(obj) without locking obj. Any race means a failure
@ -4329,7 +4326,6 @@ js_DeleteProperty(JSContext *cx, JSObject *obj, jsid id, jsval *rval)
/* Convert string indices to integers if appropriate. */
CHECK_FOR_STRING_INDEX(id);
JS_COUNT_OPERATION(cx, JSOW_DELETE_PROPERTY);
if (!js_LookupProperty(cx, obj, id, &proto, &prop))
return JS_FALSE;

View File

@ -613,6 +613,11 @@ typedef enum JSContextOp {
typedef JSBool
(* JSContextCallback)(JSContext *cx, uintN contextOp);
#ifndef JS_THREADSAFE
typedef void
(* JSHeartbeatCallback)(JSRuntime *rt);
#endif
typedef enum JSGCStatus {
JSGC_BEGIN,
JSGC_END,

View File

@ -2632,11 +2632,9 @@ PushBackTrackState(REGlobalData *gData, REOp op,
re_debug("\tBT_Push: %lu,%lu",
(unsigned long) parenIndex, (unsigned long) parenCount);
JS_COUNT_OPERATION(gData->cx, JSOW_JUMP * (1 + parenCount));
if (btincr > 0) {
ptrdiff_t offset = (char *)result - (char *)gData->backTrackStack;
JS_COUNT_OPERATION(gData->cx, JSOW_ALLOCATION);
btincr = JS_ROUNDUP(btincr, btsize);
JS_ARENA_GROW_CAST(gData->backTrackStack, REBackTrackData *,
&gData->cx->regexpPool, btsize, btincr);
@ -3813,7 +3811,7 @@ ExecuteREBytecode(REGlobalData *gData, REMatchState *x)
if (!result) {
if (gData->cursz == 0)
return NULL;
if (!JS_CHECK_OPERATION_LIMIT(gData->cx, JSOW_JUMP)) {
if (!JS_CHECK_OPERATION_LIMIT(gData->cx)) {
gData->ok = JS_FALSE;
return NULL;
}

View File

@ -1036,8 +1036,6 @@ js_AddScopeProperty(JSContext *cx, JSScope *scope, jsid id,
spp = js_SearchScope(scope, id, JS_TRUE);
sprop = overwriting = SPROP_FETCH(spp);
if (!sprop) {
JS_COUNT_OPERATION(cx, JSOW_NEW_PROPERTY);
/* Check whether we need to grow, if the load factor is >= .75. */
size = SCOPE_CAPACITY(scope);
if (scope->entryCount + scope->removedCount >= size - (size >> 2)) {

View File

@ -1234,16 +1234,15 @@ TraceRecorder::TraceRecorder(JSContext* cx, VMSideExit* _anchor, Fragment* _frag
import(treeInfo, lirbuf->sp, stackSlots, ngslots, callDepth, typeMap);
if (fragment == fragment->root) {
LIns* counter = lir->insLoadi(cx_ins,
offsetof(JSContext, operationCount));
if (js_HasOperationLimit(cx)) {
/* Add code to decrease the operationCount if the embedding relies
on its auto-updating. */
counter = lir->ins2i(LIR_sub, counter, JSOW_SCRIPT_JUMP);
lir->insStorei(counter, cx_ins,
offsetof(JSContext, operationCount));
}
guard(false, lir->ins2i(LIR_le, counter, 0), snapshot(TIMEOUT_EXIT));
LIns* x = NULL;
/*
* We poll the operation callback request flag. It is updated asynchronously whenever
* the callback is to be invoked.
*/
x = lir->insLoadi(cx_ins, offsetof(JSContext, operationCallbackFlag));
if (x)
guard(true, lir->ins_eq0(x), snapshot(TIMEOUT_EXIT));
}
/* If we are attached to a tree call guard, make sure the guard the inner tree exited from
@ -4228,6 +4227,10 @@ js_MonitorLoopEdge(JSContext* cx, uintN& inlineCallCount)
if (!js_CheckGlobalObjectShape(cx, tm, globalObj, &globalShape, &globalSlots))
js_FlushJITCache(cx);
/* Do not enter the JIT code with a pending operation callback. */
if (cx->operationCallbackFlag)
return false;
jsbytecode* pc = cx->fp->regs->pc;
if (oracle.getHits(pc) >= 0 &&

View File

@ -56,6 +56,7 @@
#include "jsatom.h"
#include "jsbuiltins.h"
#include "jscntxt.h"
#include "jsdate.h"
#include "jsdbgapi.h"
#include "jsemit.h"
#include "jsfun.h"
@ -67,6 +68,8 @@
#include "jsscope.h"
#include "jsscript.h"
#include "prmjtime.h"
#ifdef LIVECONNECT
#include "jsjava.h"
#endif
@ -105,16 +108,52 @@ static jsuword gStackBase;
static size_t gScriptStackQuota = JS_DEFAULT_SCRIPT_STACK_QUOTA;
static jsdouble gOperationTimeout = -1.0;
static uint32 gOperationLimit = 0;
static JSBool
SetTimeoutValue(JSContext *cx, jsdouble t);
static double
GetTimeoutValue(JSContext *cx);
static void
StopWatchdog(JSRuntime *rt);
static JSBool
StartWatchdog(JSRuntime *rt);
/*
* Watchdog thread state.
*/
#ifdef JS_THREADSAFE
static PRCondVar *gWatchdogWakeup;
static PRThread *gWatchdogThread;
static PRIntervalTime gWatchdogSleepDuration = 0;
static PRIntervalTime gLastWatchdogWakeup;
static PRCondVar *gWatchdogWakeup = NULL;
static PRThread *gWatchdogThread = NULL;
/*
* Holding the gcLock already guarantees that the context list is locked when
* the watchdog thread walks it.
*/
#define WITH_LOCKED_CONTEXT_LIST(x) \
JS_BEGIN_MACRO \
x; \
JS_END_MACRO
#else
static JSRuntime *gRuntime = NULL;
/*
* Since signal handlers can't block, we must disable them before manipulating
* the context list.
*/
#define WITH_LOCKED_CONTEXT_LIST(x) \
JS_BEGIN_MACRO \
StopWatchdog(gRuntime); \
x; \
StartWatchdog(gRuntime); \
JS_END_MACRO
#endif
int gExitCode = 0;
@ -211,74 +250,23 @@ GetLine(FILE *file, const char * prompt)
/*
* State to store as JSContext private.
*
* In the JS_THREADSAFE case, when the watchdog thread triggers the operation
* callback, we use PR_IntervalNow(), not JS_Now() as the latter could be
* expensive and is not suitable for calls when a GC lock is held. This forces
* us to use PRIntervalTime as a time type and deal with potential time-wraps
* over uint32 limit. In particular, we must use time relative to some recent
* timestamp when checking for expiration, not absolute time values, as in the
* !JS_THREADSAFE case, when time is int64 and no time-wraps are feasible.
*
* We declare such timestamps as volatile as they are updated in the operation
* We declare such timestamp as volatile as they are updated in the operation
* callback without taking any locks. Any possible race can only lead to more
* frequent callback calls. This is safe as the callback does everything based
* on timing.
*/
struct JSShellContextData {
#ifdef JS_THREADSAFE
PRIntervalTime timeout;
volatile PRIntervalTime startTime; /* startTime + timeout is time when
script must be stopped */
PRIntervalTime yieldPeriod;
volatile PRIntervalTime lastYieldTime; /* lastYieldTime + yieldPeriod is
the time to call
JS_YieldRequest() */
#else
int64 stopTime; /* time when script must be
stopped */
#endif
volatile JSIntervalTime startTime;
};
static JSBool
SetTimeoutValue(JSContext *cx, jsdouble t);
#ifdef JS_THREADSAFE
# define DEFAULT_YIELD_PERIOD() (PR_TicksPerSecond() / 50)
/*
* The function assumes that the GC lock is already held on entry. On a
* successful exit the lock will be held, on failure the lock is released and
* the error is reported.
*/
static JSBool
RescheduleWatchdog(JSContext *cx, JSShellContextData *data, PRIntervalTime now);
#else
const int64 MICROSECONDS_PER_SECOND = 1000000LL;
const int64 MAX_TIME_VALUE = 0x7FFFFFFFFFFFFFFFLL;
#endif
static JSShellContextData *
NewContextData()
{
JSShellContextData *data = (JSShellContextData *)
malloc(sizeof(JSShellContextData));
calloc(sizeof(JSShellContextData), 1);
if (!data)
return NULL;
#ifdef JS_THREADSAFE
data->timeout = PR_INTERVAL_NO_TIMEOUT;
data->yieldPeriod = PR_INTERVAL_NO_TIMEOUT;
# ifdef DEBUG
data->startTime = 0;
data->lastYieldTime = 0;
# endif
#else /* !JS_THREADSAFE */
data->stopTime = MAX_TIME_VALUE;
#endif
data->startTime = js_IntervalNow();
return data;
}
@ -294,36 +282,11 @@ GetContextData(JSContext *cx)
static JSBool
ShellOperationCallback(JSContext *cx)
{
JSShellContextData *data = GetContextData(cx);
JSBool doStop;
#ifdef JS_THREADSAFE
JSBool doYield;
PRIntervalTime now = PR_IntervalNow();
doStop = (data->timeout != PR_INTERVAL_NO_TIMEOUT &&
now - data->startTime >= data->timeout);
doYield = (data->yieldPeriod != PR_INTERVAL_NO_TIMEOUT &&
now - data->lastYieldTime >= data->yieldPeriod);
if (doYield)
data->lastYieldTime = now;
#else /* !JS_THREADSAFE */
int64 now = JS_Now();
doStop = (now >= data->stopTime);
#endif
if (doStop) {
fputs("Error: script is running for too long\n", stderr);
return JS_FALSE;
JSShellContextData *data;
if ((data = GetContextData(cx)) != NULL) {
/* If we spent too much time in this script, abort it. */
return !gOperationLimit || (uint32(js_IntervalNow() - data->startTime) < gOperationLimit);
}
#ifdef JS_THREADSAFE
if (doYield)
JS_YieldRequest(cx);
#endif
return JS_TRUE;
}
@ -346,8 +309,7 @@ SetContextOptions(JSContext *cx)
}
JS_SetThreadStackLimit(cx, stackLimit);
JS_SetScriptStackQuota(cx, gScriptStackQuota);
SetTimeoutValue(cx, gOperationTimeout);
JS_SetOperationCallbackFunction(cx, ShellOperationCallback);
JS_SetOperationCallback(cx, ShellOperationCallback);
}
static void
@ -715,8 +677,7 @@ extern void js_InitJITStatsClass(JSContext *cx, JSObject *glob);
if (++i == argc)
return usage();
gOperationTimeout = atof(argv[i]);
if (!SetTimeoutValue(cx, gOperationTimeout))
if (!SetTimeoutValue(cx, atof(argv[i])))
return JS_FALSE;
break;
@ -2760,7 +2721,9 @@ EvalInContext(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
if (!JS_ConvertArguments(cx, argc, argv, "S / o", &str, &sobj))
return JS_FALSE;
scx = JS_NewContext(JS_GetRuntime(cx), gStackChunkSize);
WITH_LOCKED_CONTEXT_LIST(
scx = JS_NewContext(JS_GetRuntime(cx), gStackChunkSize)
);
if (!scx) {
JS_ReportOutOfMemory(cx);
return JS_FALSE;
@ -2815,7 +2778,9 @@ out:
#ifdef JS_THREADSAFE
JS_EndRequest(scx);
#endif
JS_DestroyContextNoGC(scx);
WITH_LOCKED_CONTEXT_LIST(
JS_DestroyContextNoGC(scx)
);
return ok;
}
@ -2840,6 +2805,11 @@ ShapeOf(JSContext *cx, uintN argc, jsval *vp)
return JS_NewNumberValue(cx, ShapeOf_tn(JSVAL_TO_OBJECT(v)), vp);
}
static void
Callback(JSRuntime *rt)
{
}
#ifdef JS_THREADSAFE
static JSBool
@ -2895,40 +2865,11 @@ DoScatteredWork(JSContext *cx, ScatterThreadData *td)
{
jsval *rval = &td->shared->results[td->index];
JSShellContextData *data = GetContextData(cx);
PRIntervalTime oldYieldPeriod = data->yieldPeriod;
PRIntervalTime newYieldPeriod = DEFAULT_YIELD_PERIOD();
JSBool scheduleOk = JS_TRUE;
/*
* Here oldYieldPeriod is DEFAULT_YIELD_PERIOD() when the scatter reuses
* a context used by a previous scatter call.
*/
if (oldYieldPeriod != newYieldPeriod) {
JS_LOCK_GC(cx->runtime);
PRIntervalTime now = PR_IntervalNow();
JS_ASSERT(oldYieldPeriod == PR_INTERVAL_NO_TIMEOUT);
data->lastYieldTime = now;
data->yieldPeriod = newYieldPeriod;
scheduleOk = RescheduleWatchdog(cx, data, now);
if (scheduleOk)
JS_UNLOCK_GC(cx->runtime);
}
if (!scheduleOk ||
!JS_CallFunctionValue(cx, NULL, td->fn, 0, NULL, rval)) {
if (!JS_CallFunctionValue(cx, NULL, td->fn, 0, NULL, rval)) {
*rval = JSVAL_VOID;
JS_GetPendingException(cx, rval);
JS_ClearPendingException(cx);
}
/*
* We do not need to lock or call RescheduleWatchdog. Here yieldPeriod
* can only stay at DEFAULT_YIELD_PERIOD or go to PR_INTERVAL_NO_TIMEOUT.
* Thus we never need to wake up the watchdog thread earlier.
*/
JS_ASSERT(oldYieldPeriod == data->yieldPeriod ||
oldYieldPeriod == PR_INTERVAL_NO_TIMEOUT);
data->yieldPeriod = oldYieldPeriod;
}
static void
@ -2941,7 +2882,7 @@ RunScatterThread(void *arg)
td = (ScatterThreadData *)arg;
cx = td->cx;
/* Wait for go signal. */
/* Wait for our signal. */
PR_Lock(td->shared->lock);
while ((st = td->shared->status) == SCATTER_WAIT)
PR_WaitCondVar(td->shared->cvar, PR_INTERVAL_NO_TIMEOUT);
@ -2950,7 +2891,7 @@ RunScatterThread(void *arg)
if (st == SCATTER_CANCEL)
return;
/* We are go. */
/* We are good to go. */
JS_SetContextThread(cx);
JS_SetThreadStackLimit(cx, 0);
JS_BeginRequest(cx);
@ -3043,7 +2984,10 @@ Scatter(JSContext *cx, uintN argc, jsval *vp)
}
for (i = 1; i < n; i++) {
JSContext *newcx = JS_NewContext(JS_GetRuntime(cx), 8192);
JSContext *newcx;
WITH_LOCKED_CONTEXT_LIST(
newcx = JS_NewContext(JS_GetRuntime(cx), 8192)
);
if (!newcx)
goto fail;
JS_SetGlobalObject(newcx, JS_GetGlobalObject(cx));
@ -3101,7 +3045,9 @@ out:
acx = sd.threads[i].cx;
if (acx) {
JS_SetContextThread(acx);
JS_DestroyContext(acx);
WITH_LOCKED_CONTEXT_LIST(
JS_DestroyContext(acx)
);
}
}
free(sd.threads);
@ -3123,48 +3069,6 @@ fail:
goto out;
}
/*
* Find duration between now and base + period, set it to sleepDuration if the
* latter value is greater and set expired to true if base + period comes
* before now. This function correctly deals with a possible time wrap between
* base and now.
*/
static void
UpdateSleepDuration(PRIntervalTime now, PRIntervalTime base,
PRIntervalTime period, PRIntervalTime &sleepDuration,
JSBool &expired)
{
if (period == PR_INTERVAL_NO_TIMEOUT)
return;
PRIntervalTime t;
PRIntervalTime diff = now - base;
if (diff >= period) {
expired = JS_TRUE;
t = period;
} else {
t = period - diff;
}
if (sleepDuration == PR_INTERVAL_NO_TIMEOUT || sleepDuration > t)
sleepDuration = t;
}
static void
CheckCallbackTime(JSContext *cx, JSShellContextData *data, PRIntervalTime now,
PRIntervalTime &sleepDuration)
{
JSBool expired = JS_FALSE;
UpdateSleepDuration(now, data->startTime, data->timeout,
sleepDuration, expired);
UpdateSleepDuration(now, data->lastYieldTime, data->yieldPeriod,
sleepDuration, expired);
if (expired) {
JS_ASSERT(sleepDuration != PR_INTERVAL_NO_TIMEOUT);
JS_TriggerOperationCallback(cx);
}
}
static void
WatchdogMain(void *arg)
{
@ -3172,82 +3076,123 @@ WatchdogMain(void *arg)
JS_LOCK_GC(rt);
while (gWatchdogThread) {
PRIntervalTime now = PR_IntervalNow();
PRIntervalTime sleepDuration = PR_INTERVAL_NO_TIMEOUT;
JSContext *iter = NULL;
JSContext *acx;
JSContext *acx = NULL;
while ((acx = js_NextActiveContext(rt, acx)))
JS_TriggerOperationCallback(acx);
while ((acx = js_ContextIterator(rt, JS_FALSE, &iter))) {
if (acx->requestDepth > 0) {
JSShellContextData *data = (JSShellContextData *)
JS_GetContextPrivate(acx);
/*
* For the last context inside JS_DestroyContext the engine
* starts a new request to shutdown the runtime. For such
* context data is null.
*/
if (data)
CheckCallbackTime(acx, data, now, sleepDuration);
}
}
gLastWatchdogWakeup = now;
gWatchdogSleepDuration = sleepDuration;
#ifdef DEBUG
PRStatus status =
#endif
PR_WaitCondVar(gWatchdogWakeup, sleepDuration);
/* Trigger the operation callbacks every second. */
PR_WaitCondVar(gWatchdogWakeup, PR_SecondsToInterval(1));
JS_ASSERT(status == PR_SUCCESS);
}
/* Wake up the main thread waiting for the watchdog to terminate. */
PR_NotifyCondVar(gWatchdogWakeup);
JS_UNLOCK_GC(rt);
}
static JSBool
RescheduleWatchdog(JSContext *cx, JSShellContextData *data, PRIntervalTime now)
StartWatchdog(JSRuntime *rt)
{
JS_ASSERT(data == GetContextData(cx));
PRIntervalTime nextCallbackTime = PR_INTERVAL_NO_TIMEOUT;
CheckCallbackTime(cx, data, now, nextCallbackTime);
if (nextCallbackTime == PR_INTERVAL_NO_TIMEOUT)
if (gWatchdogThread || !gOperationLimit)
return JS_TRUE;
if (gWatchdogThread) {
/*
* Notify the watchdog if it would wake up after data->watchdogLimit
* expires. PRIntervalTime is unsigned so the subtraction in the
* following check gives the correct interval even when time wraps
* around between gLastWatchdogWakeup and now.
*/
if (gWatchdogSleepDuration == PR_INTERVAL_NO_TIMEOUT ||
PRInt32(now - gLastWatchdogWakeup) <
PRInt32(gWatchdogSleepDuration) - PRInt32(nextCallbackTime)) {
PR_NotifyCondVar(gWatchdogWakeup);
}
} else {
gWatchdogThread = PR_CreateThread(PR_USER_THREAD,
WatchdogMain,
cx->runtime,
PR_PRIORITY_NORMAL,
PR_LOCAL_THREAD,
PR_UNJOINABLE_THREAD,
0);
if (!gWatchdogThread) {
JS_UNLOCK_GC(cx->runtime);
JS_ReportError(cx, "failed to create the watchdog thread");
return JS_FALSE;
}
/* The watchdog thread does not sleep on creation. */
JS_ASSERT(gWatchdogSleepDuration == 0);
gLastWatchdogWakeup = now;
JS_LOCK_GC(rt);
gWatchdogThread = PR_CreateThread(PR_USER_THREAD,
WatchdogMain,
rt,
PR_PRIORITY_NORMAL,
PR_LOCAL_THREAD,
PR_UNJOINABLE_THREAD,
0);
if (!gWatchdogThread) {
JS_UNLOCK_GC(rt);
return JS_FALSE;
}
JS_UNLOCK_GC(rt);
return JS_TRUE;
}
static void
StopWatchdog(JSRuntime *rt)
{
JS_LOCK_GC(rt);
if (gWatchdogThread) {
/*
* The watchdog thread is running, tell it to terminate waking it up
* if necessary and wait until it signals that it done.
*/
gWatchdogThread = NULL;
PR_NotifyCondVar(gWatchdogWakeup);
PR_WaitCondVar(gWatchdogWakeup, PR_INTERVAL_NO_TIMEOUT);
}
JS_UNLOCK_GC(rt);
JS_DESTROY_CONDVAR(gWatchdogWakeup);
}
#else
static void
WatchdogHandler(int sig)
{
JSRuntime *rt = gRuntime;
JSContext *acx = NULL;
while ((acx = js_NextActiveContext(rt, acx)))
JS_TriggerOperationCallback(acx);
#ifndef XP_WIN
alarm(1);
#endif
}
#ifdef XP_WIN
static HANDLE gTimerHandle = 0;
VOID CALLBACK TimerCallback(PVOID lpParameter, BOOLEAN TimerOrWaitFired)
{
WatchdogHandler(0);
}
#endif
static JSBool
StartWatchdog(JSRuntime *rt)
{
if (!gOperationLimit)
return JS_TRUE;
#ifdef XP_WIN
JS_ASSERT(gTimerHandle == 0);
if (!CreateTimerQueueTimer(&gTimerHandle,
NULL,
(WAITORTIMERCALLBACK)TimerCallback,
NULL,
1000,
1000,
WT_EXECUTEINTIMERTHREAD))
return JS_FALSE;
#else
signal(SIGALRM, WatchdogHandler); /* set the Alarm signal capture */
alarm(1);
#endif
return JS_TRUE;
}
static void
StopWatchdog(JSRuntime *rt)
{
#ifdef XP_WIN
DeleteTimerQueueTimer(NULL, gTimerHandle, NULL);
gTimerHandle = 0;
#else
alarm(0);
signal(SIGALRM, NULL);
#endif
}
#endif /* JS_THREADSAFE */
static JSBool
@ -3259,68 +3204,30 @@ SetTimeoutValue(JSContext *cx, jsdouble t)
return JS_FALSE;
}
JSShellContextData *data = GetContextData(cx);
#ifdef JS_THREADSAFE
JS_LOCK_GC(cx->runtime);
if (t < 0) {
data->timeout = PR_INTERVAL_NO_TIMEOUT;
} else {
PRIntervalTime now = PR_IntervalNow();
data->timeout = PRIntervalTime(t * PR_TicksPerSecond());
data->startTime = now;
if (!RescheduleWatchdog(cx, data, now)) {
/* The GC lock is already released here. */
return JS_FALSE;
}
}
JS_UNLOCK_GC(cx->runtime);
gOperationLimit = (t > 0) ? JSInt64(t*1000) : 0;
#else /* !JS_THREADSAFE */
if (t < 0) {
data->stopTime = MAX_TIME_VALUE;
JS_SetOperationLimit(cx, JS_MAX_OPERATION_LIMIT);
} else {
int64 now = JS_Now();
data->stopTime = now + int64(t * MICROSECONDS_PER_SECOND);
/*
* Call the callback infrequently enough to avoid the overhead of
* time calculations there.
*/
JS_SetOperationLimit(cx, 1000 * JS_OPERATION_WEIGHT_BASE);
if (!StartWatchdog(cx->runtime)) {
JS_ReportError(cx, "failed to create the watchdog");
return JS_FALSE;
}
#endif
return JS_TRUE;
}
static double
GetTimeoutValue(JSContext *cx)
{
if (!gOperationLimit)
return -1;
return gOperationLimit/PRMJ_USEC_PER_MSEC;
}
static JSBool
Timeout(JSContext *cx, uintN argc, jsval *vp)
{
if (argc == 0) {
JSShellContextData *data = GetContextData(cx);
jsdouble t; /* remaining time to run */
#ifdef JS_THREADSAFE
if (data->timeout == PR_INTERVAL_NO_TIMEOUT) {
t = -1.0;
} else {
PRIntervalTime expiredTime = PR_IntervalNow() - data->startTime;
t = (expiredTime >= data->timeout)
? 0.0
: jsdouble(data->timeout - expiredTime) / PR_TicksPerSecond();
}
#else
if (data->stopTime == MAX_TIME_VALUE) {
t = -1.0;
} else {
int64 remainingTime = data->stopTime - JS_Now();
t = (remainingTime <= 0)
? 0.0
: jsdouble(remainingTime) / MICROSECONDS_PER_SECOND;
}
#endif
return JS_NewNumberValue(cx, t, vp);
}
if (argc == 0)
return JS_NewNumberValue(cx, GetTimeoutValue(cx), vp);
if (argc > 1) {
JS_ReportError(cx, "Wrong number of arguments");
@ -3335,6 +3242,20 @@ Timeout(JSContext *cx, uintN argc, jsval *vp)
return SetTimeoutValue(cx, t);
}
static JSBool
Elapsed(JSContext *cx, uintN argc, jsval *vp)
{
if (argc == 0) {
double d = 0.0;
JSShellContextData *data = GetContextData(cx);
if (data)
d = js_IntervalNow() - data->startTime;
return JS_NewNumberValue(cx, d, vp);
}
JS_ReportError(cx, "Wrong number of arguments");
return JS_FALSE;
}
JS_DEFINE_TRCINFO_1(Print, (2, (static, JSVAL_FAIL, Print_tn, CONTEXT, STRING, 0, 0)))
JS_DEFINE_TRCINFO_1(ShapeOf, (1, (static, INT32, ShapeOf_tn, OBJECT, 0, 0)))
@ -3529,6 +3450,7 @@ static JSFunctionSpec shell_functions[] = {
#endif
JS_FS("snarf", Snarf, 0,0,0),
JS_FN("timeout", Timeout, 1,0),
JS_FN("elapsed", Elapsed, 0,0),
JS_FS_END
};
@ -3620,6 +3542,7 @@ static const char *const shell_help_messages[] = {
"timeout([seconds])\n"
" Get/Set the limit in seconds for the execution time for the current context.\n"
" A negative value (default) means that the execution time is unlimited.",
"elapsed() Execution time elapsed for the current context.\n",
};
/* Help messages must match shell functions. */
@ -4388,9 +4311,11 @@ Evaluate(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
static JSBool
ContextCallback(JSContext *cx, uintN contextOp)
{
JSShellContextData *data;
switch (contextOp) {
case JSCONTEXT_NEW: {
JSShellContextData *data = NewContextData();
data = NewContextData();
if (!data)
return JS_FALSE;
JS_SetContextPrivate(cx, data);
@ -4398,10 +4323,8 @@ ContextCallback(JSContext *cx, uintN contextOp)
JS_SetVersion(cx, JSVERSION_LATEST);
SetContextOptions(cx);
break;
}
case JSCONTEXT_DESTROY: {
JSShellContextData *data = GetContextData(cx);
case JSCONTEXT_DESTROY:
data = GetContextData(cx);
JS_SetContextPrivate(cx, NULL);
free(data);
break;
@ -4462,11 +4385,15 @@ main(int argc, char **argv, char **envp)
gWatchdogWakeup = JS_NEW_CONDVAR(rt->gcLock);
if (!gWatchdogWakeup)
return 1;
#endif
#else
gRuntime = rt;
#endif
JS_SetContextCallback(rt, ContextCallback);
cx = JS_NewContext(rt, gStackChunkSize);
WITH_LOCKED_CONTEXT_LIST(
cx = JS_NewContext(rt, gStackChunkSize)
);
if (!cx)
return 1;
@ -4571,22 +4498,11 @@ main(int argc, char **argv, char **envp)
JS_EndRequest(cx);
#endif
JS_DestroyContext(cx);
WITH_LOCKED_CONTEXT_LIST(
JS_DestroyContext(cx)
);
#ifdef JS_THREADSAFE
JS_LOCK_GC(rt);
if (gWatchdogThread) {
/*
* The watchdog thread is running, tell it to terminate waking it up
* if necessary and wait until it signals that it done.
*/
gWatchdogThread = NULL;
PR_NotifyCondVar(gWatchdogWakeup);
PR_WaitCondVar(gWatchdogWakeup, PR_INTERVAL_NO_TIMEOUT);
}
JS_UNLOCK_GC(rt);
JS_DESTROY_CONDVAR(gWatchdogWakeup);
#endif
StopWatchdog(rt);
JS_DestroyRuntime(rt);
JS_ShutDown();

View File

@ -1539,7 +1539,7 @@ mozJSComponentLoader::ImportInto(const nsACString & aLocation,
jsval symbols;
if (targetObj) {
JSAutoRequest ar(mContext);
JSCLContextHelper cx(this);
if (!JS_GetProperty(mContext, mod->global,
"EXPORTED_SYMBOLS", &symbols)) {

View File

@ -3404,8 +3404,7 @@ ContextHolder::ContextHolder(JSContext *aOuterCx, JSObject *aSandbox)
JSOPTION_PRIVATE_IS_NSISUPPORTS);
JS_SetGlobalObject(mJSContext, aSandbox);
JS_SetContextPrivate(mJSContext, this);
JS_SetOperationCallback(mJSContext, ContextHolderOperationCallback,
JS_GetOperationLimit(aOuterCx));
JS_SetOperationCallback(mJSContext, ContextHolderOperationCallback);
}
}
@ -3421,7 +3420,6 @@ ContextHolder::ContextHolderOperationCallback(JSContext *cx)
JSBool ok = JS_TRUE;
if(callback)
ok = callback(origCx);
JS_SetOperationLimit(cx, JS_GetOperationLimit(origCx));
return ok;
}

View File

@ -793,6 +793,49 @@ JSBool XPCJSRuntime::GCCallback(JSContext *cx, JSGCStatus status)
return JS_TRUE;
}
// Auto JS GC lock helper.
class AutoLockJSGC
{
public:
AutoLockJSGC(JSRuntime* rt) : mJSRuntime(rt) { JS_LOCK_GC(mJSRuntime); }
~AutoLockJSGC() { JS_UNLOCK_GC(mJSRuntime); }
private:
JSRuntime* mJSRuntime;
// Disable copy or assignment semantics.
AutoLockJSGC(const AutoLockJSGC&);
void operator=(const AutoLockJSGC&);
};
//static
void
XPCJSRuntime::WatchdogMain(void *arg)
{
XPCJSRuntime* self = static_cast<XPCJSRuntime*>(arg);
// Lock lasts until we return
AutoLockJSGC lock(self->mJSRuntime);
while (self->mWatchdogThread)
{
#ifdef DEBUG
PRStatus status =
#endif
PR_WaitCondVar(self->mWatchdogWakeup, PR_TicksPerSecond());
JS_ASSERT(status == PR_SUCCESS);
JSContext* cx = nsnull;
while((cx = js_NextActiveContext(self->mJSRuntime, cx)))
{
JS_TriggerOperationCallback(cx);
}
}
/* Wake up the main thread waiting for the watchdog to terminate. */
PR_NotifyCondVar(self->mWatchdogWakeup);
}
/***************************************************************************/
#ifdef XPC_CHECK_WRAPPERS_AT_SHUTDOWN
@ -841,6 +884,24 @@ void XPCJSRuntime::SystemIsBeingShutDown(JSContext* cx)
XPCJSRuntime::~XPCJSRuntime()
{
if (mWatchdogWakeup)
{
// If the watchdog thread is running, tell it to terminate waking it
// up if necessary and wait until it signals that it finished. As we
// must release the lock before calling PR_DestroyCondVar, we use an
// extra block here.
{
AutoLockJSGC lock(mJSRuntime);
if (mWatchdogThread) {
mWatchdogThread = nsnull;
PR_NotifyCondVar(mWatchdogWakeup);
PR_WaitCondVar(mWatchdogWakeup, PR_INTERVAL_NO_TIMEOUT);
}
}
PR_DestroyCondVar(mWatchdogWakeup);
mWatchdogWakeup = nsnull;
}
#ifdef XPC_DUMP_AT_SHUTDOWN
{
// count the total JSContexts in use
@ -1014,7 +1075,9 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect)
mVariantRoots(nsnull),
mWrappedJSRoots(nsnull),
mObjectHolderRoots(nsnull),
mUnrootedGlobalCount(0)
mUnrootedGlobalCount(0),
mWatchdogWakeup(nsnull),
mWatchdogThread(nsnull)
{
#ifdef XPC_CHECK_WRAPPERS_AT_SHUTDOWN
DEBUG_WrappedNativeHashtable =
@ -1057,6 +1120,7 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect)
JS_SetContextCallback(mJSRuntime, ContextCallback);
JS_SetGCCallbackRT(mJSRuntime, GCCallback);
JS_SetExtraGCRoots(mJSRuntime, TraceJS, this);
mWatchdogWakeup = JS_NEW_CONDVAR(mJSRuntime->gcLock);
}
if(!JS_DHashTableInit(&mJSHolders, JS_DHashGetStubOps(), nsnull,
@ -1068,6 +1132,12 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect)
if(mJSRuntime && !JS_GetGlobalDebugHooks(mJSRuntime)->debuggerHandler)
xpc_InstallJSDebuggerKeywordHandler(mJSRuntime);
#endif
AutoLockJSGC lock(mJSRuntime);
mWatchdogThread = PR_CreateThread(PR_USER_THREAD, WatchdogMain, this,
PR_PRIORITY_NORMAL, PR_LOCAL_THREAD,
PR_UNJOINABLE_THREAD, 0);
}
// static
@ -1089,7 +1159,8 @@ XPCJSRuntime::newXPCJSRuntime(nsXPConnect* aXPConnect)
self->GetNativeScriptableSharedMap() &&
self->GetDyingWrappedNativeProtoMap() &&
self->GetExplicitNativeWrapperMap() &&
self->GetMapLock())
self->GetMapLock() &&
self->mWatchdogThread)
{
return self;
}
@ -1259,7 +1330,8 @@ void
XPCRootSetElem::AddToRootSet(JSRuntime* rt, XPCRootSetElem** listHead)
{
NS_ASSERTION(!mSelfp, "Must be not linked");
JS_LOCK_GC(rt);
AutoLockJSGC lock(rt);
mSelfp = listHead;
mNext = *listHead;
if(mNext)
@ -1268,19 +1340,18 @@ XPCRootSetElem::AddToRootSet(JSRuntime* rt, XPCRootSetElem** listHead)
mNext->mSelfp = &mNext;
}
*listHead = this;
JS_UNLOCK_GC(rt);
}
void
XPCRootSetElem::RemoveFromRootSet(JSRuntime* rt)
{
NS_ASSERTION(mSelfp, "Must be linked");
JS_LOCK_GC(rt);
AutoLockJSGC lock(rt);
NS_ASSERTION(*mSelfp == this, "Link invariant");
*mSelfp = mNext;
if(mNext)
mNext->mSelfp = mSelfp;
JS_UNLOCK_GC(rt);
#ifdef DEBUG
mSelfp = nsnull;
mNext = nsnull;

View File

@ -761,7 +761,11 @@ private:
XPCJSRuntime(); // no implementation
XPCJSRuntime(nsXPConnect* aXPConnect);
private:
// The caller must be holding the GC lock
void RescheduleWatchdog(XPCContext* ccx);
static void WatchdogMain(void *arg);
static const char* mStrings[IDX_TOTAL_COUNT];
jsid mStrIDs[IDX_TOTAL_COUNT];
jsval mStrJSVals[IDX_TOTAL_COUNT];
@ -788,6 +792,8 @@ private:
XPCRootSetElem *mObjectHolderRoots;
JSDHashTable mJSHolders;
uintN mUnrootedGlobalCount;
PRCondVar *mWatchdogWakeup;
PRThread *mWatchdogThread;
};
/***************************************************************************/