mirror of
https://github.com/UOX3DevTeam/UOX3
synced 2026-08-13 12:27:04 -04:00
195 lines
5.4 KiB
C++
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
|