--- a/dom/src/base/nsJSEnvironment.cpp
+++ b/dom/src/base/nsJSEnvironment.cpp
@@ -849,19 +849,16 @@ PrintWinCodebase(nsGlobalWindow *win)
}
nsCAutoString spec;
uri->GetSpec(spec);
printf("%s\n", spec.get());
}
#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)
{
size_t bytes = cx->runtime->gcBytes;
size_t lastBytes = cx->runtime->gcLastBytes;
if ((bytes > 8192 && bytes / 16 > lastBytes)
#ifdef DEBUG
@@ -922,17 +919,17 @@ nsJSContext::DOMOperationCallback(JSCont
PRBool lowMemory;
mem->IsLowMemory(&lowMemory);
if (lowMemory) {
// try to clean up:
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);
if (lowMemory) {
if (nsContentUtils::GetBoolPref("dom.prevent_oom_dialog", PR_FALSE))
return JS_FALSE;
@@ -983,17 +980,17 @@ nsJSContext::DOMOperationCallback(JSCont
// If we get here we're most likely executing an infinite loop in JS,
// we'll tell the user about this and we'll give the user the option
// of stopping the execution of the script.
nsCOMPtr<nsIPrompt> prompt = GetPromptFromContext(ctx);
NS_ENSURE_TRUE(prompt, JS_TRUE);
// 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.
if (debugPossible) {
PRBool jsds_IsOn = PR_FALSE;
const char jsdServiceCtrID[] = "@mozilla.org/js/jsd/debugger-service;1";
nsCOMPtr<jsdIExecutionHook> jsdHook;
nsCOMPtr<jsdIDebuggerService> jsds = do_GetService(jsdServiceCtrID, &rv);
@@ -1092,21 +1089,26 @@ nsJSContext::DOMOperationCallback(JSCont
PRBool neverShowDlgChk = PR_FALSE;
PRUint32 buttonFlags = (nsIPrompt::BUTTON_TITLE_IS_STRING *
(nsIPrompt::BUTTON_POS_0 + nsIPrompt::BUTTON_POS_1));
// Add a third button if necessary:
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
if (neverShowDlgChk) {
nsIPrefBranch *prefBranch = nsContentUtils::GetPrefBranch();
if (prefBranch) {
prefBranch->SetIntPref(isTrackingChromeCodeTime ?
@@ -1244,18 +1246,17 @@ nsJSContext::nsJSContext(JSRuntime *aRun
// Make sure the new context gets the default context options
::JS_SetOptions(mContext, mDefaultJSOptions);
// Watch for the JS boolean options
nsContentUtils::RegisterPrefCallback(js_options_dot_str,
JSOptionChangedCallback,
this);
- ::JS_SetOperationCallback(mContext, DOMOperationCallback,
- MAYBE_GC_OPERATION_WEIGHT);
+ ::JS_SetOperationCallback(mContext, DOMOperationCallback);
static JSLocaleCallbacks localeCallbacks =
{
LocaleToUpperCase,
LocaleToLowerCase,
LocaleCompare,
LocaleToUnicode
};
@@ -1297,19 +1298,16 @@ void
nsJSContext::Unlink()
{
if (!mContext)
return;
// 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,
this);
// Release mGlobalWrapperRef before the context is destroyed
mGlobalWrapperRef = nsnull;
--- a/dom/src/threads/nsDOMThreadService.cpp
+++ b/dom/src/threads/nsDOMThreadService.cpp
@@ -100,20 +100,16 @@ PR_STATIC_ASSERT(THREADPOOL_MAX_THREADS
// As we suspend threads for various reasons (navigating away from the page,
// loading scripts, etc.) we open another slot in the thread pool for another
// worker to use. We can't do this forever so we set an absolute cap on the
// number of threads we'll allow to prevent DOS attacks.
#define THREADPOOL_THREAD_CAP 20
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
// Easy access for static functions. No reference here.
static nsDOMThreadService* gDOMThreadService = nsnull;
// These pointers actually carry references and must be released.
static nsIObserverService* gObserverService = nsnull;
@@ -321,17 +317,17 @@ public:
NS_ASSERTION(cx, "nsDOMThreadService didn't give us a context!");
NS_ASSERTION(!JS_GetGlobalObject(cx), "Shouldn't have a global!");
JS_SetContextPrivate(cx, mWorker);
// 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.
JS_SetGlobalObject(cx, NULL);
JS_SetContextPrivate(cx, NULL);
}
else {
// This is usually due to a parse error in the worker script...
@@ -343,19 +339,18 @@ public:
mon.NotifyAll();
}
return NS_OK;
}
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;
{
nsAutoMonitor mon(gDOMThreadService->mMonitor);
runnable = dont_AddRef((nsIRunnable*)mRunnables.PopFront());
@@ -367,18 +362,27 @@ protected:
}
#endif
gDOMThreadService->WorkerComplete(this);
mon.NotifyAll();
return;
}
}
+ 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();
}
}
// Set at construction
nsRefPtr<nsDOMWorker> mWorker;
@@ -398,50 +402,50 @@ DOMWorkerOperationCallback(JSContext* aC
// Want a strong ref here to make sure that the monitor we wait on won't go
// away.
nsRefPtr<nsDOMWorkerPool> pool;
PRBool wasSuspended = PR_FALSE;
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]",
static_cast<void*>(worker)));
if (wasSuspended) {
if (extraThreadAllowed) {
gDOMThreadService->ChangeThreadPoolMaxThreads(-1);
}
JS_ResumeRequest(aCx, suspendDepth);
}
// Kill exectuion of the currently running JS.
- return PR_FALSE;
+ return JS_FALSE;
}
// Break out if we're not suspended.
if (!worker->IsSuspended()) {
if (wasSuspended) {
if (extraThreadAllowed) {
gDOMThreadService->ChangeThreadPoolMaxThreads(-1);
}
JS_ResumeRequest(aCx, suspendDepth);
}
- break;
+ return JS_TRUE;
}
if (!wasSuspended) {
// Make sure we can get the monitor we need to wait on. It's possible that
// 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();
// Make sure to suspend our request while we block like this, otherwise we
// prevent GC for everyone.
suspendDepth = JS_SuspendRequest(aCx);
@@ -452,30 +456,20 @@ DOMWorkerOperationCallback(JSContext* aC
NS_SUCCEEDED(gDOMThreadService->ChangeThreadPoolMaxThreads(1));
// Only do all this setup once.
wasSuspended = PR_TRUE;
}
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
DOMWorkerErrorReporter(JSContext* aCx,
const char* aMessage,
JSErrorReport* aReport)
{
NS_ASSERTION(!NS_IsMainThread(), "Huh?!");
@@ -628,16 +622,19 @@ nsDOMThreadService::Init()
NS_ENSURE_TRUE(mMonitor, NS_ERROR_OUT_OF_MEMORY);
PRBool success = mWorkersInProgress.Init();
NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
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);
runtimeSvc.forget(&gJSRuntimeService);
nsCOMPtr<nsIThreadJSContextStack>
contextStack(do_GetService("@mozilla.org/js/xpc/ContextStack;1"));
NS_ENSURE_TRUE(contextStack, NS_ERROR_FAILURE);
@@ -833,18 +830,17 @@ nsDOMThreadService::CreateJSContext()
gJSRuntimeService->GetRuntime(&rt);
NS_ENSURE_TRUE(rt, nsnull);
JSAutoContextDestroyer cx(JS_NewContext(rt, 8192));
NS_ENSURE_TRUE(cx, nsnull);
JS_SetErrorReporter(cx, DOMWorkerErrorReporter);
- JS_SetOperationCallback(cx, DOMWorkerOperationCallback,
- 100 * JS_OPERATION_WEIGHT_BASE);
+ JS_SetOperationCallback(cx, DOMWorkerOperationCallback);
static JSSecurityCallbacks securityCallbacks = {
nsDOMWorkerSecurityManager::JSCheckAccess,
nsDOMWorkerSecurityManager::JSTranscodePrincipals,
nsDOMWorkerSecurityManager::JSFindPrincipal
};
JS_SetContextSecurityCallbacks(cx, &securityCallbacks);
@@ -867,16 +863,18 @@ nsDOMThreadService::CreateJSContext()
stackLimit = (currentStackAddr + kStackSize > currentStackAddr) ?
currentStackAddr + kStackSize :
(jsuword) -1;
#endif
JS_SetThreadStackLimit(cx, stackLimit);
JS_SetScriptStackQuota(cx, 100*1024*1024);
+ JS_SetOptions(cx, JS_GetOptions(cx) | JSOPTION_JIT | JSOPTION_ANONFUNFIX);
+
return cx.forget();
}
already_AddRefed<nsDOMWorkerPool>
nsDOMThreadService::GetPoolForGlobal(nsIScriptGlobalObject* aGlobalObject,
PRBool aRemove)
{
NS_ASSERTION(aGlobalObject, "Null pointer!");
@@ -889,45 +887,65 @@ nsDOMThreadService::GetPoolForGlobal(nsI
if (aRemove) {
mPools.Remove(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)
{
NS_ASSERTION(aGlobalObject, "Null pointer!");
nsRefPtr<nsDOMWorkerPool> pool = GetPoolForGlobal(aGlobalObject, PR_TRUE);
if (pool) {
pool->Cancel();
+ TriggerOperationCallbackForPool(pool);
}
}
void
nsDOMThreadService::SuspendWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject)
{
NS_ASSERTION(aGlobalObject, "Null pointer!");
nsRefPtr<nsDOMWorkerPool> pool = GetPoolForGlobal(aGlobalObject, PR_FALSE);
if (pool) {
pool->Suspend();
+ TriggerOperationCallbackForPool(pool);
}
}
void
nsDOMThreadService::ResumeWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject)
{
NS_ASSERTION(aGlobalObject, "Null pointer!");
nsRefPtr<nsDOMWorkerPool> pool = GetPoolForGlobal(aGlobalObject, PR_FALSE);
if (pool) {
pool->Resume();
+ TriggerOperationCallbackForPool(pool);
}
}
void
nsDOMThreadService::NoteEmptyPool(nsDOMWorkerPool* aPool)
{
NS_ASSERTION(aPool, "Null pointer!");
@@ -963,16 +981,27 @@ nsDOMThreadService::ChangeThreadPoolMaxT
if (newThreadCount > THREADPOOL_THREAD_CAP) {
NS_WARNING("Thread pool cap reached!");
return NS_ERROR_FAILURE;
}
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;
}
nsIJSRuntimeService*
nsDOMThreadService::JSRuntimeService()
{
return gJSRuntimeService;
}
@@ -1063,16 +1092,26 @@ nsDOMThreadService::OnThreadCreated()
NS_ENSURE_TRUE(cx, NS_ERROR_FAILURE);
PRStatus status = PR_SetThreadPrivate(gJSContextIndex, cx);
if (status != PR_SUCCESS) {
NS_WARNING("Failed to set context on thread!");
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.
gThreadJSContextStack->Push(cx);
gThreadJSContextStack->SetSafeJSContext(cx);
return NS_OK;
}
@@ -1082,16 +1121,21 @@ nsDOMThreadService::OnThreadShuttingDown
{
LOG(("Thread shutting down"));
NS_ASSERTION(gJSContextIndex != BAD_TLS_INDEX, "No context index!");
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!");
gThreadJSContextStack->SetSafeJSContext(nsnull);
nsContentUtils::XPConnect()->ReleaseJSContext(cx, PR_TRUE);
}
--- a/dom/src/threads/nsDOMThreadService.h
+++ b/dom/src/threads/nsDOMThreadService.h
@@ -129,16 +129,18 @@ private:
void WorkerComplete(nsDOMWorkerRunnable* aRunnable);
static JSContext* CreateJSContext();
already_AddRefed<nsDOMWorkerPool>
GetPoolForGlobal(nsIScriptGlobalObject* aGlobalObject,
PRBool aRemove);
+ void TriggerOperationCallbackForPool(nsDOMWorkerPool* aPool);
+
void NoteEmptyPool(nsDOMWorkerPool* aPool);
void TimeoutReady(nsDOMWorkerTimeout* aTimeout);
nsresult RegisterWorker(nsDOMWorker* aWorker,
nsIScriptGlobalObject* aGlobalObject);
void GetAppName(nsAString& aAppName);
@@ -154,16 +156,20 @@ private:
// mMonitor protects all access to mWorkersInProgress and
// mCreationsInProgress.
PRMonitor* mMonitor;
// 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;
nsString mUserAgent;
PRBool mNavigatorStringsLoaded;
};
--- a/dom/src/threads/nsDOMWorker.cpp
+++ b/dom/src/threads/nsDOMWorker.cpp
@@ -946,17 +946,16 @@ NS_IMPL_CI_INTERFACE_GETTER3(nsDOMWorker
NS_IMPL_THREADSAFE_DOM_CI(nsDOMWorkerClassInfo)
static nsDOMWorkerClassInfo sDOMWorkerClassInfo;
nsDOMWorker::nsDOMWorker(nsDOMWorker* aParent,
nsIXPConnectWrappedNative* aParentWN)
: mParent(aParent),
mParentWN(aParentWN),
- mCallbackCount(0),
mLock(nsnull),
mInnerScope(nsnull),
mGlobal(NULL),
mNextTimeoutId(0),
mFeatureSuspendDepth(0),
mWrappedNative(nsnull),
mErrorHandlerRecursionCount(0),
mCanceled(PR_FALSE),
--- a/dom/src/threads/nsDOMWorker.h
+++ b/dom/src/threads/nsDOMWorker.h
@@ -212,18 +212,16 @@ private:
private:
// mParent will live as long as mParentWN but only mParentWN will keep the JS
// reflection alive, so we only hold one strong reference to mParentWN.
nsDOMWorker* mParent;
nsCOMPtr<nsIXPConnectWrappedNative> mParentWN;
- PRUint32 mCallbackCount;
-
PRLock* mLock;
nsRefPtr<nsDOMWorkerMessageHandler> mInnerHandler;
nsRefPtr<nsDOMWorkerMessageHandler> mOuterHandler;
nsRefPtr<nsDOMWorkerPool> mPool;
nsDOMWorkerScope* mInnerScope;
--- a/dom/src/threads/nsDOMWorkerPool.cpp
+++ b/dom/src/threads/nsDOMWorkerPool.cpp
@@ -159,31 +159,32 @@ nsDOMWorkerPool::GetWorkers(nsTArray<nsD
}
void
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;
mParentDocument = nsnull;
}
void
nsDOMWorkerPool::Suspend()
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -1853,34 +1853,32 @@ JS_ComputeThis(JSContext *cx, jsval *vp)
}
JS_PUBLIC_API(void *)
JS_malloc(JSContext *cx, size_t nbytes)
{
void *p;
JS_ASSERT(nbytes != 0);
- JS_COUNT_OPERATION(cx, JSOW_ALLOCATION);
if (nbytes == 0)
nbytes = 1;
p = malloc(nbytes);
if (!p) {
JS_ReportOutOfMemory(cx);
return NULL;
}
js_UpdateMallocCounter(cx, nbytes);
return p;
}
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);
return p;
}
JS_PUBLIC_API(void)
JS_free(JSContext *cx, void *p)
@@ -5299,104 +5297,43 @@ JS_CallFunctionValue(JSContext *cx, JSOb
JSBool ok;
CHECK_REQUEST(cx);
ok = js_InternalCall(cx, obj, fval, argc, argv, rval);
LAST_FRAME_CHECKS(cx, ok);
return ok;
}
-JS_PUBLIC_API(void)
-JS_SetOperationCallback(JSContext *cx, JSOperationCallback callback,
- uint32 operationLimit)
-{
- 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);
+JS_PUBLIC_API(JSOperationCallback)
+JS_SetOperationCallback(JSContext *cx, JSOperationCallback callback)
+{
+#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)
JS_IsRunning(JSContext *cx)
{
/* The use of cx->fp below is safe: if we're on trace, it is skipped. */
VOUCH_DOES_NOT_REQUIRE_STACK();
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -624,25 +624,16 @@ JS_StringToVersion(const char *string);
option supported for the
XUL preprocessor and kindred
beasts. */
#define JSOPTION_XML JS_BIT(6) /* EMCAScript for XML support:
parse <!-- --> as a token,
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
uncaught exceptions from
being converted to error
reports */
#define JSOPTION_RELIMIT JS_BIT(9) /* Throw exception on any
@@ -2259,78 +2250,41 @@ extern JS_PUBLIC_API(JSBool)
JS_CallFunctionName(JSContext *cx, JSObject *obj, const char *name, uintN argc,
jsval *argv, jsval *rval);
extern JS_PUBLIC_API(JSBool)
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);
extern JS_PUBLIC_API(JSBool)
JS_IsConstructing(JSContext *cx);
/*
* Returns true if a script is executing and its current bytecode is a set
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -582,17 +582,17 @@ array_length_setter(JSContext *cx, JSObj
if (OBJ_IS_DENSE_ARRAY(cx, obj)) {
/* Don't reallocate if we're not actually shrinking our slots. */
jsuint oldsize = ARRAY_DENSE_LENGTH(obj);
if (oldsize >= newlen && !ResizeSlots(cx, obj, oldsize, newlen))
return JS_FALSE;
} 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;
}
} while (oldlen != newlen);
} else {
/*
* We are going to remove a lot of indexes in a presumably sparse
* array. So instead of looping through indexes between newlen and
@@ -603,17 +603,17 @@ array_length_setter(JSContext *cx, JSObj
iter = JS_NewPropertyIterator(cx, obj);
if (!iter)
return JS_FALSE;
/* Protect iter against GC in OBJ_DELETE_PROPERTY. */
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;
if (JSVAL_IS_VOID(id))
break;
if (js_IdIsIndex(id, &index) && index - newlen < gap) {
ok = OBJ_DELETE_PROPERTY(cx, obj, id, &junk);
if (!ok)
@@ -1321,17 +1321,17 @@ array_join_sub(JSContext *cx, JSObject *
} else {
sepstr = NULL; /* indicates to use "," as separator */
seplen = 1;
}
}
/* 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;
if (hole ||
(op != TO_SOURCE &&
(JSVAL_IS_VOID(*rval) || JSVAL_IS_NULL(*rval)))) {
str = cx->runtime->emptyString;
} else {
@@ -1374,17 +1374,16 @@ array_join_sub(JSContext *cx, JSObject *
growth > (size_t)-1 / sizeof(jschar)) {
if (chars) {
free(chars);
chars = NULL;
}
goto done;
}
growth *= sizeof(jschar);
- JS_COUNT_OPERATION(cx, JSOW_ALLOCATION);
if (!chars) {
chars = (jschar *) malloc(growth);
if (!chars)
goto done;
} else {
chars = (jschar *) realloc((ochars = chars), growth);
if (!chars) {
free(ochars);
@@ -1495,17 +1494,17 @@ InitArrayElements(JSContext *cx, JSObjec
if (end > (uint32)obj->fslots[JSSLOT_ARRAY_LENGTH])
obj->fslots[JSSLOT_ARRAY_LENGTH] = end;
memcpy(obj->dslots + start, vector, sizeof(jsval) * (end - start));
return JS_TRUE;
}
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;
}
}
return JS_TRUE;
}
static JSBool
@@ -1593,17 +1592,17 @@ array_reverse(JSContext *cx, uintN argc,
obj = JS_THIS_OBJECT(cx, vp);
if (!obj || !js_GetLengthProperty(cx, obj, &len))
return JS_FALSE;
ok = JS_TRUE;
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) &&
SetOrDeleteArrayElement(cx, obj, i, hole2, *vp);
if (!ok)
break;
}
JS_POP_TEMP_ROOT(cx, &tvr);
@@ -1778,17 +1777,17 @@ sort_compare(void *arg, const void *a, c
/**
* array_sort deals with holes and undefs on its own and they should not
* come here.
*/
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;
sp = invokevp;
*sp++ = ca->fval;
*sp++ = JSVAL_NULL;
*sp++ = av;
*sp++ = bv;
@@ -1816,17 +1815,17 @@ sort_compare(void *arg, const void *a, c
static int
sort_compare_strings(void *arg, const void *a, const void *b, int *result)
{
jsval av = *(const jsval *)a, bv = *(const jsval *)b;
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));
return JS_TRUE;
}
/*
* The array_sort function below assumes JSVAL_NULL is zero in order to
@@ -1910,17 +1909,17 @@ array_sort(JSContext *cx, uintN argc, js
* Thus to sort holes and undefs we simply count them, sort the rest
* of elements, append undefs after them and then make holes after
* undefs.
*/
undefs = 0;
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;
/* Clear vec[newlen] before including it in the rooted set. */
vec[newlen] = JSVAL_NULL;
tvr.count = newlen + 1;
ok = GetArrayElement(cx, obj, i, &hole, &vec[newlen]);
if (!ok)
@@ -1996,17 +1995,17 @@ array_sort(JSContext *cx, uintN argc, js
* Rearrange and string-convert the elements of the vector from
* the tail here and, after sorting, move the results back
* starting from the start to prevent overwrite the existing
* elements.
*/
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];
str = js_ValueToString(cx, v);
if (!str) {
ok = JS_FALSE;
goto out;
}
@@ -2075,25 +2074,25 @@ array_sort(JSContext *cx, uintN argc, js
JS_POP_TEMP_ROOT(cx, &tvr);
JS_free(cx, vec);
if (!ok)
return JS_FALSE;
/* 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;
}
}
/* 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;
}
}
*vp = OBJECT_TO_JSVAL(obj);
return JS_TRUE;
}
@@ -2278,17 +2277,17 @@ array_shift(JSContext *cx, uintN argc, j
/* Get the to-be-deleted property's value into vp ASAP. */
if (!GetArrayElement(cx, obj, 0, &hole, vp))
return JS_FALSE;
/* Slide down the array above the first element. */
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)
break;
}
JS_POP_TEMP_ROOT(cx, &tvr);
if (!ok)
return JS_FALSE;
@@ -2316,17 +2315,17 @@ array_unshift(JSContext *cx, uintN argc,
/* Slide up the array to make room for argc at the bottom. */
argv = JS_ARGV(cx, vp);
if (length > 0) {
last = length;
ok = JS_TRUE;
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);
if (!ok)
break;
} while (last != 0);
JS_POP_TEMP_ROOT(cx, &tvr);
if (!ok)
@@ -2413,17 +2412,17 @@ array_splice(JSContext *cx, uintN argc,
}
MUST_FLOW_THROUGH("out");
JS_PUSH_SINGLE_TEMP_ROOT(cx, JSVAL_NULL, &tvr);
/* 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;
/* Copy tvr.u.value to new array unless it's a hole. */
if (!hole) {
ok = SetArrayElement(cx, obj2, last - begin, tvr.u.value);
if (!ok)
@@ -2437,28 +2436,28 @@ array_splice(JSContext *cx, uintN argc,
}
/* Find the direction (up or down) to copy and make way for argv. */
if (argc > count) {
delta = (jsuint)argc - count;
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);
if (!ok)
goto out;
}
length += delta;
} 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);
if (!ok)
goto out;
}
length -= delta;
}
@@ -2525,17 +2524,17 @@ array_concat(JSContext *cx, uintN argc,
length = 0;
}
MUST_FLOW_THROUGH("out");
JS_PUSH_SINGLE_TEMP_ROOT(cx, JSVAL_NULL, &tvr);
/* 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];
if (!JSVAL_IS_PRIMITIVE(v)) {
JSObject *wobj;
aobj = JSVAL_TO_OBJECT(v);
wobj = js_GetWrappedObject(cx, aobj);
@@ -2546,17 +2545,17 @@ array_concat(JSContext *cx, uintN argc,
&tvr.u.value);
if (!ok)
goto out;
alength = ValueIsLength(cx, &tvr.u.value);
ok = !JSVAL_IS_NULL(tvr.u.value);
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)
goto out;
/*
* Per ECMA 262, 15.4.4.4, step 9, ignore non-existent
* properties.
@@ -2652,17 +2651,17 @@ array_slice(JSContext *cx, uintN argc, j
if (!nobj)
return JS_FALSE;
*vp = OBJECT_TO_JSVAL(nobj);
MUST_FLOW_THROUGH("out");
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;
if (!hole) {
ok = SetArrayElement(cx, nobj, slot - begin, tvr.u.value);
if (!ok)
goto out;
}
@@ -2724,17 +2723,17 @@ array_indexOfHelper(JSContext *cx, JSBoo
stop = 0;
direction = -1;
} else {
stop = length - 1;
direction = 1;
}
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;
}
if (!hole && js_StrictlyEqual(cx, *vp, tosearch))
return js_NewNumberInRootedValue(cx, i, vp);
if (i == stop)
goto not_found;
i += direction;
@@ -2873,17 +2872,17 @@ array_extra(JSContext *cx, ArrayExtraMod
if (!elemroot)
return JS_FALSE;
MUST_FLOW_THROUGH("out");
ok = JS_TRUE;
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;
if (hole)
continue;
/*
* Push callable and 'this', then args. We must do this for every
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -75,19 +75,16 @@
/*
* The index for JSThread info, returned by PR_NewThreadPrivateIndex. The
* index value is visible and shared by all threads, but the data associated
* with it is private to each thread.
*/
static PRUintn threadTPIndex;
static JSBool tpIndexInited = JS_FALSE;
-static void
-InitOperationLimit(JSContext *cx);
-
JS_BEGIN_EXTERN_C
JSBool
js_InitThreadPrivateIndex(void (*ptr)(void *))
{
PRStatus status;
if (tpIndexInited)
return JS_TRUE;
@@ -276,17 +273,16 @@ js_NewContext(JSRuntime *rt, size_t stac
* runtime list. After that it can be accessed from another thread via
* js_ContextIterator.
*/
cx = (JSContext *) calloc(1, sizeof *cx);
if (!cx)
return NULL;
cx->runtime = rt;
- js_InitOperationLimit(cx);
cx->debugHooks = &rt->globalDebugHooks;
#if JS_STACK_GROWTH_DIRECTION > 0
cx->stackLimit = (jsuword) -1;
#endif
cx->scriptStackQuota = JS_DEFAULT_SCRIPT_STACK_QUOTA;
#ifdef JS_THREADSAFE
cx->gcLocalFreeLists = (JSGCFreeListSet *) &js_GCEmptyFreeListSet;
js_InitContextThread(cx, thread);
@@ -618,16 +614,31 @@ js_ContextIterator(JSRuntime *rt, JSBool
if (&cx->link == &rt->contextList)
cx = NULL;
*iterp = cx;
if (unlocked)
JS_UNLOCK_GC(rt);
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)
{
const JSResolvingKey *key = (const JSResolvingKey *)ptr;
return ((JSDHashNumber)JS_PTR_TO_UINT32(key->obj) >> JSVAL_TAGBITS) ^ key->id;
}
@@ -1441,41 +1452,47 @@ JS_FRIEND_API(const JSErrorFormatString
js_GetErrorMessage(void *userRef, const char *locale, const uintN errorNumber)
{
if ((errorNumber > 0) && (errorNumber < JSErr_Limit))
return &js_ErrorFormatString[errorNumber];
return NULL;
}
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
/* This is defined in jstracer.cpp in JS_TRACER builds. */
extern JS_FORCES_STACK JSStackFrame *
js_GetTopStackFrame(JSContext *cx)
{
return cx->fp;
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -832,20 +832,20 @@ JS_STATIC_ASSERT(sizeof(JSTempValueUnion
#define JS_PUSH_TEMP_ROOT_SCRIPT(cx,script_,tvr) \
JS_PUSH_TEMP_ROOT_COMMON(cx, script_, tvr, JSTVU_SCRIPT, script)
#define JSRESOLVE_INFER 0xffff /* infer bits from current bytecode */
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;
#if JS_HAS_XML_SUPPORT
/*
* Bit-set formed from binary exponentials of the XML_* tiny-ids defined
* for boolean settings in jsxml.c, plus an XSF_CACHE_VALID bit. Together
@@ -945,22 +945,17 @@ struct JSContext {
char *lastMessage;
#ifdef DEBUG
void *tracefp;
#endif
/* 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. */
uintN interpLevel;
/* Client opaque pointers. */
void *data;
void *data2;
@@ -1206,16 +1201,24 @@ js_ContextFromLinkField(JSCList *link)
/*
* If unlocked, acquire and release rt->gcLock around *iterp update; otherwise
* the caller must be holding rt->gcLock.
*/
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.
*/
extern JSBool
js_StartResolving(JSContext *cx, JSResolvingKey *key, uint32 flag,
JSResolvingEntry **entryp);
extern void
js_StopResolving(JSContext *cx, JSResolvingKey *key, uint32 flag,
@@ -1356,82 +1359,29 @@ extern JSErrorFormatString js_ErrorForma
*/
#if JS_STACK_GROWTH_DIRECTION > 0
# define JS_CHECK_STACK_SIZE(cx, lval) ((jsuword)&(lval) < (cx)->stackLimit)
#else
# define JS_CHECK_STACK_SIZE(cx, lval) ((jsuword)&(lval) > (cx)->stackLimit)
#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)))
-
-/*
- * 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))
+#define JS_CHECK_OPERATION_LIMIT(cx) \
+ (!(cx)->operationCallbackFlag || js_InvokeOperationCallback(cx))
/*
- * 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.
* (Do not access cx->fp directly except in JS_REQUIRES_STACK code.)
*
* Defined in jstracer.cpp if JS_TRACER is defined.
*/
extern JS_FORCES_STACK JSStackFrame *
--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -2473,8 +2473,26 @@ js_DateSetSeconds(JSContext *cx, JSObjec
JS_FRIEND_API(jsdouble)
js_DateGetMsecSinceEpoch(JSContext *cx, JSObject *obj)
{
jsdouble utctime;
if (!GetUTCTime(cx, obj, NULL, &utctime))
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
--- a/js/src/jsdate.h
+++ b/js/src/jsdate.h
@@ -114,11 +114,16 @@ extern JS_FRIEND_API(void)
js_DateSetMinutes(JSContext *cx, JSObject *obj, int minutes);
extern JS_FRIEND_API(void)
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___ */
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -2020,17 +2020,16 @@ testReservedObjects:
/* This is not thread-safe for thread-local allocations. */
METER_IF(flags & GCF_LOCK, rt->gcStats.lockborn++);
#ifdef JS_THREADSAFE
if (gcLocked)
JS_UNLOCK_GC(rt);
#endif
- JS_COUNT_OPERATION(cx, JSOW_ALLOCATION);
return thing;
fail:
#ifdef JS_THREADSAFE
if (gcLocked)
JS_UNLOCK_GC(rt);
#endif
METER(astats->fail++);
@@ -2168,17 +2167,16 @@ RefillDoubleFreeList(JSContext *cx)
JS_ASSERT(index + bit < DOUBLES_PER_ARENA);
JS_ASSERT_IF(index + bit == DOUBLES_PER_ARENA - 1, !list);
cell->link = list;
list = cell;
}
} 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
* it immediately consumes the head of the list.
*/
return list;
}
@@ -3307,17 +3305,17 @@ js_GC(JSContext *cx, JSGCInvocationKind
if (!(gckind & GC_LOCK_HELD))
JS_LOCK_GC(rt);
METER(rt->gcStats.poke++);
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;
@@ -3378,16 +3376,24 @@ js_GC(JSContext *cx, JSGCInvocationKind
JS_UNLOCK_GC(rt);
return;
}
/* No other thread is in GC, so indicate that we're now in GC. */
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);
#else /* !JS_THREADSAFE */
/* Bump gcLevel and return rather than nest; the outer gc will restart. */
rt->gcLevel++;
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -49,16 +49,17 @@
#include "jsarena.h" /* Added by JSIFY */
#include "jsutil.h" /* Added by JSIFY */
#include "jsprf.h"
#include "jsapi.h"
#include "jsarray.h"
#include "jsatom.h"
#include "jsbool.h"
#include "jscntxt.h"
+#include "jsdate.h"
#include "jsversion.h"
#include "jsdbgapi.h"
#include "jsfun.h"
#include "jsgc.h"
#include "jsinterp.h"
#include "jsiter.h"
#include "jslock.h"
#include "jsnum.h"
@@ -2679,17 +2680,17 @@ js_Interpret(JSContext *cx)
#endif /* !JS_TRACER */
/*
* Prepare to call a user-supplied branch handler, and abort the script
* if it returns false.
*/
#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
#define BRANCH(n) \
JS_BEGIN_MACRO \
regs.pc += n; \
if (n <= 0) { \
CHECK_BRANCH(); \
--- a/js/src/jslock.cpp
+++ b/js/src/jslock.cpp
@@ -454,16 +454,48 @@ js_FinishSharingTitle(JSContext *cx, JST
}
}
title->ownercx = NULL; /* NB: set last, after lock init */
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.
* If we claim ownership, return true. Otherwise, we wait for ownercx to be
* set to null (indicating that title is multi-threaded); or if waiting would
* deadlock, we set ownercx to null ourselves via ShareTitle. In any case,
* once ownercx is null we return false.
*/
static JSBool
@@ -556,16 +588,18 @@ ClaimTitle(JSTitle *title, JSContext *cx
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);
+
/*
* We know that some other thread's context owns title, which is now
* linked onto rt->titleSharingTodo, awaiting the end of that other
* thread's request. So it is safe to wait on rt->titleSharingDone.
*/
cx->titleToShare = title;
stat = PR_WaitCondVar(rt->titleSharingDone, PR_INTERVAL_NO_TIMEOUT);
JS_ASSERT(stat != PR_FAILURE);
--- a/js/src/jslock.h
+++ b/js/src/jslock.h
@@ -114,16 +114,17 @@ struct JSTitle {
/*
* Atomic increment and decrement for a reference counter, given jsrefcount *p.
* NB: jsrefcount is int32, aka PRInt32, so that pratom.h functions work.
*/
#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()
#define JS_DESTROY_LOCK(l) PR_DestroyLock(l)
#define JS_ACQUIRE_LOCK(l) PR_Lock(l)
#define JS_RELEASE_LOCK(l) PR_Unlock(l)
#define JS_NEW_CONDVAR(l) PR_NewCondVar(l)
@@ -193,16 +194,19 @@ extern void js_CleanupLocks();
extern void js_TransferTitle(JSContext *, JSTitle *, JSTitle *);
extern JS_FRIEND_API(jsval)
js_GetSlotThreadSafe(JSContext *, JSObject *, uint32);
extern void js_SetSlotThreadSafe(JSContext *, JSObject *, uint32, jsval);
extern void js_InitLock(JSThinLock *);
extern void js_FinishLock(JSThinLock *);
extern void js_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)
#define JS_IS_OBJ_LOCKED(cx,obj) js_IsObjLocked(cx,obj)
#define JS_IS_TITLE_LOCKED(cx,title) js_IsTitleLocked(cx,title)
extern JSBool js_IsRuntimeLocked(JSRuntime *rt);
extern JSBool js_IsObjLocked(JSContext *cx, JSObject *obj);
@@ -232,16 +236,17 @@ extern void js_SetScopeInfo(JSScope *sco
JS_LOCK_RUNTIME_VOID(_rt, e); \
JS_END_MACRO
#else /* !JS_THREADSAFE */
#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
#define JS_DESTROY_LOCK(l) ((void)0)
#define JS_ACQUIRE_LOCK(l) ((void)0)
#define JS_RELEASE_LOCK(l) ((void)0)
#define JS_LOCK(cx, tl) ((void)0)
#define JS_UNLOCK(cx, tl) ((void)0)
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -458,17 +458,17 @@ js_EnterSharpObject(JSContext *cx, JSObj
JSHashTable *table;
JSIdArray *ida;
JSHashNumber hash;
JSHashEntry *he, **hep;
jsatomid sharpid;
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. */
*sp = NULL;
map = &cx->sharpObjectMap;
table = map->table;
if (!table) {
table = JS_NewHashTable(8, js_hash_object, JS_CompareValues,
@@ -3569,17 +3569,16 @@ js_LookupPropertyWithFlags(JSContext *cx
JSResolvingKey key;
JSResolvingEntry *entry;
uint32 generation;
JSNewResolveOp newresolve;
JSBool ok;
/* 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;
for (protoIndex = 0; ; protoIndex++) {
JS_LOCK_OBJ(cx, obj);
scope = OBJ_SCOPE(obj);
if (scope->object == obj) {
sprop = SCOPE_GET_PROPERTY(scope, id);
@@ -3958,17 +3957,16 @@ js_GetPropertyHelper(JSContext *cx, JSOb
int protoIndex;
JSObject *obj2;
JSProperty *prop;
JSScopeProperty *sprop;
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,
&obj2, &prop);
if (protoIndex < 0)
return JS_FALSE;
if (!prop) {
*vp = JSVAL_VOID;
@@ -4059,17 +4057,16 @@ js_SetPropertyHelper(JSContext *cx, JSOb
JSScope *scope;
uintN attrs, flags;
intN shortid;
JSClass *clasp;
JSPropertyOp getter, setter;
/* 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
* to seal before sharing, which is inherently ambiguous.
*/
if (SCOPE_IS_SEALED(OBJ_SCOPE(obj)) && OBJ_SCOPE(obj)->object == obj) {
flags = JSREPORT_ERROR;
goto read_only_error;
@@ -4324,17 +4321,16 @@ js_DeleteProperty(JSContext *cx, JSObjec
JSScopeProperty *sprop;
JSScope *scope;
JSBool ok;
*rval = JSVAL_TRUE;
/* 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;
if (!prop || proto != obj) {
/*
* If the property was found in a native prototype, check whether it's
* shared and permanent. Such a property stands for direct properties
* in all delegating objects, matching ECMA semantics without bloating
--- a/js/src/jspubtd.h
+++ b/js/src/jspubtd.h
@@ -608,16 +608,21 @@ typedef enum JSContextOp {
* callback may perform its own cleanup and must always
* return true.
* Any other value For future compatibility the callback must do nothing
* and return true in this case.
*/
typedef JSBool
(* JSContextCallback)(JSContext *cx, uintN contextOp);
+#ifndef JS_THREADSAFE
+typedef void
+(* JSHeartbeatCallback)(JSRuntime *rt);
+#endif
+
typedef enum JSGCStatus {
JSGC_BEGIN,
JSGC_END,
JSGC_MARK_END,
JSGC_FINALIZE_END
} JSGCStatus;
typedef JSBool
--- a/js/src/jsregexp.cpp
+++ b/js/src/jsregexp.cpp
@@ -2627,21 +2627,19 @@ PushBackTrackState(REGlobalData *gData,
ptrdiff_t btsize = gData->backTrackStackSize;
ptrdiff_t btincr = ((char *)result + sz) -
((char *)gData->backTrackStack + btsize);
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);
if (!gData->backTrackStack) {
js_ReportOutOfScriptQuota(gData->cx);
gData->ok = JS_FALSE;
return NULL;
}
@@ -3808,17 +3806,17 @@ ExecuteREBytecode(REGlobalData *gData, R
/*
* If the match failed and there's a backtrack option, take it.
* Otherwise this is a complete and utter failure.
*/
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;
}
/* Potentially detect explosive regex here. */
gData->backTrackCount++;
if (gData->backTrackLimit &&
gData->backTrackCount >= gData->backTrackLimit) {
--- a/js/src/jsscope.cpp
+++ b/js/src/jsscope.cpp
@@ -1022,18 +1022,16 @@ js_AddScopeProperty(JSContext *cx, JSSco
/*
* Search for id in order to claim its entry, allocating a property tree
* node if one doesn't already exist for our parameters.
*/
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)) {
if (scope->removedCount >= size >> 2) {
METER(compresses);
change = 0;
} else {
METER(grows);
--- a/js/src/jstracer.cpp
+++ b/js/src/jstracer.cpp
@@ -1248,26 +1248,22 @@ TraceRecorder::TraceRecorder(JSContext*
*(ti->globalSlots),
ti->nStackTypes);
}
/* read into registers all values on the stack and all globals we know so far */
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));
+ /*
+ * We poll the operation callback request flag. It is updated asynchronously whenever
+ * the callback is to be invoked.
+ */
+ LIns* x = lir->insLoadi(cx_ins, offsetof(JSContext, operationCallbackFlag));
+ 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
is what we expect it to be. */
if (_anchor && _anchor->exitType == NESTED_EXIT) {
LIns* nested_ins = addName(lir->insLoad(LIR_ldp, lirbuf->state,
offsetof(InterpState, lastTreeExitGuard)),
"lastTreeExitGuard");
@@ -4220,16 +4216,20 @@ js_MonitorLoopEdge(JSContext* cx, uintN&
/* Make sure the shape of the global object still matches (this might flush the JIT cache). */
JSObject* globalObj = JS_GetGlobalForObject(cx, cx->fp->scopeChain);
uint32 globalShape = -1;
SlotList* globalSlots = NULL;
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 &&
oracle.getHits(pc)+1 < HOTLOOP) {
oracle.hit(pc);
return false;
}
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -51,27 +51,30 @@
#include "jsarena.h"
#include "jsutil.h"
#include "jsprf.h"
#include "jsapi.h"
#include "jsarray.h"
#include "jsatom.h"
#include "jsbuiltins.h"
#include "jscntxt.h"
+#include "jsdate.h"
#include "jsdbgapi.h"
#include "jsemit.h"
#include "jsfun.h"
#include "jsgc.h"
#include "jslock.h"
#include "jsnum.h"
#include "jsobj.h"
#include "jsparse.h"
#include "jsscope.h"
#include "jsscript.h"
+#include "prmjtime.h"
+
#ifdef LIVECONNECT
#include "jsjava.h"
#endif
#ifdef JSDEBUGGER
#include "jsdebug.h"
#ifdef JSDEBUGGER_JAVA_UI
#include "jsdjava.h"
@@ -100,26 +103,62 @@ typedef enum JSShellExitCode {
size_t gStackChunkSize = 8192;
/* Assume that we can not use more than 5e5 bytes of C stack by default. */
static size_t gMaxStackSize = 500000;
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;
JSBool gQuitting = JS_FALSE;
FILE *gErrFile = NULL;
FILE *gOutFile = NULL;
static JSBool reportWarnings = JS_TRUE;
@@ -206,129 +245,53 @@ GetLine(FILE *file, const char * prompt)
return buffer;
free(buffer);
return NULL;
}
/*
* 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;
}
static inline JSShellContextData *
GetContextData(JSContext *cx)
{
JSShellContextData *data = (JSShellContextData *) JS_GetContextPrivate(cx);
JS_ASSERT(data);
return data;
}
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;
}
static void
SetContextOptions(JSContext *cx)
{
jsuword stackLimit;
@@ -341,18 +304,17 @@ SetContextOptions(JSContext *cx)
#if JS_STACK_GROWTH_DIRECTION > 0
stackLimit = gStackBase + gMaxStackSize;
#else
stackLimit = gStackBase - gMaxStackSize;
#endif
}
JS_SetThreadStackLimit(cx, stackLimit);
JS_SetScriptStackQuota(cx, gScriptStackQuota);
- SetTimeoutValue(cx, gOperationTimeout);
- JS_SetOperationCallbackFunction(cx, ShellOperationCallback);
+ JS_SetOperationCallback(cx, ShellOperationCallback);
}
static void
Process(JSContext *cx, JSObject *obj, char *filename, JSBool forceTTY)
{
JSBool ok, hitEOF;
JSScript *script;
jsval result;
@@ -710,18 +672,17 @@ extern void js_InitJITStatsClass(JSConte
obj = gobj;
}
break;
case 't':
if (++i == argc)
return usage();
- gOperationTimeout = atof(argv[i]);
- if (!SetTimeoutValue(cx, gOperationTimeout))
+ if (!SetTimeoutValue(cx, atof(argv[i])))
return JS_FALSE;
break;
case 'c':
/* set stack chunk size */
gStackChunkSize = atoi(argv[++i]);
break;
@@ -2755,17 +2716,19 @@ EvalInContext(JSContext *cx, JSObject *o
JSBool lazy, ok;
jsval v;
JSStackFrame *fp;
sobj = NULL;
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;
}
JS_SetOptions(scx, JS_GetOptions(cx));
#ifdef JS_THREADSAFE
JS_BeginRequest(scx);
@@ -2810,17 +2773,19 @@ EvalInContext(JSContext *cx, JSObject *o
JS_ReportOutOfMemory(cx);
}
}
out:
#ifdef JS_THREADSAFE
JS_EndRequest(scx);
#endif
- JS_DestroyContextNoGC(scx);
+ WITH_LOCKED_CONTEXT_LIST(
+ JS_DestroyContextNoGC(scx)
+ );
return ok;
}
static int32 JS_FASTCALL
ShapeOf_tn(JSObject *obj)
{
if (!obj)
return 0;
@@ -2835,16 +2800,21 @@ ShapeOf(JSContext *cx, uintN argc, jsval
jsval v = JS_ARGV(cx, vp)[0];
if (!JSVAL_IS_OBJECT(v)) {
JS_ReportError(cx, "shapeOf: object expected");
return JS_FALSE;
}
return JS_NewNumberValue(cx, ShapeOf_tn(JSVAL_TO_OBJECT(v)), vp);
}
+static void
+Callback(JSRuntime *rt)
+{
+}
+
#ifdef JS_THREADSAFE
static JSBool
Sleep_fn(JSContext *cx, uintN argc, jsval *vp)
{
jsdouble t_secs;
PRUint32 t_ticks;
jsrefcount rc;
@@ -2890,72 +2860,43 @@ struct ScatterThreadData {
jsval fn;
};
static void
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
RunScatterThread(void *arg)
{
ScatterThreadData *td;
ScatterStatus st;
JSContext *cx;
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);
PR_Unlock(td->shared->lock);
if (st == SCATTER_CANCEL)
return;
- /* We are go. */
+ /* We are good to go. */
JS_SetContextThread(cx);
JS_SetThreadStackLimit(cx, 0);
JS_BeginRequest(cx);
DoScatteredWork(cx, td);
JS_EndRequest(cx);
JS_ClearContextThread(cx);
}
@@ -3038,17 +2979,20 @@ Scatter(JSContext *cx, uintN argc, jsval
JS_RemoveRoot(cx, &sd.threads[i].fn);
free(sd.threads);
sd.threads = NULL;
goto fail;
}
}
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));
JS_ClearContextThread(newcx);
sd.threads[i].cx = newcx;
}
for (i = 1; i < n; i++) {
@@ -3096,17 +3040,19 @@ out:
if (sd.threads) {
JSContext *acx;
for (i = 0; i < n; i++) {
JS_RemoveRoot(cx, &sd.threads[i].fn);
acx = sd.threads[i].cx;
if (acx) {
JS_SetContextThread(acx);
- JS_DestroyContext(acx);
+ WITH_LOCKED_CONTEXT_LIST(
+ JS_DestroyContext(acx)
+ );
}
}
free(sd.threads);
}
if (sd.results) {
for (i = 0; i < n; i++)
JS_RemoveRoot(cx, &sd.results[i]);
free(sd.results);
@@ -3118,228 +3064,203 @@ out:
return ok;
fail:
ok = JS_FALSE;
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)
{
JSRuntime *rt = (JSRuntime *) arg;
JS_LOCK_GC(rt);
while (gWatchdogThread) {
- PRIntervalTime now = PR_IntervalNow();
- PRIntervalTime sleepDuration = PR_INTERVAL_NO_TIMEOUT;
- JSContext *iter = NULL;
- JSContext *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;
+ JSContext *acx = NULL;
+
+ while ((acx = js_NextActiveContext(rt, acx)))
+ JS_TriggerOperationCallback(acx);
+
#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;
-
+
+ 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) {
/*
- * 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.
+ * The watchdog thread is running, tell it to terminate waking it up
+ * if necessary and wait until it signals that it done.
*/
- 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;
+ 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
SetTimeoutValue(JSContext *cx, jsdouble t)
{
/* NB: The next condition also filter out NaNs. */
if (!(t <= 3600.0)) {
JS_ReportError(cx, "Excessive timeout value");
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;
- }
+ gOperationLimit = (t > 0) ? JSInt64(t*1000) : 0;
+
+ if (!StartWatchdog(cx->runtime)) {
+ JS_ReportError(cx, "failed to create the watchdog");
+ return JS_FALSE;
}
- JS_UNLOCK_GC(cx->runtime);
-
-#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);
- }
-#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");
return JS_FALSE;
}
jsdouble t;
if (!JS_ValueToNumber(cx, JS_ARGV(cx, vp)[0], &t))
return JS_FALSE;
*vp = JSVAL_VOID;
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)))
#ifdef XP_UNIX
#include <fcntl.h>
#include <sys/stat.h>
@@ -3524,16 +3445,17 @@ static JSFunctionSpec shell_functions[]
JS_FS("arrayInfo", js_ArrayInfo, 1,0,0),
#endif
#ifdef JS_THREADSAFE
JS_FN("sleep", Sleep_fn, 1,0),
JS_FN("scatter", Scatter, 1,0),
#endif
JS_FS("snarf", Snarf, 0,0,0),
JS_FN("timeout", Timeout, 1,0),
+ JS_FN("elapsed", Elapsed, 0,0),
JS_FS_END
};
static const char shell_help_header[] =
"Command Description\n"
"======= ===========\n";
static const char *const shell_help_messages[] = {
@@ -3615,16 +3537,17 @@ static const char *const shell_help_mess
#ifdef JS_THREADSAFE
"sleep(dt) Sleep for dt seconds",
"scatter(fns) Call functions concurrently (ignoring errors)",
#endif
"snarf(filename) Read filename into returned string",
"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. */
JS_STATIC_ASSERT(JS_ARRAY_LENGTH(shell_help_messages) + 1 ==
JS_ARRAY_LENGTH(shell_functions));
#ifdef DEBUG
static void
@@ -4383,30 +4306,30 @@ Evaluate(JSContext *cx, JSObject *obj, u
return ok;
}
#endif /* NARCISSUS */
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);
JS_SetErrorReporter(cx, my_ErrorReporter);
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;
}
default:
break;
}
@@ -4457,21 +4380,25 @@ main(int argc, char **argv, char **envp)
rt = JS_NewRuntime(64L * 1024L * 1024L);
if (!rt)
return 1;
#ifdef JS_THREADSAFE
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;
#ifdef JS_THREADSAFE
JS_BeginRequest(cx);
#endif
glob = JS_NewObject(cx, &global_class, NULL, NULL);
@@ -4566,29 +4493,18 @@ main(int argc, char **argv, char **envp)
JSD_DebuggerOff(jsdc);
}
#endif /* JSDEBUGGER */
#ifdef JS_THREADSAFE
JS_EndRequest(cx);
#endif
- 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
+ WITH_LOCKED_CONTEXT_LIST(
+ JS_DestroyContext(cx)
+ );
+
+ StopWatchdog(rt);
JS_DestroyRuntime(rt);
JS_ShutDown();
return result;
}
--- a/js/src/xpconnect/src/xpccomponents.cpp
+++ b/js/src/xpconnect/src/xpccomponents.cpp
@@ -3399,34 +3399,32 @@ ContextHolder::ContextHolder(JSContext *
{
if(mJSContext)
{
JS_SetOptions(mJSContext,
JSOPTION_DONT_REPORT_UNCAUGHT |
JSOPTION_PRIVATE_IS_NSISUPPORTS);
JS_SetGlobalObject(mJSContext, aSandbox);
JS_SetContextPrivate(mJSContext, this);
- JS_SetOperationCallback(mJSContext, ContextHolderOperationCallback,
- JS_GetOperationLimit(aOuterCx));
+ JS_SetOperationCallback(mJSContext, ContextHolderOperationCallback);
}
}
JSBool
ContextHolder::ContextHolderOperationCallback(JSContext *cx)
{
ContextHolder* thisObject =
static_cast<ContextHolder*>(JS_GetContextPrivate(cx));
NS_ASSERTION(thisObject, "How did that happen?");
JSContext *origCx = thisObject->mOrigCx;
JSOperationCallback callback = JS_GetOperationCallback(origCx);
JSBool ok = JS_TRUE;
if(callback)
ok = callback(origCx);
- JS_SetOperationLimit(cx, JS_GetOperationLimit(origCx));
return ok;
}
/***************************************************************************/
/* void evalInSandbox(in AString source, in nativeobj sandbox); */
NS_IMETHODIMP
nsXPCComponents_Utils::EvalInSandbox(const nsAString &source)
--- a/js/src/xpconnect/src/xpcjsruntime.cpp
+++ b/js/src/xpconnect/src/xpcjsruntime.cpp
@@ -788,16 +788,59 @@ JSBool XPCJSRuntime::GCCallback(JSContex
default:
break;
}
}
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
static JSDHashOperator
DEBUG_WrapperChecker(JSDHashTable *table, JSDHashEntryHdr *hdr,
uint32 number, void *arg)
{
XPCWrappedNative* wrapper = (XPCWrappedNative*)((JSDHashEntryStub*)hdr)->key;
@@ -836,16 +879,34 @@ void XPCJSRuntime::SystemIsBeingShutDown
if(mDetachedWrappedNativeProtoMap)
mDetachedWrappedNativeProtoMap->
Enumerate(DetachedWrappedNativeProtoShutdownMarker, 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
JSContext* iter = nsnull;
int count = 0;
while(JS_ContextIterator(mJSRuntime, &iter))
count ++;
if(count)
@@ -1009,17 +1070,19 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect*
mMapLock(XPCAutoLock::NewLock("XPCJSRuntime::mMapLock")),
mThreadRunningGC(nsnull),
mWrappedJSToReleaseArray(),
mNativesToReleaseArray(),
mDoingFinalization(JS_FALSE),
mVariantRoots(nsnull),
mWrappedJSRoots(nsnull),
mObjectHolderRoots(nsnull),
- mUnrootedGlobalCount(0)
+ mUnrootedGlobalCount(0),
+ mWatchdogWakeup(nsnull),
+ mWatchdogThread(nsnull)
{
#ifdef XPC_CHECK_WRAPPERS_AT_SHUTDOWN
DEBUG_WrappedNativeHashtable =
JS_NewDHashTable(JS_DHashGetStubOps(), nsnull,
sizeof(JSDHashEntryStub), 128);
#endif
DOM_InitInterfaces();
@@ -1052,27 +1115,34 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect*
// finite threshold (0xffffffff is infinity for uint32 parameters).
// This leaves the maximum-JS_malloc-bytes threshold still in effect
// to cause period, and we hope hygienic, last-ditch GCs from within
// the GC's allocator.
JS_SetGCParameter(mJSRuntime, JSGC_MAX_BYTES, 0xffffffff);
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,
sizeof(ObjectHolder), 512))
mJSHolders.ops = nsnull;
// Install a JavaScript 'debugger' keyword handler in debug builds only
#ifdef DEBUG
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
XPCJSRuntime*
XPCJSRuntime::newXPCJSRuntime(nsXPConnect* aXPConnect)
{
NS_PRECONDITION(aXPConnect,"bad param");
@@ -1084,17 +1154,18 @@ XPCJSRuntime::newXPCJSRuntime(nsXPConnec
self->GetWrappedJSClassMap() &&
self->GetIID2NativeInterfaceMap() &&
self->GetClassInfo2NativeSetMap() &&
self->GetNativeSetMap() &&
self->GetThisTranslatorMap() &&
self->GetNativeScriptableSharedMap() &&
self->GetDyingWrappedNativeProtoMap() &&
self->GetExplicitNativeWrapperMap() &&
- self->GetMapLock())
+ self->GetMapLock() &&
+ self->mWatchdogThread)
{
return self;
}
delete self;
return nsnull;
}
JSBool
@@ -1254,35 +1325,35 @@ XPCJSRuntime::DebugDump(PRInt16 depth)
}
/***************************************************************************/
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)
{
NS_ASSERTION(mNext->mSelfp == listHead, "Must be list start");
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;
#endif
}
--- a/js/src/xpconnect/src/xpcprivate.h
+++ b/js/src/xpconnect/src/xpcprivate.h
@@ -756,17 +756,21 @@ private:
JSDHashTable* DEBUG_WrappedNativeHashtable;
public:
#endif
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];
nsXPConnect* mXPConnect;
JSRuntime* mJSRuntime;
JSObject2WrappedJSMap* mWrappedJSMap;
IID2WrappedJSClassMap* mWrappedJSClassMap;
@@ -783,16 +787,18 @@ private:
nsVoidArray mWrappedJSToReleaseArray;
nsVoidArray mNativesToReleaseArray;
JSBool mDoingFinalization;
XPCRootSetElem *mVariantRoots;
XPCRootSetElem *mWrappedJSRoots;
XPCRootSetElem *mObjectHolderRoots;
JSDHashTable mJSHolders;
uintN mUnrootedGlobalCount;
+ PRCondVar *mWatchdogWakeup;
+ PRThread *mWatchdogThread;
};
/***************************************************************************/
/***************************************************************************/
// XPCContext is mostly a dumb class to hold JSContext specific data and
// maps that let us find wrappers created for the given JSContext.
// no virtuals