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 : b20ddca63da88a8efe2f5dc06364785aebe8b9ea
This commit is contained in:
Luke Wagner 2011-07-01 14:11:31 -07:00
parent 1350be5c5c
commit 4263a80f79
21 changed files with 254 additions and 637 deletions

View File

@ -5347,6 +5347,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;

View File

@ -80,8 +80,6 @@ CPPSRCS = \
testScriptObject.cpp \
testSetProperty.cpp \
testStringBuffer.cpp \
testThreadGC.cpp \
testThreads.cpp \
testTrap.cpp \
testUTF8.cpp \
testVersion.cpp \

View File

@ -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_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

View File

@ -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

View File

@ -596,6 +596,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, Valueify(v1), Valueify(v2), equal);
}
@ -603,6 +604,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, Valueify(v1), Valueify(v2), equal);
}
@ -610,6 +612,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, Valueify(v1), Valueify(v2), same);
}
@ -647,6 +650,10 @@ JSRuntime::JSRuntime()
bool
JSRuntime::init(uint32 maxbytes)
{
#ifdef JS_THREADSAFE
ownerThread_ = js_CurrentThreadId();
#endif
#ifdef JS_METHODJIT_SPEW
JMCheckLogging();
#endif
@ -742,6 +749,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)
{
@ -1998,12 +2027,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);
}
@ -5332,12 +5363,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;
@ -5523,6 +5558,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;
@ -5539,6 +5576,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 :
@ -6113,6 +6152,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()) {
@ -6130,9 +6171,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_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

View File

@ -3729,6 +3729,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
/************************************************************************/
/*

View File

@ -316,6 +316,8 @@ static const size_t TEMP_POOL_CHUNK_SIZE = 4096 - ARENA_HEADER_SIZE_HACK;
JSContext *
js_NewContext(JSRuntime *rt, size_t stackChunkSize)
{
JS_AbortIfWrongThread(rt);
JSContext *cx;
JSBool first;
JSContextCallback cxCallback;
@ -427,13 +429,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

View File

@ -327,7 +327,8 @@ typedef js::Vector<JSCompartment *, 0, js::SystemAllocPolicy> CompartmentVector;
}
struct JSRuntime {
struct JSRuntime
{
/* Default compartment. */
JSCompartment *atomsCompartment;
#ifdef JS_THREADSAFE
@ -340,6 +341,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;
@ -1334,11 +1349,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
static inline JSAtom **

View File

@ -1905,6 +1905,7 @@ void
MaybeGC(JSContext *cx)
{
JSRuntime *rt = cx->runtime;
JS_ASSERT(rt->onOwnerThread());
if (rt->gcZeal()) {
GCREASON(MAYBEGC);
@ -2671,6 +2672,7 @@ void
js_GC(JSContext *cx, JSCompartment *comp, JSGCInvocationKind gckind)
{
JSRuntime *rt = cx->runtime;
JS_AbortIfWrongThread(rt);
/*
* Don't collect garbage if the runtime isn't up, and cx is not the last
@ -2856,6 +2858,8 @@ JSCompartment *
NewCompartment(JSContext *cx, JSPrincipals *principals)
{
JSRuntime *rt = cx->runtime;
JS_AbortIfWrongThread(rt);
JSCompartment *compartment = cx->new_<JSCompartment>(rt);
if (compartment && compartment->init()) {
// Any compartment with the trusted principals -- and there can be

View File

@ -1111,7 +1111,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;
}
@ -3233,245 +3233,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)
{
@ -3656,7 +3417,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);
@ -4197,7 +3958,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),
@ -4337,7 +4097,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",

View File

@ -170,6 +170,7 @@ class WorkerParent {
}
void disposeChildren();
void notifyTerminating();
};
template <class T>
@ -528,11 +529,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. */
@ -593,6 +595,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
@ -603,7 +606,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) {
@ -616,13 +619,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;
@ -754,11 +768,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
@ -797,6 +817,11 @@ class Worker : public WorkerParent
JS_TriggerOperationCallback(context);
}
void notifyTerminating() {
setTerminateFlag();
WorkerParent::notifyTerminating();
}
void processOneEvent();
/* Trace method to be called from C++. */
@ -977,6 +1002,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()
{
@ -1110,7 +1143,8 @@ Worker::processOneEvent()
Event::Result result;
{
JSAutoRequest req(context);
JSAutoSetRuntimeThread asrt(JS_GetRuntime(context));
JSAutoRequest ar(context);
result = event->process(context);
}
@ -1126,7 +1160,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);
@ -1260,9 +1295,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

View File

@ -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

View File

@ -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.");
}

View File

@ -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

View File

@ -762,6 +762,8 @@ mozJSComponentLoader::GlobalForLocation(nsILocalFile *aComponentFile,
JSPrincipals* jsPrincipals = nsnull;
JSCLContextHelper cx(this);
JS_AbortIfWrongThread(JS_GetRuntime(cx));
// preserve caller's compartment
js::PreserveCompartment pc(cx);

View File

@ -532,6 +532,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()
{
@ -2444,7 +2472,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;
}

View File

@ -502,6 +502,7 @@ XPCCallContext::GetCalleeClassInfo(nsIClassInfo * *aCalleeClassInfo)
NS_IMETHODIMP
XPCCallContext::GetJSContext(JSContext * *aJSContext)
{
JS_AbortIfWrongThread(JS_GetRuntime(mJSContext));
*aJSContext = mJSContext;
return NS_OK;
}

View File

@ -3544,6 +3544,8 @@ xpc_EvalInSandbox(JSContext *cx, JSObject *sandbox, const nsAString& source,
const char *filename, PRInt32 lineNo,
JSVersion jsVersion, PRBool returnStringOnly, jsval *rval)
{
JS_AbortIfWrongThread(JS_GetRuntime(cx));
#ifdef DEBUG
// NB: The "unsafe" unwrap here is OK because we must be called from chrome.
{

View File

@ -546,6 +546,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();

View File

@ -3448,6 +3448,12 @@ class nsCycleCollectorRunner : public nsRunnable
PRBool mShutdown;
PRBool mCollected;
nsCycleCollectionJSRuntime *GetJSRuntime()
{
return static_cast<nsCycleCollectionJSRuntime*>
(mCollector->mRuntimes[nsIProgrammingLanguage::JAVASCRIPT]);
}
public:
NS_IMETHOD Run()
{
@ -3478,7 +3484,9 @@ public:
return NS_OK;
}
GetJSRuntime()->NotifyEnterCycleCollectionThread();
mCollected = mCollector->BeginCollection(mListener);
GetJSRuntime()->NotifyLeaveCycleCollectionThread();
mReply.Notify();
}
@ -3517,8 +3525,10 @@ public:
NS_ASSERTION(!mListener, "Should have cleared this already!");
mListener = aListener;
GetJSRuntime()->NotifyLeaveMainThread();
mRequest.Notify();
mReply.Wait();
GetJSRuntime()->NotifyEnterMainThread();
mListener = nsnull;

View File

@ -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?
*/