зеркало из https://github.com/mozilla/gecko-dev.git
Bug 650411 - assert in debug/release builds that JSRuntime is only used in a single-threaded manner (r=jorendorff,sr=dmandelin)
--HG-- extra : rebase_source : f18561fff556332789e05623ab0cec2e7faacf78
This commit is contained in:
Родитель
e2f4183574
Коммит
d542a26a63
|
@ -5420,6 +5420,8 @@ CClosure::ClosureStub(ffi_cif* cif, void* result, void** args, void* userData)
|
|||
// Assert that we're on the thread we were created from.
|
||||
JS_ASSERT(cinfo->cxThread == JS_GetContextThread(cx));
|
||||
|
||||
JS_AbortIfWrongThread(JS_GetRuntime(cx));
|
||||
|
||||
JSAutoRequest ar(cx);
|
||||
|
||||
JSAutoEnterCompartment ac;
|
||||
|
|
|
@ -84,8 +84,6 @@ CPPSRCS = \
|
|||
testScriptObject.cpp \
|
||||
testSetProperty.cpp \
|
||||
testStringBuffer.cpp \
|
||||
testThreadGC.cpp \
|
||||
testThreads.cpp \
|
||||
testTrap.cpp \
|
||||
testUTF8.cpp \
|
||||
testValueABI.cpp \
|
||||
|
|
|
@ -1,195 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sw=4 et tw=99:
|
||||
*/
|
||||
|
||||
#ifdef JS_THREADSAFE
|
||||
|
||||
#include "tests.h"
|
||||
#include "prthread.h"
|
||||
|
||||
#include "jscntxt.h"
|
||||
|
||||
/*
|
||||
* We test that if a GC callback cancels the GC on a child thread the GC can
|
||||
* still proceed on the main thread even if the child thread continue to
|
||||
* run uninterrupted.
|
||||
*/
|
||||
|
||||
struct SharedData {
|
||||
enum ChildState {
|
||||
CHILD_STARTING,
|
||||
CHILD_RUNNING,
|
||||
CHILD_DONE,
|
||||
CHILD_ERROR
|
||||
};
|
||||
|
||||
JSRuntime *const runtime;
|
||||
PRThread *const mainThread;
|
||||
PRLock *const lock;
|
||||
PRCondVar *const signal;
|
||||
ChildState childState;
|
||||
bool childShouldStop;
|
||||
JSContext *childContext;
|
||||
|
||||
SharedData(JSRuntime *rt, bool *ok)
|
||||
: runtime(rt),
|
||||
mainThread(PR_GetCurrentThread()),
|
||||
lock(PR_NewLock()),
|
||||
signal(lock ? PR_NewCondVar(lock) : NULL),
|
||||
childState(CHILD_STARTING),
|
||||
childShouldStop(false),
|
||||
childContext(NULL)
|
||||
{
|
||||
JS_ASSERT(!*ok);
|
||||
*ok = !!signal;
|
||||
}
|
||||
|
||||
~SharedData() {
|
||||
if (signal)
|
||||
PR_DestroyCondVar(signal);
|
||||
if (lock)
|
||||
PR_DestroyLock(lock);
|
||||
}
|
||||
};
|
||||
|
||||
static SharedData *shared;
|
||||
|
||||
static JSBool
|
||||
CancelNonMainThreadGCCallback(JSContext *cx, JSGCStatus status)
|
||||
{
|
||||
return status != JSGC_BEGIN || PR_GetCurrentThread() == shared->mainThread;
|
||||
}
|
||||
|
||||
static JSBool
|
||||
StopChildOperationCallback(JSContext *cx)
|
||||
{
|
||||
bool shouldStop;
|
||||
PR_Lock(shared->lock);
|
||||
shouldStop = shared->childShouldStop;
|
||||
PR_Unlock(shared->lock);
|
||||
return !shouldStop;
|
||||
}
|
||||
|
||||
static JSBool
|
||||
NotifyMainThreadAboutBusyLoop(JSContext *cx, uintN argc, jsval *vp)
|
||||
{
|
||||
PR_Lock(shared->lock);
|
||||
JS_ASSERT(shared->childState == SharedData::CHILD_STARTING);
|
||||
shared->childState = SharedData::CHILD_RUNNING;
|
||||
shared->childContext = cx;
|
||||
PR_NotifyCondVar(shared->signal);
|
||||
PR_Unlock(shared->lock);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
ChildThreadMain(void *arg)
|
||||
{
|
||||
JS_ASSERT(!arg);
|
||||
bool error = true;
|
||||
JSContext *cx = JS_NewContext(shared->runtime, 8192);
|
||||
if (cx) {
|
||||
JS_SetOperationCallback(cx, StopChildOperationCallback);
|
||||
JSAutoRequest ar(cx);
|
||||
JSObject *global = JS_NewCompartmentAndGlobalObject(cx, JSAPITest::basicGlobalClass(),
|
||||
NULL);
|
||||
if (global) {
|
||||
JS_SetGlobalObject(cx, global);
|
||||
if (JS_InitStandardClasses(cx, global) &&
|
||||
JS_DefineFunction(cx, global, "notify", NotifyMainThreadAboutBusyLoop, 0, 0)) {
|
||||
|
||||
jsval rval;
|
||||
static const char code[] = "var i = 0; notify(); for (var i = 0; ; ++i);";
|
||||
JSBool ok = JS_EvaluateScript(cx, global, code, strlen(code),
|
||||
__FILE__, __LINE__, &rval);
|
||||
if (!ok && !JS_IsExceptionPending(cx)) {
|
||||
/* Evaluate should only return via the callback cancellation. */
|
||||
error = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PR_Lock(shared->lock);
|
||||
shared->childState = error ? SharedData::CHILD_DONE : SharedData::CHILD_ERROR;
|
||||
shared->childContext = NULL;
|
||||
PR_NotifyCondVar(shared->signal);
|
||||
PR_Unlock(shared->lock);
|
||||
|
||||
if (cx)
|
||||
JS_DestroyContextNoGC(cx);
|
||||
}
|
||||
|
||||
BEGIN_TEST(testThreadGC_bug590533)
|
||||
{
|
||||
/*
|
||||
* Test the child thread busy running while the current thread calls
|
||||
* the GC both with JSRuntime->gcIsNeeded set and unset.
|
||||
*/
|
||||
bool ok = TestChildThread(true);
|
||||
CHECK(ok);
|
||||
ok = TestChildThread(false);
|
||||
CHECK(ok);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool TestChildThread(bool setGCIsNeeded)
|
||||
{
|
||||
bool ok = false;
|
||||
shared = new SharedData(rt, &ok);
|
||||
CHECK(ok);
|
||||
|
||||
JSGCCallback oldGCCallback = JS_SetGCCallback(cx, CancelNonMainThreadGCCallback);
|
||||
|
||||
PRThread *thread =
|
||||
PR_CreateThread(PR_USER_THREAD, ChildThreadMain, NULL,
|
||||
PR_PRIORITY_NORMAL, PR_LOCAL_THREAD, PR_JOINABLE_THREAD, 0);
|
||||
if (!thread)
|
||||
return false;
|
||||
|
||||
PR_Lock(shared->lock);
|
||||
while (shared->childState == SharedData::CHILD_STARTING)
|
||||
PR_WaitCondVar(shared->signal, PR_INTERVAL_NO_TIMEOUT);
|
||||
JS_ASSERT(shared->childState != SharedData::CHILD_DONE);
|
||||
ok = (shared->childState == SharedData::CHILD_RUNNING);
|
||||
PR_Unlock(shared->lock);
|
||||
|
||||
CHECK(ok);
|
||||
|
||||
if (setGCIsNeeded) {
|
||||
/*
|
||||
* Use JS internal API to set the GC trigger flag after we know
|
||||
* that the child is in a request and is about to run an infinite
|
||||
* loop. Then run the GC with JSRuntime->gcIsNeeded flag set.
|
||||
*/
|
||||
js::AutoLockGC lock(rt);
|
||||
js::TriggerGC(rt, js::gcstats::PUBLIC_API);
|
||||
}
|
||||
|
||||
JS_GC(cx);
|
||||
|
||||
PR_Lock(shared->lock);
|
||||
shared->childShouldStop = true;
|
||||
while (shared->childState == SharedData::CHILD_RUNNING) {
|
||||
JS_TriggerOperationCallback(shared->childContext);
|
||||
PR_WaitCondVar(shared->signal, PR_INTERVAL_NO_TIMEOUT);
|
||||
}
|
||||
JS_ASSERT(shared->childState != SharedData::CHILD_STARTING);
|
||||
ok = (shared->childState == SharedData::CHILD_DONE);
|
||||
PR_Unlock(shared->lock);
|
||||
|
||||
JS_SetGCCallback(cx, oldGCCallback);
|
||||
|
||||
PR_JoinThread(thread);
|
||||
|
||||
delete shared;
|
||||
shared = NULL;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
END_TEST(testThreadGC_bug590533)
|
||||
|
||||
#endif
|
|
@ -1,174 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sw=4 et tw=99:
|
||||
*/
|
||||
|
||||
#ifdef JS_THREADSAFE
|
||||
|
||||
#include "tests.h"
|
||||
#include "prthread.h"
|
||||
|
||||
struct ThreadData {
|
||||
JSRuntime *rt;
|
||||
JSObject *obj;
|
||||
const char *code;
|
||||
bool ok;
|
||||
};
|
||||
|
||||
BEGIN_TEST(testThreads_bug561444)
|
||||
{
|
||||
const char *code = "<a><b/></a>.b.@c = '';";
|
||||
EXEC(code);
|
||||
|
||||
jsrefcount rc = JS_SuspendRequest(cx);
|
||||
{
|
||||
ThreadData data = {rt, global, code, false};
|
||||
PRThread *thread =
|
||||
PR_CreateThread(PR_USER_THREAD, threadMain, &data,
|
||||
PR_PRIORITY_NORMAL, PR_LOCAL_THREAD, PR_JOINABLE_THREAD, 0);
|
||||
CHECK(thread);
|
||||
PR_JoinThread(thread);
|
||||
CHECK(data.ok);
|
||||
}
|
||||
JS_ResumeRequest(cx, rc);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void threadMain(void *arg) {
|
||||
ThreadData *d = (ThreadData *) arg;
|
||||
|
||||
JSContext *cx = JS_NewContext(d->rt, 8192);
|
||||
if (!cx)
|
||||
return;
|
||||
JS_BeginRequest(cx);
|
||||
{
|
||||
JSAutoEnterCompartment ac;
|
||||
jsval v;
|
||||
d->ok = ac.enter(cx, d->obj) &&
|
||||
JS_EvaluateScript(cx, d->obj, d->code, strlen(d->code), __FILE__, __LINE__,
|
||||
&v);
|
||||
}
|
||||
JS_DestroyContext(cx);
|
||||
}
|
||||
END_TEST(testThreads_bug561444)
|
||||
|
||||
const PRUint32 NATIVE_STACK_SIZE = 64 * 1024;
|
||||
const PRUint32 NATIVE_STACK_HEADROOM = 8 * 1024;
|
||||
|
||||
template <class T>
|
||||
class Repeat {
|
||||
size_t n;
|
||||
const T &t;
|
||||
|
||||
public:
|
||||
Repeat(size_t n, const T &t) : n(n), t(t) {}
|
||||
|
||||
bool operator()() const {
|
||||
for (size_t i = 0; i < n; i++)
|
||||
if (!t())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template <class T> Repeat<T> repeat(size_t n, const T &t) { return Repeat<T>(n, t); }
|
||||
|
||||
/* Class of callable that does something in n parallel threads. */
|
||||
template <class T>
|
||||
class Parallel {
|
||||
size_t n;
|
||||
const T &t;
|
||||
|
||||
struct pair { const Parallel *self; bool ok; };
|
||||
|
||||
static void threadMain(void *arg) {
|
||||
pair *p = (pair *) arg;
|
||||
if (!p->self->t())
|
||||
p->ok = false;
|
||||
}
|
||||
|
||||
public:
|
||||
Parallel(size_t n, const T &t) : n(n), t(t) {}
|
||||
|
||||
bool operator()() const {
|
||||
pair p = {this, true};
|
||||
|
||||
PRThread **thread = new PRThread *[n];
|
||||
if (!thread)
|
||||
return false;
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < n; i++) {
|
||||
thread[i] = PR_CreateThread(PR_USER_THREAD, threadMain, &p, PR_PRIORITY_NORMAL,
|
||||
PR_LOCAL_THREAD, PR_JOINABLE_THREAD, NATIVE_STACK_SIZE);
|
||||
if (thread[i] == NULL) {
|
||||
p.ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (i--)
|
||||
PR_JoinThread(thread[i]);
|
||||
|
||||
delete[] thread;
|
||||
return p.ok;
|
||||
}
|
||||
};
|
||||
|
||||
template <class T> Parallel<T> parallel(size_t n, const T &t) { return Parallel<T>(n, t); }
|
||||
|
||||
/* Class of callable that creates a compartment and runs some code in it. */
|
||||
class eval {
|
||||
JSRuntime *rt;
|
||||
const char *code;
|
||||
|
||||
public:
|
||||
eval(JSRuntime *rt, const char *code) : rt(rt), code(code) {}
|
||||
|
||||
bool operator()() const {
|
||||
JSContext *cx = JS_NewContext(rt, 8192);
|
||||
if (!cx)
|
||||
return false;
|
||||
|
||||
JS_SetNativeStackQuota(cx, NATIVE_STACK_SIZE - NATIVE_STACK_HEADROOM);
|
||||
bool ok = false;
|
||||
{
|
||||
JSAutoRequest ar(cx);
|
||||
JSObject *global =
|
||||
JS_NewCompartmentAndGlobalObject(cx, JSAPITest::basicGlobalClass(), NULL);
|
||||
if (global) {
|
||||
JS_SetGlobalObject(cx, global);
|
||||
jsval rval;
|
||||
ok = JS_InitStandardClasses(cx, global) &&
|
||||
JS_EvaluateScript(cx, global, code, strlen(code), "", 0, &rval);
|
||||
}
|
||||
}
|
||||
JS_DestroyContextMaybeGC(cx);
|
||||
return ok;
|
||||
}
|
||||
};
|
||||
|
||||
BEGIN_TEST(testThreads_bug604782)
|
||||
{
|
||||
jsrefcount rc = JS_SuspendRequest(cx);
|
||||
bool ok = repeat(20, parallel(3, eval(rt, "for(i=0;i<1000;i++);")))();
|
||||
JS_ResumeRequest(cx, rc);
|
||||
CHECK(ok);
|
||||
return true;
|
||||
}
|
||||
END_TEST(testThreads_bug604782)
|
||||
|
||||
BEGIN_TEST(testThreads_bug609103)
|
||||
{
|
||||
const char *code =
|
||||
"var x = {};\n"
|
||||
"for (var i = 0; i < 10000; i++)\n"
|
||||
" x = {next: x};\n";
|
||||
|
||||
jsrefcount rc = JS_SuspendRequest(cx);
|
||||
bool ok = parallel(2, eval(rt, code))();
|
||||
JS_ResumeRequest(cx, rc);
|
||||
CHECK(ok);
|
||||
return true;
|
||||
}
|
||||
END_TEST(testThreads_bug609103)
|
||||
|
||||
#endif
|
|
@ -594,6 +594,7 @@ JS_GetTypeName(JSContext *cx, JSType type)
|
|||
JS_PUBLIC_API(JSBool)
|
||||
JS_StrictlyEqual(JSContext *cx, jsval v1, jsval v2, JSBool *equal)
|
||||
{
|
||||
CHECK_REQUEST(cx);
|
||||
assertSameCompartment(cx, v1, v2);
|
||||
return StrictlyEqual(cx, v1, v2, equal);
|
||||
}
|
||||
|
@ -601,6 +602,7 @@ JS_StrictlyEqual(JSContext *cx, jsval v1, jsval v2, JSBool *equal)
|
|||
JS_PUBLIC_API(JSBool)
|
||||
JS_LooselyEqual(JSContext *cx, jsval v1, jsval v2, JSBool *equal)
|
||||
{
|
||||
CHECK_REQUEST(cx);
|
||||
assertSameCompartment(cx, v1, v2);
|
||||
return LooselyEqual(cx, v1, v2, equal);
|
||||
}
|
||||
|
@ -608,6 +610,7 @@ JS_LooselyEqual(JSContext *cx, jsval v1, jsval v2, JSBool *equal)
|
|||
JS_PUBLIC_API(JSBool)
|
||||
JS_SameValue(JSContext *cx, jsval v1, jsval v2, JSBool *same)
|
||||
{
|
||||
CHECK_REQUEST(cx);
|
||||
assertSameCompartment(cx, v1, v2);
|
||||
return SameValue(cx, v1, v2, same);
|
||||
}
|
||||
|
@ -735,6 +738,10 @@ JSRuntime::JSRuntime()
|
|||
bool
|
||||
JSRuntime::init(uint32 maxbytes)
|
||||
{
|
||||
#ifdef JS_THREADSAFE
|
||||
ownerThread_ = js_CurrentThreadId();
|
||||
#endif
|
||||
|
||||
#ifdef JS_METHODJIT_SPEW
|
||||
JMCheckLogging();
|
||||
#endif
|
||||
|
@ -824,6 +831,28 @@ JSRuntime::~JSRuntime()
|
|||
#endif
|
||||
}
|
||||
|
||||
#ifdef JS_THREADSAFE
|
||||
void
|
||||
JSRuntime::setOwnerThread()
|
||||
{
|
||||
JS_ASSERT(ownerThread_ == (void *)-1);
|
||||
ownerThread_ = js_CurrentThreadId();
|
||||
}
|
||||
|
||||
void
|
||||
JSRuntime::clearOwnerThread()
|
||||
{
|
||||
JS_ASSERT(onOwnerThread());
|
||||
ownerThread_ = (void *)-1;
|
||||
}
|
||||
|
||||
JS_FRIEND_API(bool)
|
||||
JSRuntime::onOwnerThread() const
|
||||
{
|
||||
return ownerThread_ == js_CurrentThreadId();
|
||||
}
|
||||
#endif
|
||||
|
||||
JS_PUBLIC_API(JSRuntime *)
|
||||
JS_NewRuntime(uint32 maxbytes)
|
||||
{
|
||||
|
@ -2087,12 +2116,14 @@ JS_ComputeThis(JSContext *cx, jsval *vp)
|
|||
JS_PUBLIC_API(void *)
|
||||
JS_malloc(JSContext *cx, size_t nbytes)
|
||||
{
|
||||
CHECK_REQUEST(cx);
|
||||
return cx->malloc_(nbytes);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(void *)
|
||||
JS_realloc(JSContext *cx, void *p, size_t nbytes)
|
||||
{
|
||||
CHECK_REQUEST(cx);
|
||||
return cx->realloc_(p, nbytes);
|
||||
}
|
||||
|
||||
|
@ -5454,12 +5485,16 @@ JS_GetFlatStringChars(JSFlatString *str)
|
|||
JS_PUBLIC_API(JSBool)
|
||||
JS_CompareStrings(JSContext *cx, JSString *str1, JSString *str2, int32 *result)
|
||||
{
|
||||
CHECK_REQUEST(cx);
|
||||
|
||||
return CompareStrings(cx, str1, str2, result);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(JSBool)
|
||||
JS_StringEqualsAscii(JSContext *cx, JSString *str, const char *asciiBytes, JSBool *match)
|
||||
{
|
||||
CHECK_REQUEST(cx);
|
||||
|
||||
JSLinearString *linearStr = str->ensureLinear(cx);
|
||||
if (!linearStr)
|
||||
return false;
|
||||
|
@ -5644,6 +5679,8 @@ JS_ReadStructuredClone(JSContext *cx, const uint64 *buf, size_t nbytes,
|
|||
const JSStructuredCloneCallbacks *optionalCallbacks,
|
||||
void *closure)
|
||||
{
|
||||
CHECK_REQUEST(cx);
|
||||
|
||||
if (version > JS_STRUCTURED_CLONE_VERSION) {
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_CLONE_VERSION);
|
||||
return false;
|
||||
|
@ -5660,6 +5697,8 @@ JS_WriteStructuredClone(JSContext *cx, jsval v, uint64 **bufp, size_t *nbytesp,
|
|||
const JSStructuredCloneCallbacks *optionalCallbacks,
|
||||
void *closure)
|
||||
{
|
||||
CHECK_REQUEST(cx);
|
||||
|
||||
const JSStructuredCloneCallbacks *callbacks =
|
||||
optionalCallbacks ?
|
||||
optionalCallbacks :
|
||||
|
@ -5672,6 +5711,8 @@ JS_StructuredClone(JSContext *cx, jsval v, jsval *vp,
|
|||
const JSStructuredCloneCallbacks *optionalCallbacks,
|
||||
void *closure)
|
||||
{
|
||||
CHECK_REQUEST(cx);
|
||||
|
||||
const JSStructuredCloneCallbacks *callbacks =
|
||||
optionalCallbacks ?
|
||||
optionalCallbacks :
|
||||
|
@ -6218,6 +6259,8 @@ JS_GetContextThread(JSContext *cx)
|
|||
JS_PUBLIC_API(jsword)
|
||||
JS_SetContextThread(JSContext *cx)
|
||||
{
|
||||
JS_AbortIfWrongThread(cx->runtime);
|
||||
|
||||
#ifdef JS_THREADSAFE
|
||||
JS_ASSERT(!cx->outstandingRequests);
|
||||
if (cx->thread()) {
|
||||
|
@ -6235,9 +6278,36 @@ JS_SetContextThread(JSContext *cx)
|
|||
return 0;
|
||||
}
|
||||
|
||||
extern JS_PUBLIC_API(void)
|
||||
JS_ClearRuntimeThread(JSRuntime *rt)
|
||||
{
|
||||
#ifdef JS_THREADSAFE
|
||||
rt->clearOwnerThread();
|
||||
#endif
|
||||
}
|
||||
|
||||
extern JS_PUBLIC_API(void)
|
||||
JS_SetRuntimeThread(JSRuntime *rt)
|
||||
{
|
||||
#ifdef JS_THREADSAFE
|
||||
rt->setOwnerThread();
|
||||
#endif
|
||||
}
|
||||
|
||||
extern JS_NEVER_INLINE JS_PUBLIC_API(void)
|
||||
JS_AbortIfWrongThread(JSRuntime *rt)
|
||||
{
|
||||
#ifdef JS_THREADSAFE
|
||||
if (!rt->onOwnerThread())
|
||||
JS_Assert("rt->onOwnerThread()", __FILE__, __LINE__);
|
||||
#endif
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(jsword)
|
||||
JS_ClearContextThread(JSContext *cx)
|
||||
{
|
||||
JS_AbortIfWrongThread(cx->runtime);
|
||||
|
||||
#ifdef JS_THREADSAFE
|
||||
/*
|
||||
* cx must have exited all requests it entered and, if cx is associated
|
||||
|
|
|
@ -4809,6 +4809,51 @@ JS_SetContextThread(JSContext *cx);
|
|||
extern JS_PUBLIC_API(jsword)
|
||||
JS_ClearContextThread(JSContext *cx);
|
||||
|
||||
/*
|
||||
* A JS runtime always has an "owner thread". The owner thread is set when the
|
||||
* runtime is created (to the current thread) and practically all entry points
|
||||
* into the JS engine check that a runtime (or anything contained in the
|
||||
* runtime: context, compartment, object, etc) is only touched by its owner
|
||||
* thread. Embeddings may check this invariant outside the JS engine by calling
|
||||
* JS_AbortIfWrongThread (which will abort if not on the owner thread, even for
|
||||
* non-debug builds).
|
||||
*
|
||||
* It is possible to "move" a runtime between threads. This is accomplished by
|
||||
* calling JS_ClearRuntimeThread on a runtime's owner thread and then calling
|
||||
* JS_SetRuntimeThread on the new owner thread. The runtime must not be
|
||||
* accessed between JS_ClearRuntimeThread and JS_SetRuntimeThread. Also, the
|
||||
* caller is responsible for synchronizing the calls to Set/Clear.
|
||||
*/
|
||||
|
||||
extern JS_PUBLIC_API(void)
|
||||
JS_AbortIfWrongThread(JSRuntime *rt);
|
||||
|
||||
extern JS_PUBLIC_API(void)
|
||||
JS_ClearRuntimeThread(JSRuntime *rt);
|
||||
|
||||
extern JS_PUBLIC_API(void)
|
||||
JS_SetRuntimeThread(JSRuntime *rt);
|
||||
|
||||
#ifdef __cplusplus
|
||||
JS_END_EXTERN_C
|
||||
|
||||
class JSAutoSetRuntimeThread
|
||||
{
|
||||
JSRuntime *runtime;
|
||||
|
||||
public:
|
||||
JSAutoSetRuntimeThread(JSRuntime *runtime) : runtime(runtime) {
|
||||
JS_SetRuntimeThread(runtime);
|
||||
}
|
||||
|
||||
~JSAutoSetRuntimeThread() {
|
||||
JS_ClearRuntimeThread(runtime);
|
||||
}
|
||||
};
|
||||
|
||||
JS_BEGIN_EXTERN_C
|
||||
#endif
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
/*
|
||||
|
|
|
@ -318,6 +318,8 @@ js_PurgeThreads(JSContext *cx)
|
|||
JSContext *
|
||||
js_NewContext(JSRuntime *rt, size_t stackChunkSize)
|
||||
{
|
||||
JS_AbortIfWrongThread(rt);
|
||||
|
||||
/*
|
||||
* We need to initialize the new context fully before adding it to the
|
||||
* runtime list. After that it can be accessed from another thread via
|
||||
|
@ -416,13 +418,14 @@ js_NewContext(JSRuntime *rt, size_t stackChunkSize)
|
|||
void
|
||||
js_DestroyContext(JSContext *cx, JSDestroyContextMode mode)
|
||||
{
|
||||
JSRuntime *rt;
|
||||
JSRuntime *rt = cx->runtime;
|
||||
JS_AbortIfWrongThread(rt);
|
||||
|
||||
JSContextCallback cxCallback;
|
||||
JSBool last;
|
||||
|
||||
JS_ASSERT(!cx->enumerators);
|
||||
|
||||
rt = cx->runtime;
|
||||
#ifdef JS_THREADSAFE
|
||||
/*
|
||||
* For API compatibility we allow to destroy contexts without a thread in
|
||||
|
|
|
@ -347,7 +347,8 @@ typedef js::Vector<JSCompartment *, 0, js::SystemAllocPolicy> CompartmentVector;
|
|||
|
||||
}
|
||||
|
||||
struct JSRuntime {
|
||||
struct JSRuntime
|
||||
{
|
||||
/* Default compartment. */
|
||||
JSCompartment *atomsCompartment;
|
||||
#ifdef JS_THREADSAFE
|
||||
|
@ -360,6 +361,20 @@ struct JSRuntime {
|
|||
/* Runtime state, synchronized by the stateChange/gcLock condvar/lock. */
|
||||
JSRuntimeState state;
|
||||
|
||||
/* See comment for JS_AbortIfWrongThread in jsapi.h. */
|
||||
#ifdef JS_THREADSAFE
|
||||
public:
|
||||
void clearOwnerThread();
|
||||
void setOwnerThread();
|
||||
JS_FRIEND_API(bool) onOwnerThread() const;
|
||||
private:
|
||||
void *ownerThread_;
|
||||
public:
|
||||
#else
|
||||
public:
|
||||
bool onOwnerThread() const { return true; }
|
||||
#endif
|
||||
|
||||
/* Context create/destroy callback. */
|
||||
JSContextCallback cxCallback;
|
||||
|
||||
|
@ -1320,11 +1335,11 @@ class AutoCheckRequestDepth {
|
|||
# define CHECK_REQUEST(cx) \
|
||||
JS_ASSERT((cx)->thread()); \
|
||||
JS_ASSERT((cx)->thread()->data.requestDepth || (cx)->thread() == (cx)->runtime->gcThread); \
|
||||
JS_ASSERT(cx->runtime->onOwnerThread()); \
|
||||
AutoCheckRequestDepth _autoCheckRequestDepth(cx);
|
||||
|
||||
#else
|
||||
# define CHECK_REQUEST(cx) ((void) 0)
|
||||
# define CHECK_REQUEST_THREAD(cx) ((void) 0)
|
||||
#endif
|
||||
|
||||
struct AutoResolving {
|
||||
|
|
|
@ -2113,6 +2113,7 @@ void
|
|||
MaybeGC(JSContext *cx)
|
||||
{
|
||||
JSRuntime *rt = cx->runtime;
|
||||
JS_ASSERT(rt->onOwnerThread());
|
||||
|
||||
if (rt->gcZeal()) {
|
||||
js_GC(cx, NULL, GC_NORMAL, gcstats::MAYBEGC);
|
||||
|
@ -2933,6 +2934,7 @@ void
|
|||
js_GC(JSContext *cx, JSCompartment *comp, JSGCInvocationKind gckind, gcstats::Reason reason)
|
||||
{
|
||||
JSRuntime *rt = cx->runtime;
|
||||
JS_AbortIfWrongThread(rt);
|
||||
|
||||
/*
|
||||
* Don't collect garbage if the runtime isn't up, and cx is not the last
|
||||
|
@ -3163,6 +3165,8 @@ JSCompartment *
|
|||
NewCompartment(JSContext *cx, JSPrincipals *principals)
|
||||
{
|
||||
JSRuntime *rt = cx->runtime;
|
||||
JS_AbortIfWrongThread(rt);
|
||||
|
||||
JSCompartment *compartment = cx->new_<JSCompartment>(rt);
|
||||
if (compartment && compartment->init(cx)) {
|
||||
// Any compartment with the trusted principals -- and there can be
|
||||
|
|
|
@ -1118,7 +1118,7 @@ Quit(JSContext *cx, uintN argc, jsval *vp)
|
|||
gQuitting = JS_TRUE;
|
||||
#ifdef JS_THREADSAFE
|
||||
if (gWorkerThreadPool)
|
||||
js::workers::terminateAll(JS_GetRuntime(cx), gWorkerThreadPool);
|
||||
js::workers::terminateAll(gWorkerThreadPool);
|
||||
#endif
|
||||
return JS_FALSE;
|
||||
}
|
||||
|
@ -3276,245 +3276,6 @@ Sleep_fn(JSContext *cx, uintN argc, jsval *vp)
|
|||
return !gCanceled;
|
||||
}
|
||||
|
||||
typedef struct ScatterThreadData ScatterThreadData;
|
||||
typedef struct ScatterData ScatterData;
|
||||
|
||||
typedef enum ScatterStatus {
|
||||
SCATTER_WAIT,
|
||||
SCATTER_GO,
|
||||
SCATTER_CANCEL
|
||||
} ScatterStatus;
|
||||
|
||||
struct ScatterData {
|
||||
ScatterThreadData *threads;
|
||||
jsval *results;
|
||||
PRLock *lock;
|
||||
PRCondVar *cvar;
|
||||
ScatterStatus status;
|
||||
};
|
||||
|
||||
struct ScatterThreadData {
|
||||
jsint index;
|
||||
ScatterData *shared;
|
||||
PRThread *thr;
|
||||
JSContext *cx;
|
||||
jsval fn;
|
||||
};
|
||||
|
||||
static void
|
||||
DoScatteredWork(JSContext *cx, ScatterThreadData *td)
|
||||
{
|
||||
jsval *rval = &td->shared->results[td->index];
|
||||
|
||||
if (!JS_CallFunctionValue(cx, NULL, td->fn, 0, NULL, rval)) {
|
||||
*rval = JSVAL_VOID;
|
||||
JS_GetPendingException(cx, rval);
|
||||
JS_ClearPendingException(cx);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
RunScatterThread(void *arg)
|
||||
{
|
||||
int stackDummy;
|
||||
ScatterThreadData *td;
|
||||
ScatterStatus st;
|
||||
JSContext *cx;
|
||||
|
||||
if (PR_FAILURE == PR_SetThreadPrivate(gStackBaseThreadIndex, &stackDummy))
|
||||
return;
|
||||
|
||||
td = (ScatterThreadData *)arg;
|
||||
cx = td->cx;
|
||||
|
||||
/* 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 good to go. */
|
||||
JS_SetContextThread(cx);
|
||||
JS_SetNativeStackQuota(cx, gMaxStackSize);
|
||||
JS_BeginRequest(cx);
|
||||
DoScatteredWork(cx, td);
|
||||
JS_EndRequest(cx);
|
||||
JS_ClearContextThread(cx);
|
||||
}
|
||||
|
||||
/*
|
||||
* scatter(fnArray) - Call each function in `fnArray` without arguments, each
|
||||
* in a different thread. When all threads have finished, return an array: the
|
||||
* return values. Errors are not propagated; if any of the function calls
|
||||
* fails, the corresponding element in the results array gets the exception
|
||||
* object, if any, else (undefined).
|
||||
*/
|
||||
static JSBool
|
||||
Scatter(JSContext *cx, uintN argc, jsval *vp)
|
||||
{
|
||||
jsuint i;
|
||||
jsuint n; /* number of threads */
|
||||
JSObject *inArr;
|
||||
JSObject *arr;
|
||||
JSObject *global;
|
||||
ScatterData sd;
|
||||
JSBool ok;
|
||||
|
||||
sd.lock = NULL;
|
||||
sd.cvar = NULL;
|
||||
sd.results = NULL;
|
||||
sd.threads = NULL;
|
||||
sd.status = SCATTER_WAIT;
|
||||
|
||||
if (argc == 0 || JSVAL_IS_PRIMITIVE(JS_ARGV(cx, vp)[0])) {
|
||||
JS_ReportError(cx, "the first argument must be an object");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
inArr = JSVAL_TO_OBJECT(JS_ARGV(cx, vp)[0]);
|
||||
ok = JS_GetArrayLength(cx, inArr, &n);
|
||||
if (!ok)
|
||||
goto out;
|
||||
if (n == 0)
|
||||
goto success;
|
||||
|
||||
sd.lock = PR_NewLock();
|
||||
if (!sd.lock)
|
||||
goto fail;
|
||||
|
||||
sd.cvar = PR_NewCondVar(sd.lock);
|
||||
if (!sd.cvar)
|
||||
goto fail;
|
||||
|
||||
sd.results = (jsval *) malloc(n * sizeof(jsval));
|
||||
if (!sd.results)
|
||||
goto fail;
|
||||
for (i = 0; i < n; i++) {
|
||||
sd.results[i] = JSVAL_VOID;
|
||||
ok = JS_AddValueRoot(cx, &sd.results[i]);
|
||||
if (!ok) {
|
||||
while (i-- > 0)
|
||||
JS_RemoveValueRoot(cx, &sd.results[i]);
|
||||
free(sd.results);
|
||||
sd.results = NULL;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
sd.threads = (ScatterThreadData *) malloc(n * sizeof(ScatterThreadData));
|
||||
if (!sd.threads)
|
||||
goto fail;
|
||||
for (i = 0; i < n; i++) {
|
||||
sd.threads[i].index = i;
|
||||
sd.threads[i].shared = &sd;
|
||||
sd.threads[i].thr = NULL;
|
||||
sd.threads[i].cx = NULL;
|
||||
sd.threads[i].fn = JSVAL_NULL;
|
||||
|
||||
ok = JS_AddValueRoot(cx, &sd.threads[i].fn);
|
||||
if (ok && !JS_GetElement(cx, inArr, i, &sd.threads[i].fn)) {
|
||||
JS_RemoveValueRoot(cx, &sd.threads[i].fn);
|
||||
ok = JS_FALSE;
|
||||
}
|
||||
if (!ok) {
|
||||
while (i-- > 0)
|
||||
JS_RemoveValueRoot(cx, &sd.threads[i].fn);
|
||||
free(sd.threads);
|
||||
sd.threads = NULL;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
global = JS_GetGlobalObject(cx);
|
||||
for (i = 1; i < n; i++) {
|
||||
JSContext *newcx = NewContext(JS_GetRuntime(cx));
|
||||
if (!newcx)
|
||||
goto fail;
|
||||
|
||||
{
|
||||
JSAutoRequest req(newcx);
|
||||
JS_SetGlobalObject(newcx, global);
|
||||
}
|
||||
JS_ClearContextThread(newcx);
|
||||
sd.threads[i].cx = newcx;
|
||||
}
|
||||
|
||||
for (i = 1; i < n; i++) {
|
||||
PRThread *t = PR_CreateThread(PR_USER_THREAD,
|
||||
RunScatterThread,
|
||||
&sd.threads[i],
|
||||
PR_PRIORITY_NORMAL,
|
||||
PR_GLOBAL_THREAD,
|
||||
PR_JOINABLE_THREAD,
|
||||
0);
|
||||
if (!t) {
|
||||
/* Failed to start thread. */
|
||||
PR_Lock(sd.lock);
|
||||
sd.status = SCATTER_CANCEL;
|
||||
PR_NotifyAllCondVar(sd.cvar);
|
||||
PR_Unlock(sd.lock);
|
||||
while (i-- > 1)
|
||||
PR_JoinThread(sd.threads[i].thr);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
sd.threads[i].thr = t;
|
||||
}
|
||||
PR_Lock(sd.lock);
|
||||
sd.status = SCATTER_GO;
|
||||
PR_NotifyAllCondVar(sd.cvar);
|
||||
PR_Unlock(sd.lock);
|
||||
|
||||
DoScatteredWork(cx, &sd.threads[0]);
|
||||
|
||||
{
|
||||
JSAutoSuspendRequest suspended(cx);
|
||||
for (i = 1; i < n; i++) {
|
||||
PR_JoinThread(sd.threads[i].thr);
|
||||
}
|
||||
}
|
||||
|
||||
success:
|
||||
arr = JS_NewArrayObject(cx, n, sd.results);
|
||||
if (!arr)
|
||||
goto fail;
|
||||
*vp = OBJECT_TO_JSVAL(arr);
|
||||
ok = JS_TRUE;
|
||||
|
||||
out:
|
||||
if (sd.threads) {
|
||||
JSContext *acx;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
JS_RemoveValueRoot(cx, &sd.threads[i].fn);
|
||||
acx = sd.threads[i].cx;
|
||||
if (acx) {
|
||||
JS_SetContextThread(acx);
|
||||
DestroyContext(acx, true);
|
||||
}
|
||||
}
|
||||
free(sd.threads);
|
||||
}
|
||||
if (sd.results) {
|
||||
for (i = 0; i < n; i++)
|
||||
JS_RemoveValueRoot(cx, &sd.results[i]);
|
||||
free(sd.results);
|
||||
}
|
||||
if (sd.cvar)
|
||||
PR_DestroyCondVar(sd.cvar);
|
||||
if (sd.lock)
|
||||
PR_DestroyLock(sd.lock);
|
||||
|
||||
return ok;
|
||||
|
||||
fail:
|
||||
ok = JS_FALSE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
static bool
|
||||
InitWatchdog(JSRuntime *rt)
|
||||
{
|
||||
|
@ -3699,7 +3460,7 @@ CancelExecution(JSRuntime *rt)
|
|||
gExitCode = EXITCODE_TIMEOUT;
|
||||
#ifdef JS_THREADSAFE
|
||||
if (gWorkerThreadPool)
|
||||
js::workers::terminateAll(rt, gWorkerThreadPool);
|
||||
js::workers::terminateAll(gWorkerThreadPool);
|
||||
#endif
|
||||
JS_TriggerAllOperationCallbacks(rt);
|
||||
|
||||
|
@ -4256,7 +4017,6 @@ static JSFunctionSpec shell_functions[] = {
|
|||
#endif
|
||||
#ifdef JS_THREADSAFE
|
||||
JS_FN("sleep", Sleep_fn, 1,0),
|
||||
JS_FN("scatter", Scatter, 1,0),
|
||||
#endif
|
||||
JS_FN("snarf", Snarf, 0,0),
|
||||
JS_FN("read", Snarf, 0,0),
|
||||
|
@ -4383,7 +4143,6 @@ static const char *const shell_help_messages[] = {
|
|||
#endif
|
||||
#ifdef JS_THREADSAFE
|
||||
"sleep(dt) Sleep for dt seconds",
|
||||
"scatter(fns) Call functions concurrently (ignoring errors)",
|
||||
#endif
|
||||
"snarf(filename) Read filename into returned string",
|
||||
"read(filename) Synonym for snarf",
|
||||
|
|
|
@ -169,6 +169,7 @@ class WorkerParent {
|
|||
}
|
||||
|
||||
void disposeChildren();
|
||||
void notifyTerminating();
|
||||
};
|
||||
|
||||
template <class T>
|
||||
|
@ -527,11 +528,12 @@ class ThreadPool
|
|||
return ok;
|
||||
}
|
||||
|
||||
void terminateAll(JSRuntime *rt) {
|
||||
void terminateAll() {
|
||||
// See comment about JS_ATOMIC_SET in the implementation of
|
||||
// JS_TriggerOperationCallback.
|
||||
JS_ATOMIC_SET(&terminating, 1);
|
||||
JS_TriggerAllOperationCallbacks(rt);
|
||||
if (mq)
|
||||
mq->notifyTerminating();
|
||||
}
|
||||
|
||||
/* This context is used only to free memory. */
|
||||
|
@ -592,6 +594,7 @@ class Worker : public WorkerParent
|
|||
ThreadPool *threadPool;
|
||||
WorkerParent *parent;
|
||||
JSObject *object; // Worker object exposed to parent
|
||||
JSRuntime *runtime;
|
||||
JSContext *context;
|
||||
JSLock *lock;
|
||||
Queue<Event *, SystemAllocPolicy> events; // owning pointers to pending events
|
||||
|
@ -602,7 +605,7 @@ class Worker : public WorkerParent
|
|||
static JSClass jsWorkerClass;
|
||||
|
||||
Worker()
|
||||
: threadPool(NULL), parent(NULL), object(NULL),
|
||||
: threadPool(NULL), parent(NULL), object(NULL), runtime(NULL),
|
||||
context(NULL), lock(NULL), current(NULL), terminated(false), terminateFlag(0) {}
|
||||
|
||||
bool init(JSContext *parentcx, WorkerParent *parent, JSObject *obj) {
|
||||
|
@ -615,13 +618,24 @@ class Worker : public WorkerParent
|
|||
this->object = obj;
|
||||
lock = JS_NEW_LOCK();
|
||||
return lock &&
|
||||
createRuntime(parentcx) &&
|
||||
createContext(parentcx, parent) &&
|
||||
JS_SetPrivate(parentcx, obj, this);
|
||||
}
|
||||
|
||||
bool createRuntime(JSContext *parentcx) {
|
||||
runtime = JS_NewRuntime(1L * 1024L * 1024L);
|
||||
if (!runtime) {
|
||||
JS_ReportOutOfMemory(parentcx);
|
||||
return false;
|
||||
}
|
||||
JS_ClearRuntimeThread(runtime);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool createContext(JSContext *parentcx, WorkerParent *parent) {
|
||||
JSRuntime *rt = JS_GetRuntime(parentcx);
|
||||
context = JS_NewContext(rt, 8192);
|
||||
JSAutoSetRuntimeThread guard(runtime);
|
||||
context = JS_NewContext(runtime, 8192);
|
||||
if (!context)
|
||||
return false;
|
||||
|
||||
|
@ -753,11 +767,17 @@ class Worker : public WorkerParent
|
|||
JS_DESTROY_LOCK(lock);
|
||||
lock = NULL;
|
||||
}
|
||||
if (runtime)
|
||||
JS_SetRuntimeThread(runtime);
|
||||
if (context) {
|
||||
JS_SetContextThread(context);
|
||||
JS_DestroyContextNoGC(context);
|
||||
context = NULL;
|
||||
}
|
||||
if (runtime) {
|
||||
JS_DestroyRuntime(runtime);
|
||||
runtime = NULL;
|
||||
}
|
||||
object = NULL;
|
||||
|
||||
// Do not call parent->removeChild(). This is called either from
|
||||
|
@ -796,6 +816,11 @@ class Worker : public WorkerParent
|
|||
JS_TriggerOperationCallback(context);
|
||||
}
|
||||
|
||||
void notifyTerminating() {
|
||||
setTerminateFlag();
|
||||
WorkerParent::notifyTerminating();
|
||||
}
|
||||
|
||||
void processOneEvent();
|
||||
|
||||
/* Trace method to be called from C++. */
|
||||
|
@ -976,6 +1001,14 @@ WorkerParent::disposeChildren()
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
WorkerParent::notifyTerminating()
|
||||
{
|
||||
AutoLock hold(getLock());
|
||||
for (ChildSet::Range r = children.all(); !r.empty(); r.popFront())
|
||||
r.front()->notifyTerminating();
|
||||
}
|
||||
|
||||
bool
|
||||
MainQueue::shouldStop()
|
||||
{
|
||||
|
@ -1109,7 +1142,8 @@ Worker::processOneEvent()
|
|||
|
||||
Event::Result result;
|
||||
{
|
||||
JSAutoRequest req(context);
|
||||
JSAutoSetRuntimeThread asrt(JS_GetRuntime(context));
|
||||
JSAutoRequest ar(context);
|
||||
result = event->process(context);
|
||||
}
|
||||
|
||||
|
@ -1125,7 +1159,8 @@ Worker::processOneEvent()
|
|||
}
|
||||
}
|
||||
if (result == Event::fail && !checkTermination()) {
|
||||
JSAutoRequest req(context);
|
||||
JSAutoSetRuntimeThread asrt(JS_GetRuntime(context));
|
||||
JSAutoRequest ar(context);
|
||||
Event *err = ErrorEvent::create(context, this);
|
||||
if (err && !parent->post(err)) {
|
||||
JS_ReportOutOfMemory(context);
|
||||
|
@ -1259,9 +1294,9 @@ js::workers::init(JSContext *cx, WorkerHooks *hooks, JSObject *global, JSObject
|
|||
}
|
||||
|
||||
void
|
||||
js::workers::terminateAll(JSRuntime *rt, ThreadPool *tp)
|
||||
js::workers::terminateAll(ThreadPool *tp)
|
||||
{
|
||||
tp->terminateAll(rt);
|
||||
tp->terminateAll();
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -61,13 +61,13 @@ namespace js {
|
|||
};
|
||||
|
||||
/*
|
||||
* Initialize workers. This defines the Worker constructor on global.
|
||||
* Requires request. rootp must point to a GC root.
|
||||
*
|
||||
* On success, *rootp receives a pointer to an object, and init returns
|
||||
* Initialize workers. This defines the Worker constructor on global.
|
||||
* Requires request. rootp must point to a GC root.
|
||||
*
|
||||
* On success, *rootp receives a pointer to an object, and init returns
|
||||
* a non-null value. The caller must keep the object rooted and must
|
||||
* pass it to js::workers::finish later.
|
||||
*/
|
||||
*/
|
||||
ThreadPool *init(JSContext *cx, WorkerHooks *hooks, JSObject *global, JSObject **rootp);
|
||||
|
||||
/* Asynchronously signal for all workers to terminate.
|
||||
|
@ -75,7 +75,7 @@ namespace js {
|
|||
* Call this before calling finish() to shut down without waiting for
|
||||
* all messages to be proceesed.
|
||||
*/
|
||||
void terminateAll(JSRuntime *rt, ThreadPool *tp);
|
||||
void terminateAll(ThreadPool *tp);
|
||||
|
||||
/*
|
||||
* Finish running any workers, shut down the thread pool, and free all
|
||||
|
|
|
@ -9,10 +9,10 @@ if (typeof Worker != 'undefined') {
|
|||
JSTest.waitForExplicitFinish();
|
||||
var w = Worker(workerDir + "worker-fib-child.js");
|
||||
w.onmessage = function (event) {
|
||||
reportCompare("55", event.data, "worker-fib");
|
||||
reportCompare("21", event.data, "worker-fib");
|
||||
JSTest.testFinished();
|
||||
};
|
||||
w.postMessage("10\t" + workerDir); // 0 1 1 2 3 5 8 13 21 34 55
|
||||
w.postMessage("8\t" + workerDir); // 0 1 1 2 3 5 8 13 21
|
||||
} else {
|
||||
reportCompare(0, 0, "Test skipped. Shell workers required.");
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ def set_limits():
|
|||
try:
|
||||
import resource
|
||||
GB = 2**30
|
||||
resource.setrlimit(resource.RLIMIT_AS, (1*GB, 1*GB))
|
||||
resource.setrlimit(resource.RLIMIT_AS, (2*GB, 2*GB))
|
||||
except:
|
||||
return
|
||||
|
||||
|
|
|
@ -751,6 +751,8 @@ mozJSComponentLoader::GlobalForLocation(nsILocalFile *aComponentFile,
|
|||
JSPrincipals* jsPrincipals = nsnull;
|
||||
JSCLContextHelper cx(this);
|
||||
|
||||
JS_AbortIfWrongThread(JS_GetRuntime(cx));
|
||||
|
||||
// preserve caller's compartment
|
||||
js::AutoPreserveCompartment pc(cx);
|
||||
|
||||
|
|
|
@ -475,6 +475,7 @@ XPCCallContext::GetCalleeClassInfo(nsIClassInfo * *aCalleeClassInfo)
|
|||
NS_IMETHODIMP
|
||||
XPCCallContext::GetJSContext(JSContext * *aJSContext)
|
||||
{
|
||||
JS_AbortIfWrongThread(JS_GetRuntime(mJSContext));
|
||||
*aJSContext = mJSContext;
|
||||
return NS_OK;
|
||||
}
|
||||
|
|
|
@ -3519,6 +3519,8 @@ xpc_EvalInSandbox(JSContext *cx, JSObject *sandbox, const nsAString& source,
|
|||
const char *filename, PRInt32 lineNo,
|
||||
JSVersion jsVersion, bool returnStringOnly, jsval *rval)
|
||||
{
|
||||
JS_AbortIfWrongThread(JS_GetRuntime(cx));
|
||||
|
||||
#ifdef DEBUG
|
||||
// NB: The "unsafe" unwrap here is OK because we must be called from chrome.
|
||||
{
|
||||
|
|
|
@ -513,6 +513,34 @@ nsXPConnect::BeginCycleCollection(nsCycleCollectionTraversalCallback &cb,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
nsXPConnect::NotifyLeaveMainThread()
|
||||
{
|
||||
NS_ABORT_IF_FALSE(NS_IsMainThread(), "Off main thread");
|
||||
JS_ClearRuntimeThread(mRuntime->GetJSRuntime());
|
||||
}
|
||||
|
||||
void
|
||||
nsXPConnect::NotifyEnterCycleCollectionThread()
|
||||
{
|
||||
NS_ABORT_IF_FALSE(!NS_IsMainThread(), "On main thread");
|
||||
JS_SetRuntimeThread(mRuntime->GetJSRuntime());
|
||||
}
|
||||
|
||||
void
|
||||
nsXPConnect::NotifyLeaveCycleCollectionThread()
|
||||
{
|
||||
NS_ABORT_IF_FALSE(!NS_IsMainThread(), "On main thread");
|
||||
JS_ClearRuntimeThread(mRuntime->GetJSRuntime());
|
||||
}
|
||||
|
||||
void
|
||||
nsXPConnect::NotifyEnterMainThread()
|
||||
{
|
||||
NS_ABORT_IF_FALSE(NS_IsMainThread(), "Off main thread");
|
||||
JS_SetRuntimeThread(mRuntime->GetJSRuntime());
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsXPConnect::FinishTraverse()
|
||||
{
|
||||
|
@ -2401,7 +2429,9 @@ nsXPConnect::GetRuntime(JSRuntime **runtime)
|
|||
if (!runtime)
|
||||
return NS_ERROR_NULL_POINTER;
|
||||
|
||||
*runtime = GetRuntime()->GetJSRuntime();
|
||||
JSRuntime *rt = GetRuntime()->GetJSRuntime();
|
||||
JS_AbortIfWrongThread(rt);
|
||||
*runtime = rt;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -556,6 +556,10 @@ public:
|
|||
nsCycleCollectionTraversalCallback &cb);
|
||||
|
||||
// nsCycleCollectionLanguageRuntime
|
||||
virtual void NotifyLeaveMainThread();
|
||||
virtual void NotifyEnterCycleCollectionThread();
|
||||
virtual void NotifyLeaveCycleCollectionThread();
|
||||
virtual void NotifyEnterMainThread();
|
||||
virtual nsresult BeginCycleCollection(nsCycleCollectionTraversalCallback &cb,
|
||||
bool explainExpectedLiveGarbage);
|
||||
virtual nsresult FinishTraverse();
|
||||
|
|
|
@ -3464,6 +3464,12 @@ class nsCycleCollectorRunner : public nsRunnable
|
|||
bool mShutdown;
|
||||
bool mCollected;
|
||||
|
||||
nsCycleCollectionJSRuntime *GetJSRuntime()
|
||||
{
|
||||
return static_cast<nsCycleCollectionJSRuntime*>
|
||||
(mCollector->mRuntimes[nsIProgrammingLanguage::JAVASCRIPT]);
|
||||
}
|
||||
|
||||
public:
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
|
@ -3494,7 +3500,9 @@ public:
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
GetJSRuntime()->NotifyEnterCycleCollectionThread();
|
||||
mCollected = mCollector->BeginCollection(mListener);
|
||||
GetJSRuntime()->NotifyLeaveCycleCollectionThread();
|
||||
|
||||
mReply.Notify();
|
||||
}
|
||||
|
@ -3533,8 +3541,10 @@ public:
|
|||
NS_ASSERTION(!mListener, "Should have cleared this already!");
|
||||
mListener = aListener;
|
||||
|
||||
GetJSRuntime()->NotifyLeaveMainThread();
|
||||
mRequest.Notify();
|
||||
mReply.Wait();
|
||||
GetJSRuntime()->NotifyEnterMainThread();
|
||||
|
||||
mListener = nsnull;
|
||||
|
||||
|
|
|
@ -76,6 +76,14 @@ void nsCycleCollector_shutdown();
|
|||
// nsCycleCollector_doCollect directly.
|
||||
struct nsCycleCollectionJSRuntime : public nsCycleCollectionLanguageRuntime
|
||||
{
|
||||
/**
|
||||
* Called before/after transitioning to/from the main thread.
|
||||
*/
|
||||
virtual void NotifyLeaveMainThread() = 0;
|
||||
virtual void NotifyEnterCycleCollectionThread() = 0;
|
||||
virtual void NotifyLeaveCycleCollectionThread() = 0;
|
||||
virtual void NotifyEnterMainThread() = 0;
|
||||
|
||||
/**
|
||||
* Should we force a JavaScript GC before a CC?
|
||||
*/
|
||||
|
|
Загрузка…
Ссылка в новой задаче