Mozilla製のJavaScriptエンジンであるSpiderMonkey(v38)をC++アプリケーションに組み込むことを検討しています。
通常のブラウザで実装されているsetIntervalの実装方法がわからずに困っています。
setIntervalを実装するには、setIntervalに渡されたクロージャを別スレッドで実行する必要があると思い、
JSRuntimeを生成したスレッドではないスレッドで、実行したところ失敗しました。
JSRuntimeは、シングルスレッドでしか動かない仕様のようですね。
JSRuntimeを生成したスレッドではない別のスレッドで、JS_CallFunction等を呼び出すと、内部でスレッドIDの検査が行われて、実行できなようようになっているとどこかに書いてありました。
実際に試してみたら、クラッシュしました。
マルチスレッド環境で動かすには、スレッドの数だけJSRuntimeが必要なようですが、
そうするとJSRuntime同士は、メモリを共有していないので、setIntervalに渡すクロージャの外で定義したオブジェクトは、使えなくなるのではないでしょうか。
特にマルチスレッドにこだわっているわけではありません。
ブラウザ上では、シングルスレッド上で動いているようですし、シングルスレッドでの実装方法でも構いません。
setIntervalの実装方法をご教授願います。
ちなみに、以下が1つのJSRuntimeのみを用いてマルチスレッドで動かして失敗した例です。
C++
1/* 2SpiderMonkeyで同一のJSRuntimeを別スレッドで使用して、エラーになる例。 3*/ 4 5#include <iostream> 6#include <thread> 7#include <jsapi.h> 8 9using namespace std; 10 11/** 12 * 実行するJavaScript。 13 * spawn関数は、引数のクロージャを別のスレッドで評価する。 14 * ここでは、コンソールに'hello'と出力する。 15 * spawn、print関数は、下記でglobalオブジェクトに定義してある。 16 * 下記のmain関数内のJS::Evaluateでこのスクリプトを評価している。 17 */ 18static const char* script = "spawn(() => print(`hello\n`));"; 19 20/** 21 * spawnのC++での実装。 22 * 別スレッドでJS_CallFunctionを実行している。 23 */ 24bool js_spawn(JSContext *cx, unsigned argc, JS::Value *vp) 25{ 26 JS::CallArgs args = CallArgsFromVp(argc, vp); 27 // 念の為、別スレッドで呼ばれてもいいように、GCされないようにする。 28 JS::PersistentRooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx)); 29 // 念の為、別スレッドで呼ばれてもいいように、GCされないようにする。 30 JS::PersistentRooted<JSFunction*> func(cx, JS_ValueToFunction(cx, args[0])); 31 32 // スレッド生成。 33 auto th = std::thread( [cx, global, func]() { 34 JS::RootedValue rval(cx); 35 if( !JS_CallFunction(cx, global, func, JS::HandleValueArray::empty(), &rval) ) 36 cerr << "ERROR: failed to call function at js_spawn()" << endl; 37 }); 38 th.join(); 39 return true; 40} 41 42/** 43 * 文字列をコンソールに出力する。 44 */ 45bool js_print(JSContext *cx, unsigned argc, JS::Value *vp) 46{ 47 JS::CallArgs args = CallArgsFromVp(argc, vp); 48 char* s = JS_EncodeString(cx, args[0].toString()); 49 printf("%s", s); 50 JS_free(cx, s); 51 return true; 52} 53 54static const size_t heapMaxBytes = 16L * 1024 * 1024; 55static const size_t nurseryBytes = 4L * 1024 * 1024; 56static const size_t maxStackSize = 6L * 1024 * 1024; 57static const size_t stackChunkSize = 8192; 58 59int main( int argc, char** argv ) { 60 61 JS_Init(); 62 63 JSRuntime *rt = JS_NewRuntime(heapMaxBytes, nurseryBytes); 64 65 JS_SetNativeStackQuota(rt, maxStackSize); 66 67 JSContext *cx = JS_NewContext(rt, stackChunkSize); 68 69 { 70 JSAutoRequest ar(cx); 71 72 JSClass globalClass = { 73 "global", 74 JSCLASS_GLOBAL_FLAGS 75 }; 76 77 JS::RootedObject global(cx, JS_NewGlobalObject( cx, &globalClass, nullptr, JS::FireOnNewGlobalHook)); 78 79 JS::RootedValue rval(cx); 80 { 81 JSAutoCompartment ac(cx, global); 82 83 JSFunctionSpec globalFunctions[] = { 84 JS_FS("print", js_print, 1, 0), 85 JS_FS("spawn", js_spawn, 1, 0), 86 JS_FS_END 87 }; 88 89 JS_DefineFunctions(cx, global, globalFunctions); 90 91 JS::CompileOptions opts(cx); 92 opts.setLine(1); 93 94 JS::Evaluate(cx, global, opts, script, strlen(script), &rval); 95 } 96 97 } 98 99 JS_DestroyContext(cx); 100 JS_DestroyRuntime(rt); 101 JS_ShutDown(); 102}