uox3/spidermonkey/jsapi-tests/testThreadGC.cpp
2025-03-19 21:25:58 +10:00

195 lines
5.4 KiB
C++

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