mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-11 20:35:50 +00:00
Substitute operation counting with a watchdog thread (477187, r=brendan/mrbkap/jst, sr=brendan/jst).
This commit is contained in:
parent
6ee38d80d3
commit
86e19efcc6
@ -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,
|
||||
|
@ -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!");
|
||||
|
@ -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;
|
||||
|
@ -951,7 +951,6 @@ nsDOMWorker::nsDOMWorker(nsDOMWorker* aParent,
|
||||
nsIXPConnectWrappedNative* aParentWN)
|
||||
: mParent(aParent),
|
||||
mParentWN(aParentWN),
|
||||
mCallbackCount(0),
|
||||
mLock(nsnull),
|
||||
mInnerScope(nsnull),
|
||||
mGlobal(NULL),
|
||||
|
@ -217,8 +217,6 @@ private:
|
||||
nsDOMWorker* mParent;
|
||||
nsCOMPtr<nsIXPConnectWrappedNative> mParentWN;
|
||||
|
||||
PRUint32 mCallbackCount;
|
||||
|
||||
PRLock* mLock;
|
||||
|
||||
nsRefPtr<nsDOMWorkerMessageHandler> mInnerHandler;
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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___ */
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)) {
|
||||
|
@ -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 &&
|
||||
|
@ -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();
|
||||
|
@ -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)) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
/***************************************************************************/
|
||||
|
Loading…
Reference in New Issue
Block a user